@opencard-dev/core 0.1.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.
Files changed (53) hide show
  1. package/dist/db.d.ts +145 -0
  2. package/dist/db.d.ts.map +1 -0
  3. package/dist/db.js +373 -0
  4. package/dist/db.js.map +1 -0
  5. package/dist/errors.d.ts +72 -0
  6. package/dist/errors.d.ts.map +1 -0
  7. package/dist/errors.js +124 -0
  8. package/dist/errors.js.map +1 -0
  9. package/dist/index.d.ts +34 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +67 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/logger.d.ts +53 -0
  14. package/dist/logger.d.ts.map +1 -0
  15. package/dist/logger.js +109 -0
  16. package/dist/logger.js.map +1 -0
  17. package/dist/rules-engine.d.ts +159 -0
  18. package/dist/rules-engine.d.ts.map +1 -0
  19. package/dist/rules-engine.js +375 -0
  20. package/dist/rules-engine.js.map +1 -0
  21. package/dist/rules-store.d.ts +187 -0
  22. package/dist/rules-store.d.ts.map +1 -0
  23. package/dist/rules-store.js +291 -0
  24. package/dist/rules-store.js.map +1 -0
  25. package/dist/rules-validation.d.ts +54 -0
  26. package/dist/rules-validation.d.ts.map +1 -0
  27. package/dist/rules-validation.js +110 -0
  28. package/dist/rules-validation.js.map +1 -0
  29. package/dist/stripe-client.d.ts +154 -0
  30. package/dist/stripe-client.d.ts.map +1 -0
  31. package/dist/stripe-client.js +444 -0
  32. package/dist/stripe-client.js.map +1 -0
  33. package/dist/test-utils.d.ts +55 -0
  34. package/dist/test-utils.d.ts.map +1 -0
  35. package/dist/test-utils.js +91 -0
  36. package/dist/test-utils.js.map +1 -0
  37. package/dist/tracker.d.ts +130 -0
  38. package/dist/tracker.d.ts.map +1 -0
  39. package/dist/tracker.js +196 -0
  40. package/dist/tracker.js.map +1 -0
  41. package/dist/transaction-reconciler.d.ts +30 -0
  42. package/dist/transaction-reconciler.d.ts.map +1 -0
  43. package/dist/transaction-reconciler.js +131 -0
  44. package/dist/transaction-reconciler.js.map +1 -0
  45. package/dist/types.d.ts +194 -0
  46. package/dist/types.d.ts.map +1 -0
  47. package/dist/types.js +7 -0
  48. package/dist/types.js.map +1 -0
  49. package/dist/webhooks.d.ts +121 -0
  50. package/dist/webhooks.d.ts.map +1 -0
  51. package/dist/webhooks.js +307 -0
  52. package/dist/webhooks.js.map +1 -0
  53. package/package.json +46 -0
package/dist/db.d.ts ADDED
@@ -0,0 +1,145 @@
1
+ /**
2
+ * OpenCard SQLite Database
3
+ *
4
+ * ─── Instance-per-call pattern (intentional) ─────────────────────────────────
5
+ *
6
+ * Callers create a new OpenCardDatabase() per operation rather than sharing
7
+ * a singleton. This was a deliberate choice for launch:
8
+ *
9
+ * Why: Each handler is self-contained — opens DB, does work, closes it.
10
+ * No shared state, no initialization ordering bugs, no "who closed the
11
+ * connection" surprises. Easy to test, easy to reason about.
12
+ *
13
+ * Cost: ~5ms overhead per call (file handle + pragma setup). Negligible
14
+ * at current call volumes (a few per minute). SQLite handles multiple
15
+ * connections to the same file natively via WAL mode — no corruption
16
+ * risk, no lock contention at this scale.
17
+ *
18
+ * Future: If profiling shows this matters (it won't for a while),
19
+ * switch to dependency injection — initialize once at server startup,
20
+ * pass the instance into handlers. Same pattern the webhook server
21
+ * already uses for TransactionReconciler.
22
+ *
23
+ * ─────────────────────────────────────────────────────────────────────────────
24
+ */
25
+ import Database from 'better-sqlite3';
26
+ export interface ApprovalRequest {
27
+ id: string;
28
+ card_id: string;
29
+ amount: number;
30
+ currency: string;
31
+ merchant_name: string;
32
+ merchant_category: string | null;
33
+ reason: string;
34
+ status: string;
35
+ decided_by: string | null;
36
+ decided_at: string | null;
37
+ decision_note: string | null;
38
+ created_at: string;
39
+ expires_at: string;
40
+ }
41
+ export interface StoredTransaction {
42
+ id: string;
43
+ stripe_id: string;
44
+ card_id: string;
45
+ amount: number;
46
+ currency: string;
47
+ merchant_name: string | null;
48
+ merchant_category: string | null;
49
+ status: string;
50
+ stripe_created_at: string | null;
51
+ recorded_at: string;
52
+ }
53
+ export interface StoredAuthorization {
54
+ id: string;
55
+ stripe_id: string;
56
+ card_id: string;
57
+ amount: number;
58
+ currency: string;
59
+ merchant_name: string | null;
60
+ merchant_category: string | null;
61
+ status: string;
62
+ decision_reason: string | null;
63
+ created_at: string;
64
+ updated_at: string;
65
+ }
66
+ export interface RuleVersion {
67
+ id: number;
68
+ rule_id: string;
69
+ version: number;
70
+ config: string;
71
+ changed_by: string | null;
72
+ changed_at: string;
73
+ change_type: string;
74
+ }
75
+ export interface StoredRule {
76
+ id: string;
77
+ card_id: string | null;
78
+ name: string | null;
79
+ config: string;
80
+ enabled: number;
81
+ created_at: string;
82
+ updated_at: string;
83
+ }
84
+ export interface StoredCard {
85
+ id: string;
86
+ stripe_id: string;
87
+ status: string;
88
+ created_at: string;
89
+ }
90
+ export interface StoredCardholder {
91
+ id: string;
92
+ name: string;
93
+ email: string;
94
+ created_at: string;
95
+ }
96
+ export declare class OpenCardDatabase {
97
+ private readonly dbPath;
98
+ private db;
99
+ constructor(dbPath?: string);
100
+ private initialize;
101
+ private runMigrations;
102
+ getDb(): Database.Database;
103
+ close(): void;
104
+ createTransaction(record: Omit<StoredTransaction, 'id' | 'recorded_at'>): StoredTransaction;
105
+ getTransaction(id: string): StoredTransaction | null;
106
+ getTransactionsForCard(cardId: string, limit?: number): StoredTransaction[];
107
+ createAuthorization(record: Omit<StoredAuthorization, 'id' | 'created_at' | 'updated_at'>): StoredAuthorization;
108
+ updateAuthorization(id: string, updates: Partial<Omit<StoredAuthorization, 'id' | 'stripe_id' | 'created_at'>>): StoredAuthorization;
109
+ getAuthorization(id: string): StoredAuthorization | null;
110
+ getAuthorizationByStripeId(stripeId: string): StoredAuthorization | null;
111
+ getPendingAuthorizationsOlderThan(cardId: string, hours: number): StoredAuthorization[];
112
+ createRuleVersion(ruleId: string, config: string, changeType: string, changedBy?: string): RuleVersion;
113
+ getRuleHistory(ruleId: string): RuleVersion[];
114
+ getRuleAtVersion(ruleId: string, version: number): RuleVersion | null;
115
+ saveRule(id: string, cardId: string | null, name: string | null, config: string): void;
116
+ getRule(id: string): {
117
+ id: string;
118
+ config: string;
119
+ } | null;
120
+ deleteRule(id: string): void;
121
+ createApprovalRequest(record: {
122
+ card_id: string;
123
+ amount: number;
124
+ currency?: string;
125
+ merchant_name: string;
126
+ merchant_category?: string | null;
127
+ reason: string;
128
+ timeout_seconds?: number;
129
+ }): ApprovalRequest;
130
+ getApprovalRequest(id: string): ApprovalRequest | null;
131
+ listPendingApprovalRequests(): ApprovalRequest[];
132
+ listAllApprovalRequests(limit?: number): ApprovalRequest[];
133
+ approveRequest(id: string, decidedBy: string, note?: string): ApprovalRequest;
134
+ denyRequest(id: string, decidedBy: string, note?: string): ApprovalRequest;
135
+ expireStaleApprovalRequests(): number;
136
+ }
137
+ /**
138
+ * Initialize and return a new database instance.
139
+ * Call this at server startup to get a DB instance to inject into services.
140
+ * Prefer this over the module-level singleton to avoid import-time side effects.
141
+ *
142
+ * @param dbPath - Optional path override. Defaults to OPENCARD_DB_PATH env var or ~/.opencard/opencard.db
143
+ */
144
+ export declare function initDatabase(dbPath?: string): OpenCardDatabase;
145
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
package/dist/db.js ADDED
@@ -0,0 +1,373 @@
1
+ "use strict";
2
+ /**
3
+ * OpenCard SQLite Database
4
+ *
5
+ * ─── Instance-per-call pattern (intentional) ─────────────────────────────────
6
+ *
7
+ * Callers create a new OpenCardDatabase() per operation rather than sharing
8
+ * a singleton. This was a deliberate choice for launch:
9
+ *
10
+ * Why: Each handler is self-contained — opens DB, does work, closes it.
11
+ * No shared state, no initialization ordering bugs, no "who closed the
12
+ * connection" surprises. Easy to test, easy to reason about.
13
+ *
14
+ * Cost: ~5ms overhead per call (file handle + pragma setup). Negligible
15
+ * at current call volumes (a few per minute). SQLite handles multiple
16
+ * connections to the same file natively via WAL mode — no corruption
17
+ * risk, no lock contention at this scale.
18
+ *
19
+ * Future: If profiling shows this matters (it won't for a while),
20
+ * switch to dependency injection — initialize once at server startup,
21
+ * pass the instance into handlers. Same pattern the webhook server
22
+ * already uses for TransactionReconciler.
23
+ *
24
+ * ─────────────────────────────────────────────────────────────────────────────
25
+ */
26
+ var __importDefault = (this && this.__importDefault) || function (mod) {
27
+ return (mod && mod.__esModule) ? mod : { "default": mod };
28
+ };
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ exports.OpenCardDatabase = void 0;
31
+ exports.initDatabase = initDatabase;
32
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
33
+ const fs_1 = __importDefault(require("fs"));
34
+ const path_1 = __importDefault(require("path"));
35
+ const os_1 = __importDefault(require("os"));
36
+ class OpenCardDatabase {
37
+ dbPath;
38
+ db = null;
39
+ constructor(dbPath) {
40
+ if (dbPath) {
41
+ this.dbPath = dbPath;
42
+ }
43
+ else if (process.env.OPENCARD_DB_PATH) {
44
+ this.dbPath = process.env.OPENCARD_DB_PATH;
45
+ }
46
+ else {
47
+ const openCardDir = path_1.default.join(os_1.default.homedir(), '.opencard');
48
+ this.dbPath = path_1.default.join(openCardDir, 'opencard.db');
49
+ }
50
+ const dir = path_1.default.dirname(this.dbPath);
51
+ if (!fs_1.default.existsSync(dir)) {
52
+ fs_1.default.mkdirSync(dir, { recursive: true });
53
+ console.log(`[OpenCardDB] Created directory: ${dir}`);
54
+ }
55
+ this.initialize();
56
+ }
57
+ initialize() {
58
+ if (this.db)
59
+ return;
60
+ this.db = new better_sqlite3_1.default(this.dbPath);
61
+ this.db.pragma('journal_mode = WAL');
62
+ this.db.pragma('foreign_keys = ON');
63
+ console.log(`[OpenCardDB] Opened database: ${this.dbPath}`);
64
+ this.runMigrations();
65
+ }
66
+ runMigrations() {
67
+ if (!this.db) {
68
+ throw new Error('[OpenCardDB] Database not initialized');
69
+ }
70
+ const migrationsDir = path_1.default.join(__dirname, '..', 'migrations');
71
+ if (!fs_1.default.existsSync(migrationsDir)) {
72
+ console.warn(`[OpenCardDB] Migrations directory not found: ${migrationsDir}`);
73
+ return;
74
+ }
75
+ const files = fs_1.default.readdirSync(migrationsDir)
76
+ .filter(f => f.endsWith('.sql'))
77
+ .sort();
78
+ for (const file of files) {
79
+ const migrationName = file;
80
+ const migrationPath = path_1.default.join(migrationsDir, file);
81
+ const sql = fs_1.default.readFileSync(migrationPath, 'utf-8');
82
+ // Check if migrations table exists each iteration — the first migration creates it.
83
+ const migrationsTableExists = this.db
84
+ .prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='migrations'")
85
+ .get();
86
+ if (migrationsTableExists) {
87
+ const alreadyApplied = this.db
88
+ .prepare('SELECT 1 FROM migrations WHERE name = ?')
89
+ .get(migrationName);
90
+ if (alreadyApplied) {
91
+ console.log(`[OpenCardDB] Already applied: ${migrationName}`);
92
+ continue;
93
+ }
94
+ }
95
+ try {
96
+ this.db.exec(sql);
97
+ this.db.prepare('INSERT INTO migrations (name) VALUES (?)').run(migrationName);
98
+ console.log(`[OpenCardDB] Applied migration: ${migrationName}`);
99
+ }
100
+ catch (error) {
101
+ throw new Error(`[OpenCardDB] Failed to apply migration ${migrationName}: ${error instanceof Error ? error.message : error}`);
102
+ }
103
+ }
104
+ }
105
+ getDb() {
106
+ if (!this.db) {
107
+ throw new Error('[OpenCardDB] Database not initialized');
108
+ }
109
+ return this.db;
110
+ }
111
+ close() {
112
+ if (this.db) {
113
+ this.db.close();
114
+ this.db = null;
115
+ console.log(`[OpenCardDB] Closed database`);
116
+ }
117
+ }
118
+ createTransaction(record) {
119
+ const db = this.getDb();
120
+ const id = `txn_${Date.now()}_${Math.random().toString(36).substring(7)}`;
121
+ const stmt = db.prepare(`
122
+ INSERT INTO transactions (
123
+ id, stripe_id, card_id, amount, currency,
124
+ merchant_name, merchant_category, status, stripe_created_at
125
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
126
+ `);
127
+ stmt.run(id, record.stripe_id, record.card_id, record.amount, record.currency, record.merchant_name, record.merchant_category, record.status, record.stripe_created_at);
128
+ const result = db
129
+ .prepare('SELECT * FROM transactions WHERE id = ?')
130
+ .get(id);
131
+ console.log(`[OpenCardDB] Created transaction: ${id}`);
132
+ return result;
133
+ }
134
+ getTransaction(id) {
135
+ const db = this.getDb();
136
+ return db
137
+ .prepare('SELECT * FROM transactions WHERE id = ?')
138
+ .get(id) || null;
139
+ }
140
+ getTransactionsForCard(cardId, limit = 100) {
141
+ const db = this.getDb();
142
+ return db
143
+ .prepare('SELECT * FROM transactions WHERE card_id = ? ORDER BY stripe_created_at DESC LIMIT ?')
144
+ .all(cardId, limit);
145
+ }
146
+ createAuthorization(record) {
147
+ const db = this.getDb();
148
+ const id = `auth_${Date.now()}_${Math.random().toString(36).substring(7)}`;
149
+ const stmt = db.prepare(`
150
+ INSERT INTO authorizations (
151
+ id, stripe_id, card_id, amount, currency,
152
+ merchant_name, merchant_category, status, decision_reason
153
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
154
+ `);
155
+ stmt.run(id, record.stripe_id, record.card_id, record.amount, record.currency, record.merchant_name, record.merchant_category, record.status, record.decision_reason);
156
+ const result = db
157
+ .prepare('SELECT * FROM authorizations WHERE id = ?')
158
+ .get(id);
159
+ console.log(`[OpenCardDB] Created authorization: ${id}`);
160
+ return result;
161
+ }
162
+ updateAuthorization(id, updates) {
163
+ const db = this.getDb();
164
+ const existingAuth = db
165
+ .prepare('SELECT * FROM authorizations WHERE id = ?')
166
+ .get(id);
167
+ if (!existingAuth) {
168
+ throw new Error(`[OpenCardDB] Authorization ${id} not found`);
169
+ }
170
+ const updatedAuth = { ...existingAuth, ...updates };
171
+ const stmt = db.prepare(`
172
+ UPDATE authorizations
173
+ SET card_id = ?, amount = ?, currency = ?,
174
+ merchant_name = ?, merchant_category = ?, status = ?, decision_reason = ?,
175
+ updated_at = CURRENT_TIMESTAMP
176
+ WHERE id = ?
177
+ `);
178
+ stmt.run(updatedAuth.card_id, updatedAuth.amount, updatedAuth.currency, updatedAuth.merchant_name, updatedAuth.merchant_category, updatedAuth.status, updatedAuth.decision_reason, id);
179
+ const result = db
180
+ .prepare('SELECT * FROM authorizations WHERE id = ?')
181
+ .get(id);
182
+ console.log(`[OpenCardDB] Updated authorization: ${id}`);
183
+ return result;
184
+ }
185
+ getAuthorization(id) {
186
+ const db = this.getDb();
187
+ return db
188
+ .prepare('SELECT * FROM authorizations WHERE id = ?')
189
+ .get(id) || null;
190
+ }
191
+ getAuthorizationByStripeId(stripeId) {
192
+ const db = this.getDb();
193
+ return db
194
+ .prepare('SELECT * FROM authorizations WHERE stripe_id = ?')
195
+ .get(stripeId) || null;
196
+ }
197
+ getPendingAuthorizationsOlderThan(cardId, hours) {
198
+ const db = this.getDb();
199
+ // Use SQLite's datetime() to compute the cutoff relative to 'now' — this
200
+ // avoids timezone/format mismatches between JS ISO strings and SQLite's
201
+ // CURRENT_TIMESTAMP format ('YYYY-MM-DD HH:MM:SS' without timezone).
202
+ const cutoffExpr = `-${hours} hours`;
203
+ return db
204
+ .prepare("SELECT * FROM authorizations WHERE card_id = ? AND status = ? AND created_at < datetime('now', ?) ORDER BY created_at")
205
+ .all(cardId, 'pending', cutoffExpr);
206
+ }
207
+ createRuleVersion(ruleId, config, changeType, changedBy) {
208
+ const db = this.getDb();
209
+ const maxVersion = db
210
+ .prepare('SELECT MAX(version) as v FROM rule_versions WHERE rule_id = ?')
211
+ .get(ruleId);
212
+ const nextVersion = (maxVersion.v || 0) + 1;
213
+ const stmt = db.prepare(`
214
+ INSERT INTO rule_versions (rule_id, version, config, changed_by, change_type)
215
+ VALUES (?, ?, ?, ?, ?)
216
+ `);
217
+ stmt.run(ruleId, nextVersion, config, changedBy || null, changeType);
218
+ const result = db
219
+ .prepare('SELECT * FROM rule_versions WHERE rule_id = ? AND version = ?')
220
+ .get(ruleId, nextVersion);
221
+ console.log(`[OpenCardDB] Created rule version: ${ruleId} v${nextVersion} (${changeType})`);
222
+ return result;
223
+ }
224
+ getRuleHistory(ruleId) {
225
+ const db = this.getDb();
226
+ return db
227
+ .prepare('SELECT * FROM rule_versions WHERE rule_id = ? ORDER BY version ASC')
228
+ .all(ruleId);
229
+ }
230
+ getRuleAtVersion(ruleId, version) {
231
+ const db = this.getDb();
232
+ return db
233
+ .prepare('SELECT * FROM rule_versions WHERE rule_id = ? AND version = ?')
234
+ .get(ruleId, version) || null;
235
+ }
236
+ saveRule(id, cardId, name, config) {
237
+ const db = this.getDb();
238
+ const existing = db
239
+ .prepare('SELECT 1 FROM rules WHERE id = ?')
240
+ .get(id);
241
+ if (existing) {
242
+ const stmt = db.prepare(`
243
+ UPDATE rules
244
+ SET card_id = ?, name = ?, config = ?, updated_at = CURRENT_TIMESTAMP
245
+ WHERE id = ?
246
+ `);
247
+ stmt.run(cardId, name, config, id);
248
+ }
249
+ else {
250
+ const stmt = db.prepare(`
251
+ INSERT INTO rules (id, card_id, name, config)
252
+ VALUES (?, ?, ?, ?)
253
+ `);
254
+ stmt.run(id, cardId, name, config);
255
+ }
256
+ }
257
+ getRule(id) {
258
+ const db = this.getDb();
259
+ return db
260
+ .prepare('SELECT id, config FROM rules WHERE id = ?')
261
+ .get(id) || null;
262
+ }
263
+ deleteRule(id) {
264
+ const db = this.getDb();
265
+ db.prepare('DELETE FROM rules WHERE id = ?').run(id);
266
+ console.log(`[OpenCardDB] Deleted rule: ${id}`);
267
+ }
268
+ // ─── Approval Requests ───────────────────────────────────────────────────
269
+ createApprovalRequest(record) {
270
+ const db = this.getDb();
271
+ const id = `apr_${Date.now()}_${Math.random().toString(36).substring(7)}`;
272
+ const timeoutSeconds = record.timeout_seconds ?? 300;
273
+ const expiresAt = new Date(Date.now() + timeoutSeconds * 1000)
274
+ .toISOString()
275
+ .replace('T', ' ')
276
+ .slice(0, 19);
277
+ const stmt = db.prepare(`
278
+ INSERT INTO approval_requests (
279
+ id, card_id, amount, currency, merchant_name, merchant_category,
280
+ reason, status, expires_at
281
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?)
282
+ `);
283
+ stmt.run(id, record.card_id, record.amount, record.currency ?? 'usd', record.merchant_name, record.merchant_category ?? null, record.reason, expiresAt);
284
+ const result = db
285
+ .prepare('SELECT * FROM approval_requests WHERE id = ?')
286
+ .get(id);
287
+ console.log(`[OpenCardDB] Created approval request: ${id}`);
288
+ return result;
289
+ }
290
+ getApprovalRequest(id) {
291
+ const db = this.getDb();
292
+ return db
293
+ .prepare('SELECT * FROM approval_requests WHERE id = ?')
294
+ .get(id) || null;
295
+ }
296
+ listPendingApprovalRequests() {
297
+ const db = this.getDb();
298
+ return db
299
+ .prepare("SELECT * FROM approval_requests WHERE status = 'pending' ORDER BY created_at ASC")
300
+ .all();
301
+ }
302
+ listAllApprovalRequests(limit = 50) {
303
+ const db = this.getDb();
304
+ return db
305
+ .prepare('SELECT * FROM approval_requests ORDER BY created_at DESC LIMIT ?')
306
+ .all(limit);
307
+ }
308
+ approveRequest(id, decidedBy, note) {
309
+ const db = this.getDb();
310
+ const existing = db
311
+ .prepare('SELECT * FROM approval_requests WHERE id = ?')
312
+ .get(id);
313
+ if (!existing) {
314
+ throw new Error(`Approval request ${id} not found`);
315
+ }
316
+ if (existing.status !== 'pending') {
317
+ throw new Error(`Approval request ${id} is already ${existing.status}`);
318
+ }
319
+ const now = new Date().toISOString().replace('T', ' ').slice(0, 19);
320
+ db.prepare(`
321
+ UPDATE approval_requests
322
+ SET status = 'approved', decided_by = ?, decided_at = ?, decision_note = ?
323
+ WHERE id = ?
324
+ `).run(decidedBy, now, note ?? null, id);
325
+ console.log(`[OpenCardDB] Approved request: ${id} by ${decidedBy}`);
326
+ return db.prepare('SELECT * FROM approval_requests WHERE id = ?').get(id);
327
+ }
328
+ denyRequest(id, decidedBy, note) {
329
+ const db = this.getDb();
330
+ const existing = db
331
+ .prepare('SELECT * FROM approval_requests WHERE id = ?')
332
+ .get(id);
333
+ if (!existing) {
334
+ throw new Error(`Approval request ${id} not found`);
335
+ }
336
+ if (existing.status !== 'pending') {
337
+ throw new Error(`Approval request ${id} is already ${existing.status}`);
338
+ }
339
+ const now = new Date().toISOString().replace('T', ' ').slice(0, 19);
340
+ db.prepare(`
341
+ UPDATE approval_requests
342
+ SET status = 'denied', decided_by = ?, decided_at = ?, decision_note = ?
343
+ WHERE id = ?
344
+ `).run(decidedBy, now, note ?? null, id);
345
+ console.log(`[OpenCardDB] Denied request: ${id} by ${decidedBy}`);
346
+ return db.prepare('SELECT * FROM approval_requests WHERE id = ?').get(id);
347
+ }
348
+ expireStaleApprovalRequests() {
349
+ const db = this.getDb();
350
+ const now = new Date().toISOString().replace('T', ' ').slice(0, 19);
351
+ const result = db.prepare(`
352
+ UPDATE approval_requests
353
+ SET status = 'expired'
354
+ WHERE status = 'pending' AND expires_at <= ?
355
+ `).run(now);
356
+ if (result.changes > 0) {
357
+ console.log(`[OpenCardDB] Expired ${result.changes} stale approval request(s)`);
358
+ }
359
+ return result.changes;
360
+ }
361
+ }
362
+ exports.OpenCardDatabase = OpenCardDatabase;
363
+ /**
364
+ * Initialize and return a new database instance.
365
+ * Call this at server startup to get a DB instance to inject into services.
366
+ * Prefer this over the module-level singleton to avoid import-time side effects.
367
+ *
368
+ * @param dbPath - Optional path override. Defaults to OPENCARD_DB_PATH env var or ~/.opencard/opencard.db
369
+ */
370
+ function initDatabase(dbPath) {
371
+ return new OpenCardDatabase(dbPath);
372
+ }
373
+ //# sourceMappingURL=db.js.map
package/dist/db.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * OpenCard Error Classes
3
+ * ======================
4
+ * Custom error types for OpenCard, designed to wrap Stripe errors while
5
+ * preserving the original error code, message, and type.
6
+ *
7
+ * This allows callers to recover from specific errors (e.g., retry on
8
+ * rate limits, handle authorization failures differently) rather than
9
+ * treating all errors as opaque strings.
10
+ */
11
+ /**
12
+ * Base class for all OpenCard errors.
13
+ * Extends Error and preserves the Stripe error context.
14
+ */
15
+ export declare class OpenCardError extends Error {
16
+ /** Original error if this is wrapping a thrown error */
17
+ cause?: Error;
18
+ /** Context about what operation failed (stripe-client, rules-engine, etc.) */
19
+ component: string;
20
+ /** Stripe error code if available (e.g., 'rate_limit_error', 'authentication_error') */
21
+ stripeCode?: string;
22
+ /** Stripe error type if available (e.g., 'StripeInvalidRequestError') */
23
+ stripeType?: string;
24
+ /** Additional context (cardId, ruleId, etc.) */
25
+ context: Record<string, unknown>;
26
+ constructor(message: string, component: string, context?: Record<string, unknown>);
27
+ }
28
+ /**
29
+ * Wraps a Stripe error, preserving its type and code.
30
+ * Useful for error recovery based on the specific Stripe error.
31
+ *
32
+ * @param error - The original error (likely from a Stripe SDK call)
33
+ * @param message - User-friendly message describing what we were doing
34
+ * @param component - Component name (e.g., 'stripe-client')
35
+ * @param context - Additional context (cardId, etc.)
36
+ * @returns OpenCardError with Stripe error info preserved
37
+ *
38
+ * @example
39
+ * try {
40
+ * await stripe.issuing.cards.create({ ... });
41
+ * } catch (err) {
42
+ * throw wrapStripeError(err, 'Failed to create card', 'stripe-client', { cardholderId });
43
+ * }
44
+ */
45
+ export declare function wrapStripeError(error: unknown, message: string, component: string, context?: Record<string, unknown>): OpenCardError;
46
+ /**
47
+ * Specific error: Stripe authentication failed (invalid key, revoked key, etc.)
48
+ */
49
+ export declare class StripeAuthenticationError extends OpenCardError {
50
+ constructor(message: string, context?: Record<string, unknown>);
51
+ }
52
+ /**
53
+ * Specific error: Rate limit exceeded — client should retry with backoff.
54
+ */
55
+ export declare class RateLimitError extends OpenCardError {
56
+ /** Seconds to wait before retrying (from Stripe's Retry-After header) */
57
+ retryAfter?: number;
58
+ constructor(message: string, component: string, retryAfter?: number, context?: Record<string, unknown>);
59
+ }
60
+ /**
61
+ * Specific error: Invalid parameters (e.g., missing required field, bad enum value)
62
+ */
63
+ export declare class InvalidParameterError extends OpenCardError {
64
+ constructor(message: string, context?: Record<string, unknown>);
65
+ }
66
+ /**
67
+ * Specific error: Rule not found or invalid in the rules store.
68
+ */
69
+ export declare class RuleError extends OpenCardError {
70
+ constructor(message: string, context?: Record<string, unknown>);
71
+ }
72
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;GAGG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACtC,wDAAwD;IACxD,KAAK,CAAC,EAAE,KAAK,CAAC;IAEd,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;IAElB,wFAAwF;IACxF,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAG/B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;CASxC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACpC,aAAa,CAqBf;AAED;;GAEG;AACH,qBAAa,yBAA0B,SAAQ,aAAa;gBAC9C,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;CAKnE;AAED;;GAEG;AACH,qBAAa,cAAe,SAAQ,aAAa;IAC/C,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;gBAGlB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;CAOxC;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,aAAa;gBAC1C,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;CAKnE;AAED;;GAEG;AACH,qBAAa,SAAU,SAAQ,aAAa;gBAC9B,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;CAKnE"}