@develit-services/bank 5.7.1 → 5.9.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/dist/base.cjs +296 -32
- package/dist/base.d.cts +38 -11
- package/dist/base.d.mts +38 -11
- package/dist/base.d.ts +38 -11
- package/dist/base.mjs +296 -32
- package/dist/database/schema.cjs +1 -1
- package/dist/database/schema.d.cts +1 -1
- package/dist/database/schema.d.mts +1 -1
- package/dist/database/schema.d.ts +1 -1
- package/dist/database/schema.mjs +1 -1
- package/dist/export/workflows.cjs +213 -42
- package/dist/export/workflows.d.cts +1 -0
- package/dist/export/workflows.d.mts +1 -0
- package/dist/export/workflows.d.ts +1 -0
- package/dist/export/workflows.mjs +214 -43
- package/dist/service.d.cts +3 -0
- package/dist/service.d.mts +3 -0
- package/dist/service.d.ts +3 -0
- package/dist/shared/{bank.BzDNLxB_.mjs → bank.BT7HayCV.mjs} +4 -0
- package/dist/shared/{bank.BUzoc8p6.d.cts → bank.Bhh_O_5-.d.mts} +1 -1
- package/dist/shared/{bank.DZ3Ow4bP.d.mts → bank.BhyIbhvK.d.cts} +1 -1
- package/dist/shared/{bank.CBpXmaTL.d.ts → bank.Bnc_DoG4.d.ts} +1 -1
- package/dist/shared/{bank.CQjuudum.cjs → bank.BsWBG0gb.cjs} +1 -1
- package/dist/shared/{bank.9Yw4KHyl.cjs → bank.Ca3jzmIb.cjs} +4 -0
- package/dist/shared/{bank.DLU1sOBm.mjs → bank.DAjqsBQN.mjs} +30 -3
- package/dist/shared/{bank.B0DNtuUM.cjs → bank.DBTtRCcf.cjs} +33 -2
- package/dist/shared/{bank.BVXtqHnq.mjs → bank.kz-PKVi5.mjs} +1 -1
- package/dist/shared/{bank.BQwwtIR4.d.ts → bank.mkq4IlpC.d.cts} +68 -2
- package/dist/shared/{bank.BQwwtIR4.d.cts → bank.mkq4IlpC.d.mts} +68 -2
- package/dist/shared/{bank.BQwwtIR4.d.mts → bank.mkq4IlpC.d.ts} +68 -2
- package/dist/types.cjs +2 -2
- package/dist/types.d.cts +4 -4
- package/dist/types.d.mts +4 -4
- package/dist/types.d.ts +4 -4
- package/dist/types.mjs +2 -2
- package/package.json +1 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { first, uuidv4, asNonEmpty } from '@develit-io/backend-sdk';
|
|
2
|
-
import { G as tables, H as relations, v as toBatchedPaymentFromPaymentRequest, z as toPreparedPayment, m as isPaymentCompleted } from '../shared/bank.
|
|
2
|
+
import { G as tables, H as relations, v as toBatchedPaymentFromPaymentRequest, z as toPreparedPayment, m as isPaymentCompleted } from '../shared/bank.kz-PKVi5.mjs';
|
|
3
3
|
import { i as isBatchAuthorized, b as isBatchFailed, d as isBatchProcessing } from '../shared/bank.XqSw509X.mjs';
|
|
4
|
-
import {
|
|
5
|
-
import { g as getBatchByIdQuery, a as getPaymentRequestsByBatchIdQuery, c as checksum, u as upsertBatchCommand, b as getAccountByIdQuery, d as createCredentialsResolver, i as initiateConnector, e as updatePaymentRequestStatusCommand, f as createPaymentCommand } from '../shared/bank.
|
|
4
|
+
import { sql, and, eq, inArray } from 'drizzle-orm';
|
|
5
|
+
import { g as getBatchByIdQuery, a as getPaymentRequestsByBatchIdQuery, c as checksum, u as upsertBatchCommand, b as getAccountByIdQuery, d as createCredentialsResolver, i as initiateConnector, e as updatePaymentRequestStatusCommand, f as isSupersededBy, h as createPaymentCommand, r as resolveIterationBudget } from '../shared/bank.DAjqsBQN.mjs';
|
|
6
6
|
import { WorkflowEntrypoint } from 'cloudflare:workers';
|
|
7
7
|
import { NonRetryableError } from 'cloudflare:workflows';
|
|
8
8
|
import { drizzle } from 'drizzle-orm/d1';
|
|
9
9
|
import 'node:crypto';
|
|
10
10
|
import 'date-fns';
|
|
11
|
-
import '../shared/bank.
|
|
11
|
+
import '../shared/bank.BT7HayCV.mjs';
|
|
12
12
|
import 'drizzle-orm/sqlite-core';
|
|
13
13
|
import '@develit-io/general-codes';
|
|
14
14
|
import 'jose';
|
|
@@ -18,12 +18,17 @@ import 'drizzle-orm/zod';
|
|
|
18
18
|
const updateAccountLastSyncCommand = (db, {
|
|
19
19
|
lastSyncAt,
|
|
20
20
|
accountId,
|
|
21
|
-
lastSyncMetadata
|
|
21
|
+
lastSyncMetadata,
|
|
22
|
+
seenInstanceId
|
|
22
23
|
}) => {
|
|
24
|
+
const markerUnchanged = seenInstanceId === void 0 ? void 0 : (
|
|
25
|
+
// IS instead of = so a null marker compares equal to null.
|
|
26
|
+
sql`json_extract(${tables.account.lastSyncMetadata}, '$.instanceId') IS ${seenInstanceId}`
|
|
27
|
+
);
|
|
23
28
|
const command = db.update(tables.account).set({
|
|
24
29
|
lastSyncAt,
|
|
25
30
|
lastSyncMetadata
|
|
26
|
-
}).where(eq(tables.account.id, accountId)).returning();
|
|
31
|
+
}).where(and(eq(tables.account.id, accountId), markerUnchanged)).returning();
|
|
27
32
|
return {
|
|
28
33
|
command
|
|
29
34
|
};
|
|
@@ -338,6 +343,72 @@ function getStepCount(ctx) {
|
|
|
338
343
|
return ctx.step?.count ?? 0;
|
|
339
344
|
}
|
|
340
345
|
|
|
346
|
+
const MAX_CATCH_UP_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
347
|
+
function resolveSyncWindow(lastSyncAt, nowMs, maxWindowMs) {
|
|
348
|
+
const openEnded = {
|
|
349
|
+
dateFrom: lastSyncAt,
|
|
350
|
+
dateTo: new Date(Math.max(nowMs, lastSyncAt.getTime())),
|
|
351
|
+
isCatchUp: false
|
|
352
|
+
};
|
|
353
|
+
if (!Number.isFinite(maxWindowMs) || maxWindowMs <= 0) return openEnded;
|
|
354
|
+
const clippedToMs = lastSyncAt.getTime() + maxWindowMs;
|
|
355
|
+
if (clippedToMs >= nowMs) return openEnded;
|
|
356
|
+
return {
|
|
357
|
+
dateFrom: lastSyncAt,
|
|
358
|
+
dateTo: new Date(clippedToMs),
|
|
359
|
+
isCatchUp: true
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const STEP_RESULT_BYTE_CEILING = 800 * 1024;
|
|
364
|
+
class StepResultTooLargeError extends NonRetryableError {
|
|
365
|
+
}
|
|
366
|
+
function guardStepResultSize(value, ceilingBytes = STEP_RESULT_BYTE_CEILING) {
|
|
367
|
+
const bytes = new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
368
|
+
if (bytes > ceilingBytes) {
|
|
369
|
+
const count = Array.isArray(value) ? value.length : 1;
|
|
370
|
+
throw new StepResultTooLargeError(
|
|
371
|
+
`Fetched ${count} payments (${bytes} bytes), over the ${ceilingBytes} byte step result ceiling`
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function rethrowFetchError(err) {
|
|
376
|
+
if (err instanceof NonRetryableError) throw err;
|
|
377
|
+
const message = err instanceof Error ? err.message : typeof err === "object" && err !== null && "message" in err ? String(err.message) : JSON.stringify(err);
|
|
378
|
+
throw new Error(message);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const MIN_CATCH_UP_WINDOW_MS = 6 * 60 * 60 * 1e3;
|
|
382
|
+
async function fetchBoundedByWindow({
|
|
383
|
+
window,
|
|
384
|
+
fetchWindow,
|
|
385
|
+
guardResult,
|
|
386
|
+
minWindowMs,
|
|
387
|
+
onNarrowed
|
|
388
|
+
}) {
|
|
389
|
+
let current = window;
|
|
390
|
+
while (true) {
|
|
391
|
+
const result = await fetchWindow(current);
|
|
392
|
+
try {
|
|
393
|
+
guardResult(result);
|
|
394
|
+
return { result, window: current };
|
|
395
|
+
} catch (tooLarge) {
|
|
396
|
+
if (!(tooLarge instanceof StepResultTooLargeError)) throw tooLarge;
|
|
397
|
+
const halvedSpanMs = Math.floor(
|
|
398
|
+
(current.dateTo.getTime() - current.dateFrom.getTime()) / 2
|
|
399
|
+
);
|
|
400
|
+
if (halvedSpanMs < minWindowMs) throw tooLarge;
|
|
401
|
+
current = {
|
|
402
|
+
dateFrom: current.dateFrom,
|
|
403
|
+
dateTo: new Date(current.dateFrom.getTime() + halvedSpanMs),
|
|
404
|
+
// Stops short of the original target, so another pass is needed.
|
|
405
|
+
isCatchUp: true
|
|
406
|
+
};
|
|
407
|
+
onNarrowed?.(current);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
341
412
|
async function pushToQueue(queue, message) {
|
|
342
413
|
if (!Array.isArray(message)) {
|
|
343
414
|
await queue.send(message, { contentType: "v8" });
|
|
@@ -352,7 +423,8 @@ async function pushToQueue(queue, message) {
|
|
|
352
423
|
}
|
|
353
424
|
class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
354
425
|
async run(event, step) {
|
|
355
|
-
const { accountId } = event.payload;
|
|
426
|
+
const { accountId, maxIterations } = event.payload;
|
|
427
|
+
const iterationBudget = resolveIterationBudget(maxIterations);
|
|
356
428
|
const db = drizzle(this.env.BANK_D1, { schema: tables, relations });
|
|
357
429
|
const logger = createWorkflowLogger(event.instanceId);
|
|
358
430
|
if (!accountId) {
|
|
@@ -362,8 +434,7 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
|
362
434
|
"capture workflow start time",
|
|
363
435
|
async () => Date.now()
|
|
364
436
|
);
|
|
365
|
-
|
|
366
|
-
const now = /* @__PURE__ */ new Date();
|
|
437
|
+
for (let iteration = 0; iteration < iterationBudget; iteration++) {
|
|
367
438
|
const account = await step.do("load account", async () => {
|
|
368
439
|
const account2 = await getAccountByIdQuery(db, { accountId });
|
|
369
440
|
if (!account2) {
|
|
@@ -371,10 +442,49 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
|
371
442
|
}
|
|
372
443
|
return account2;
|
|
373
444
|
});
|
|
445
|
+
if (!account.syncEnabled) {
|
|
446
|
+
logger.info("sync.disabled", {
|
|
447
|
+
accountId,
|
|
448
|
+
instanceId: event.instanceId,
|
|
449
|
+
iteration
|
|
450
|
+
});
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
374
453
|
if (!account.lastSyncAt) {
|
|
375
454
|
throw new Error(`lastSyncedAt is not set for account: ${accountId}`);
|
|
376
455
|
}
|
|
377
|
-
|
|
456
|
+
if (isSupersededBy(
|
|
457
|
+
event.instanceId,
|
|
458
|
+
account.lastSyncMetadata?.instanceId,
|
|
459
|
+
accountId
|
|
460
|
+
)) {
|
|
461
|
+
logger.info("sync.superseded", {
|
|
462
|
+
accountId,
|
|
463
|
+
instanceId: event.instanceId,
|
|
464
|
+
supersededBy: account.lastSyncMetadata?.instanceId,
|
|
465
|
+
iteration
|
|
466
|
+
});
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const lastSyncAtMs = account.lastSyncAt.getTime();
|
|
470
|
+
const windowMs = await step.do("resolve sync window", async () => {
|
|
471
|
+
const window = resolveSyncWindow(
|
|
472
|
+
new Date(lastSyncAtMs),
|
|
473
|
+
Date.now(),
|
|
474
|
+
MAX_CATCH_UP_WINDOW_MS
|
|
475
|
+
);
|
|
476
|
+
return {
|
|
477
|
+
dateFromMs: window.dateFrom.getTime(),
|
|
478
|
+
dateToMs: window.dateTo.getTime(),
|
|
479
|
+
isCatchUp: window.isCatchUp
|
|
480
|
+
};
|
|
481
|
+
});
|
|
482
|
+
const syncWindow = {
|
|
483
|
+
dateFrom: new Date(windowMs.dateFromMs),
|
|
484
|
+
dateTo: new Date(windowMs.dateToMs),
|
|
485
|
+
isCatchUp: windowMs.isCatchUp
|
|
486
|
+
};
|
|
487
|
+
const fetched = await step.do(
|
|
378
488
|
"fetch bank payments",
|
|
379
489
|
{
|
|
380
490
|
retries: { limit: 5, delay: "2 minutes", backoff: "exponential" },
|
|
@@ -403,42 +513,88 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
|
403
513
|
}
|
|
404
514
|
]
|
|
405
515
|
});
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
516
|
+
const fetchWindow = async (window) => {
|
|
517
|
+
const result = await connector.getAllAccountPayments({
|
|
518
|
+
account,
|
|
519
|
+
filter: {
|
|
520
|
+
dateFrom: window.dateFrom,
|
|
521
|
+
dateTo: window.dateTo
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
const identifiable = result.filter(
|
|
525
|
+
(p) => p.parsed.bankRefId?.trim()
|
|
526
|
+
);
|
|
527
|
+
const skipped = result.length - identifiable.length;
|
|
528
|
+
if (skipped > 0) {
|
|
529
|
+
logger.warn("payments.fetch.skipped_blank_bank_ref", {
|
|
530
|
+
accountId,
|
|
531
|
+
connectorKey: account.connectorKey,
|
|
532
|
+
skipped
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
return Promise.all(
|
|
536
|
+
identifiable.map(async (p) => ({
|
|
537
|
+
parsed: {
|
|
538
|
+
...p.parsed,
|
|
539
|
+
id: await deriveV4PaymentId(p.parsed)
|
|
540
|
+
}
|
|
541
|
+
}))
|
|
542
|
+
);
|
|
543
|
+
};
|
|
544
|
+
let bounded;
|
|
545
|
+
try {
|
|
546
|
+
bounded = await fetchBoundedByWindow({
|
|
547
|
+
window: syncWindow,
|
|
548
|
+
fetchWindow,
|
|
549
|
+
guardResult: guardStepResultSize,
|
|
550
|
+
minWindowMs: MIN_CATCH_UP_WINDOW_MS,
|
|
551
|
+
onNarrowed: (narrowed) => logger.warn("payments.fetch.window-narrowed", {
|
|
552
|
+
accountId,
|
|
553
|
+
connectorKey: account.connectorKey,
|
|
554
|
+
dateFrom: narrowed.dateFrom.toISOString(),
|
|
555
|
+
dateTo: narrowed.dateTo.toISOString()
|
|
556
|
+
})
|
|
557
|
+
});
|
|
558
|
+
} catch (err) {
|
|
559
|
+
if (err instanceof StepResultTooLargeError) {
|
|
560
|
+
logger.error("payments.fetch.result-too-large", {
|
|
561
|
+
accountId,
|
|
562
|
+
connectorKey: account.connectorKey,
|
|
563
|
+
dateFrom: syncWindow.dateFrom.toISOString(),
|
|
564
|
+
dateTo: syncWindow.dateTo.toISOString()
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
throw err;
|
|
568
|
+
}
|
|
410
569
|
logger.info("payments.fetch.completed", {
|
|
411
570
|
accountId,
|
|
412
571
|
connectorKey: account.connectorKey,
|
|
413
|
-
paymentsCount: result.length
|
|
572
|
+
paymentsCount: bounded.result.length
|
|
414
573
|
});
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
skipped
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
return Promise.all(
|
|
427
|
-
identifiable.map(async (p) => ({
|
|
428
|
-
parsed: { ...p.parsed, id: await deriveV4PaymentId(p.parsed) }
|
|
429
|
-
}))
|
|
430
|
-
);
|
|
574
|
+
return {
|
|
575
|
+
payments: bounded.result,
|
|
576
|
+
windowMs: {
|
|
577
|
+
dateFromMs: bounded.window.dateFrom.getTime(),
|
|
578
|
+
dateToMs: bounded.window.dateTo.getTime(),
|
|
579
|
+
isCatchUp: bounded.window.isCatchUp
|
|
580
|
+
}
|
|
581
|
+
};
|
|
431
582
|
} catch (err) {
|
|
432
|
-
const message = err instanceof Error ? err.message : typeof err === "object" && err !== null && "message" in err ? String(err.message) : JSON.stringify(err);
|
|
433
583
|
logger.error("payments.fetch.failed", {
|
|
434
584
|
accountId,
|
|
435
585
|
connectorKey: account.connectorKey,
|
|
436
|
-
error: message
|
|
586
|
+
error: err instanceof Error ? err.message : String(err)
|
|
437
587
|
});
|
|
438
|
-
|
|
588
|
+
rethrowFetchError(err);
|
|
439
589
|
}
|
|
440
590
|
}
|
|
441
591
|
);
|
|
592
|
+
const payments = fetched.payments;
|
|
593
|
+
const effectiveWindow = {
|
|
594
|
+
dateFrom: new Date(fetched.windowMs.dateFromMs),
|
|
595
|
+
dateTo: new Date(fetched.windowMs.dateToMs),
|
|
596
|
+
isCatchUp: fetched.windowMs.isCatchUp
|
|
597
|
+
};
|
|
442
598
|
const paymentsToProcess = payments.filter(
|
|
443
599
|
(p) => isPaymentCompleted(p.parsed)
|
|
444
600
|
);
|
|
@@ -513,6 +669,7 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
|
513
669
|
}))
|
|
514
670
|
);
|
|
515
671
|
const lastSyncMetadata = {
|
|
672
|
+
instanceId: event.instanceId,
|
|
516
673
|
payments: payments.length,
|
|
517
674
|
paymentsToProcess: paymentsToProcess.length,
|
|
518
675
|
paymentsInserted: paymentsToInsert.length,
|
|
@@ -529,15 +686,27 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
|
529
686
|
};
|
|
530
687
|
const updateLastSyncCommand = updateAccountLastSyncCommand(db, {
|
|
531
688
|
accountId: account.id,
|
|
532
|
-
|
|
533
|
-
|
|
689
|
+
// End of the window just fetched, not `now`: while catching up the
|
|
690
|
+
// window stops short of now, and advancing past it would skip every
|
|
691
|
+
// payment in the gap.
|
|
692
|
+
lastSyncAt: effectiveWindow.dateTo,
|
|
693
|
+
lastSyncMetadata,
|
|
694
|
+
seenInstanceId: account.lastSyncMetadata?.instanceId ?? null
|
|
534
695
|
}).command;
|
|
696
|
+
let syncStateRows;
|
|
535
697
|
if (createCommands.length) {
|
|
536
|
-
await db.batch(
|
|
698
|
+
const [updateResult] = await db.batch(
|
|
537
699
|
asNonEmpty([updateLastSyncCommand, ...createCommands])
|
|
538
700
|
);
|
|
701
|
+
syncStateRows = updateResult;
|
|
539
702
|
} else {
|
|
540
|
-
await updateLastSyncCommand;
|
|
703
|
+
syncStateRows = await updateLastSyncCommand;
|
|
704
|
+
}
|
|
705
|
+
if (syncStateRows.length === 0) {
|
|
706
|
+
logger.info("sync.write-superseded", {
|
|
707
|
+
accountId,
|
|
708
|
+
instanceId: event.instanceId
|
|
709
|
+
});
|
|
541
710
|
}
|
|
542
711
|
if (eventsToEmit.length) {
|
|
543
712
|
await pushToQueue(
|
|
@@ -547,14 +716,16 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
|
547
716
|
}
|
|
548
717
|
return {
|
|
549
718
|
...lastSyncMetadata,
|
|
550
|
-
newLastSyncAt:
|
|
719
|
+
newLastSyncAt: effectiveWindow.dateTo
|
|
551
720
|
};
|
|
552
721
|
}
|
|
553
722
|
);
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
723
|
+
if (!effectiveWindow.isCatchUp) {
|
|
724
|
+
await step.sleep(
|
|
725
|
+
"Sleep for next sync",
|
|
726
|
+
`${account.syncIntervalS} seconds`
|
|
727
|
+
);
|
|
728
|
+
}
|
|
558
729
|
}
|
|
559
730
|
}
|
|
560
731
|
}
|
package/dist/service.d.cts
CHANGED
|
@@ -17,6 +17,9 @@ interface BankServiceVariables {
|
|
|
17
17
|
DBUCS_TX_AUTH_URI: string;
|
|
18
18
|
REDIRECT_URI: string;
|
|
19
19
|
SYNC_WORKFLOW_RESET_AFTER_ITERATIONS: string;
|
|
20
|
+
CRON_SYNC_WORKFLOW_DISPATCH: string;
|
|
21
|
+
SYNC_DISPATCH_ACCOUNT_IDS: string;
|
|
22
|
+
SYNC_WORKFLOW_MAX_ITERATIONS: string;
|
|
20
23
|
[key: string]: string | number | boolean;
|
|
21
24
|
}
|
|
22
25
|
declare const BANK_SERVICE_BINDINGS: {
|
package/dist/service.d.mts
CHANGED
|
@@ -17,6 +17,9 @@ interface BankServiceVariables {
|
|
|
17
17
|
DBUCS_TX_AUTH_URI: string;
|
|
18
18
|
REDIRECT_URI: string;
|
|
19
19
|
SYNC_WORKFLOW_RESET_AFTER_ITERATIONS: string;
|
|
20
|
+
CRON_SYNC_WORKFLOW_DISPATCH: string;
|
|
21
|
+
SYNC_DISPATCH_ACCOUNT_IDS: string;
|
|
22
|
+
SYNC_WORKFLOW_MAX_ITERATIONS: string;
|
|
20
23
|
[key: string]: string | number | boolean;
|
|
21
24
|
}
|
|
22
25
|
declare const BANK_SERVICE_BINDINGS: {
|
package/dist/service.d.ts
CHANGED
|
@@ -17,6 +17,9 @@ interface BankServiceVariables {
|
|
|
17
17
|
DBUCS_TX_AUTH_URI: string;
|
|
18
18
|
REDIRECT_URI: string;
|
|
19
19
|
SYNC_WORKFLOW_RESET_AFTER_ITERATIONS: string;
|
|
20
|
+
CRON_SYNC_WORKFLOW_DISPATCH: string;
|
|
21
|
+
SYNC_DISPATCH_ACCOUNT_IDS: string;
|
|
22
|
+
SYNC_WORKFLOW_MAX_ITERATIONS: string;
|
|
20
23
|
[key: string]: string | number | boolean;
|
|
21
24
|
}
|
|
22
25
|
declare const BANK_SERVICE_BINDINGS: {
|
|
@@ -75,6 +75,10 @@ const account = sqliteTable(
|
|
|
75
75
|
status: text("status", { enum: ACCOUNT_STATUSES }).$type().notNull(),
|
|
76
76
|
bankRefId: text("bank_ref_id").notNull(),
|
|
77
77
|
batchSizeLimit: integer("batch_size_limit").notNull().default(50),
|
|
78
|
+
// Ops toggle ("should this account sync?"), separate from `status`:
|
|
79
|
+
// DISABLED is owned by disconnect (deletes credentials) and status also
|
|
80
|
+
// gates payments, so neither can express "stop syncing only".
|
|
81
|
+
syncEnabled: integer("sync_enabled", { mode: "boolean" }).notNull().default(true),
|
|
78
82
|
syncIntervalS: integer("sync_interval_s").notNull().default(600),
|
|
79
83
|
lastSyncAt: integer("last_sync_at", { mode: "timestamp_ms" }),
|
|
80
84
|
lastSyncMetadata: text("last_sync_metadata", {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { e as CurrencyCode, S as BankCode, a2 as CountryCode, P as PaymentRequestSelectType } from './bank.
|
|
1
|
+
import { e as CurrencyCode, S as BankCode, a2 as CountryCode, P as PaymentRequestSelectType } from './bank.mkq4IlpC.mjs';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
4
|
type ReferenceType = `${'VS' | 'SS' | 'KS'}:${number}`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { e as CurrencyCode, S as BankCode, a2 as CountryCode, P as PaymentRequestSelectType } from './bank.
|
|
1
|
+
import { e as CurrencyCode, S as BankCode, a2 as CountryCode, P as PaymentRequestSelectType } from './bank.mkq4IlpC.cjs';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
4
|
type ReferenceType = `${'VS' | 'SS' | 'KS'}:${number}`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { e as CurrencyCode, S as BankCode, a2 as CountryCode, P as PaymentRequestSelectType } from './bank.
|
|
1
|
+
import { e as CurrencyCode, S as BankCode, a2 as CountryCode, P as PaymentRequestSelectType } from './bank.mkq4IlpC.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
4
|
type ReferenceType = `${'VS' | 'SS' | 'KS'}:${number}`;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const backendSdk = require('@develit-io/backend-sdk');
|
|
4
4
|
const dateFns = require('date-fns');
|
|
5
|
-
const database_schema = require('./bank.
|
|
5
|
+
const database_schema = require('./bank.Ca3jzmIb.cjs');
|
|
6
6
|
const drizzleOrm = require('drizzle-orm');
|
|
7
7
|
const jose = require('jose');
|
|
8
8
|
const zod = require('zod');
|
|
@@ -77,6 +77,10 @@ const account = sqliteCore.sqliteTable(
|
|
|
77
77
|
status: sqliteCore.text("status", { enum: ACCOUNT_STATUSES }).$type().notNull(),
|
|
78
78
|
bankRefId: sqliteCore.text("bank_ref_id").notNull(),
|
|
79
79
|
batchSizeLimit: sqliteCore.integer("batch_size_limit").notNull().default(50),
|
|
80
|
+
// Ops toggle ("should this account sync?"), separate from `status`:
|
|
81
|
+
// DISABLED is owned by disconnect (deletes credentials) and status also
|
|
82
|
+
// gates payments, so neither can express "stop syncing only".
|
|
83
|
+
syncEnabled: sqliteCore.integer("sync_enabled", { mode: "boolean" }).notNull().default(true),
|
|
80
84
|
syncIntervalS: sqliteCore.integer("sync_interval_s").notNull().default(600),
|
|
81
85
|
lastSyncAt: sqliteCore.integer("last_sync_at", { mode: "timestamp_ms" }),
|
|
82
86
|
lastSyncMetadata: sqliteCore.text("last_sync_metadata", {
|
|
@@ -1,12 +1,39 @@
|
|
|
1
1
|
import { sql, and, eq, isNull } from 'drizzle-orm';
|
|
2
|
-
import { G as tables, b as FinbricksConnector, J as buildEndToEndId, c as MockConnector, D as DbuConnector, C as CsobConnector, K as KBConnector, M as MockCobsConnector, E as ErsteConnector } from './bank.
|
|
2
|
+
import { G as tables, b as FinbricksConnector, J as buildEndToEndId, c as MockConnector, D as DbuConnector, C as CsobConnector, K as KBConnector, M as MockCobsConnector, E as ErsteConnector } from './bank.kz-PKVi5.mjs';
|
|
3
3
|
import { uuidv4 } from '@develit-io/backend-sdk';
|
|
4
|
-
import './bank.
|
|
4
|
+
import './bank.BT7HayCV.mjs';
|
|
5
5
|
import 'date-fns';
|
|
6
6
|
import 'jose';
|
|
7
7
|
import '@develit-io/general-codes';
|
|
8
8
|
import { createHash } from 'node:crypto';
|
|
9
9
|
|
|
10
|
+
function parseInstanceWindow(instanceId, accountId) {
|
|
11
|
+
const prefix = `${accountId}-`;
|
|
12
|
+
if (!instanceId.startsWith(prefix)) return -1;
|
|
13
|
+
const suffix = instanceId.slice(prefix.length);
|
|
14
|
+
if (!/^\d+$/.test(suffix)) return -1;
|
|
15
|
+
return Number(suffix);
|
|
16
|
+
}
|
|
17
|
+
function resolveCurrentSyncInstanceId(accountId, recordedInstanceId) {
|
|
18
|
+
return recordedInstanceId ?? accountId;
|
|
19
|
+
}
|
|
20
|
+
function isSupersededBy(myInstanceId, recordedInstanceId, accountId) {
|
|
21
|
+
if (recordedInstanceId == null) return false;
|
|
22
|
+
return parseInstanceWindow(recordedInstanceId, accountId) > parseInstanceWindow(myInstanceId, accountId);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeBudget(value) {
|
|
26
|
+
if (!Number.isFinite(value) || value < 1) return void 0;
|
|
27
|
+
return Math.floor(value);
|
|
28
|
+
}
|
|
29
|
+
function parseIterationBudgetParam(value) {
|
|
30
|
+
return normalizeBudget(Number(value?.trim() || Number.NaN));
|
|
31
|
+
}
|
|
32
|
+
function resolveIterationBudget(maxIterations) {
|
|
33
|
+
if (maxIterations == null) return Number.POSITIVE_INFINITY;
|
|
34
|
+
return normalizeBudget(maxIterations) ?? Number.POSITIVE_INFINITY;
|
|
35
|
+
}
|
|
36
|
+
|
|
10
37
|
const createPaymentCommand = (db, { payment }) => {
|
|
11
38
|
return {
|
|
12
39
|
command: db.insert(tables.payment).values({
|
|
@@ -359,4 +386,4 @@ const initiateConnector = async ({
|
|
|
359
386
|
}
|
|
360
387
|
};
|
|
361
388
|
|
|
362
|
-
export { getPaymentRequestsByBatchIdQuery as a, getAccountByIdQuery as b, checksum as c, createCredentialsResolver as d, updatePaymentRequestStatusCommand as e,
|
|
389
|
+
export { getPaymentRequestsByBatchIdQuery as a, getAccountByIdQuery as b, checksum as c, createCredentialsResolver as d, updatePaymentRequestStatusCommand as e, isSupersededBy as f, getBatchByIdQuery as g, createPaymentCommand as h, initiateConnector as i, encrypt as j, resolveCurrentSyncInstanceId as k, importAesKey as l, parseIterationBudgetParam as p, resolveIterationBudget as r, upsertBatchCommand as u };
|
|
@@ -1,14 +1,41 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const drizzleOrm = require('drizzle-orm');
|
|
4
|
-
const paymentDirection = require('./bank.
|
|
4
|
+
const paymentDirection = require('./bank.BsWBG0gb.cjs');
|
|
5
5
|
const backendSdk = require('@develit-io/backend-sdk');
|
|
6
|
-
require('./bank.
|
|
6
|
+
require('./bank.Ca3jzmIb.cjs');
|
|
7
7
|
require('date-fns');
|
|
8
8
|
require('jose');
|
|
9
9
|
require('@develit-io/general-codes');
|
|
10
10
|
const node_crypto = require('node:crypto');
|
|
11
11
|
|
|
12
|
+
function parseInstanceWindow(instanceId, accountId) {
|
|
13
|
+
const prefix = `${accountId}-`;
|
|
14
|
+
if (!instanceId.startsWith(prefix)) return -1;
|
|
15
|
+
const suffix = instanceId.slice(prefix.length);
|
|
16
|
+
if (!/^\d+$/.test(suffix)) return -1;
|
|
17
|
+
return Number(suffix);
|
|
18
|
+
}
|
|
19
|
+
function resolveCurrentSyncInstanceId(accountId, recordedInstanceId) {
|
|
20
|
+
return recordedInstanceId ?? accountId;
|
|
21
|
+
}
|
|
22
|
+
function isSupersededBy(myInstanceId, recordedInstanceId, accountId) {
|
|
23
|
+
if (recordedInstanceId == null) return false;
|
|
24
|
+
return parseInstanceWindow(recordedInstanceId, accountId) > parseInstanceWindow(myInstanceId, accountId);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeBudget(value) {
|
|
28
|
+
if (!Number.isFinite(value) || value < 1) return void 0;
|
|
29
|
+
return Math.floor(value);
|
|
30
|
+
}
|
|
31
|
+
function parseIterationBudgetParam(value) {
|
|
32
|
+
return normalizeBudget(Number(value?.trim() || Number.NaN));
|
|
33
|
+
}
|
|
34
|
+
function resolveIterationBudget(maxIterations) {
|
|
35
|
+
if (maxIterations == null) return Number.POSITIVE_INFINITY;
|
|
36
|
+
return normalizeBudget(maxIterations) ?? Number.POSITIVE_INFINITY;
|
|
37
|
+
}
|
|
38
|
+
|
|
12
39
|
const createPaymentCommand = (db, { payment }) => {
|
|
13
40
|
return {
|
|
14
41
|
command: db.insert(paymentDirection.tables.payment).values({
|
|
@@ -370,5 +397,9 @@ exports.getBatchByIdQuery = getBatchByIdQuery;
|
|
|
370
397
|
exports.getPaymentRequestsByBatchIdQuery = getPaymentRequestsByBatchIdQuery;
|
|
371
398
|
exports.importAesKey = importAesKey;
|
|
372
399
|
exports.initiateConnector = initiateConnector;
|
|
400
|
+
exports.isSupersededBy = isSupersededBy;
|
|
401
|
+
exports.parseIterationBudgetParam = parseIterationBudgetParam;
|
|
402
|
+
exports.resolveCurrentSyncInstanceId = resolveCurrentSyncInstanceId;
|
|
403
|
+
exports.resolveIterationBudget = resolveIterationBudget;
|
|
373
404
|
exports.updatePaymentRequestStatusCommand = updatePaymentRequestStatusCommand;
|
|
374
405
|
exports.upsertBatchCommand = upsertBatchCommand;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { uuidv4, createInternalError, bicSchema, useResult, structuredAddressSchema } from '@develit-io/backend-sdk';
|
|
2
2
|
import { format, parseISO } from 'date-fns';
|
|
3
|
-
import { s as schema, h as account, i as accountCredentials, o as ott } from './bank.
|
|
3
|
+
import { s as schema, h as account, i as accountCredentials, o as ott } from './bank.BT7HayCV.mjs';
|
|
4
4
|
import { defineRelations, and, not, inArray, isNull } from 'drizzle-orm';
|
|
5
5
|
import { importPKCS8, SignJWT } from 'jose';
|
|
6
6
|
import { z } from 'zod';
|
|
@@ -27,6 +27,8 @@ interface LastSyncMetadata {
|
|
|
27
27
|
eventsEmitted: number;
|
|
28
28
|
iterationCount?: number;
|
|
29
29
|
workflowStartedAt?: number;
|
|
30
|
+
/** Which instance wrote this sync — lets an older sibling detect it was superseded. */
|
|
31
|
+
instanceId?: string;
|
|
30
32
|
}
|
|
31
33
|
type ConnectorConfig = Record<string, string | number | boolean | null>;
|
|
32
34
|
|
|
@@ -1040,6 +1042,22 @@ declare const accountInsertSchema: drizzle_orm_zod.BuildSchema<"insert", {
|
|
|
1040
1042
|
identity: undefined;
|
|
1041
1043
|
generated: undefined;
|
|
1042
1044
|
}, {}>;
|
|
1045
|
+
syncEnabled: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
1046
|
+
name: string;
|
|
1047
|
+
tableName: "account";
|
|
1048
|
+
dataType: "boolean";
|
|
1049
|
+
data: boolean;
|
|
1050
|
+
driverParam: number;
|
|
1051
|
+
notNull: true;
|
|
1052
|
+
hasDefault: true;
|
|
1053
|
+
isPrimaryKey: false;
|
|
1054
|
+
isAutoincrement: false;
|
|
1055
|
+
hasRuntimeDefault: false;
|
|
1056
|
+
enumValues: undefined;
|
|
1057
|
+
baseColumn: never;
|
|
1058
|
+
identity: undefined;
|
|
1059
|
+
generated: undefined;
|
|
1060
|
+
}, {}>;
|
|
1043
1061
|
syncIntervalS: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
1044
1062
|
name: string;
|
|
1045
1063
|
tableName: "account";
|
|
@@ -1745,6 +1763,22 @@ declare const accountUpdateSchema: drizzle_orm_zod.BuildSchema<"update", {
|
|
|
1745
1763
|
identity: undefined;
|
|
1746
1764
|
generated: undefined;
|
|
1747
1765
|
}, {}>;
|
|
1766
|
+
syncEnabled: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
1767
|
+
name: string;
|
|
1768
|
+
tableName: "account";
|
|
1769
|
+
dataType: "boolean";
|
|
1770
|
+
data: boolean;
|
|
1771
|
+
driverParam: number;
|
|
1772
|
+
notNull: true;
|
|
1773
|
+
hasDefault: true;
|
|
1774
|
+
isPrimaryKey: false;
|
|
1775
|
+
isAutoincrement: false;
|
|
1776
|
+
hasRuntimeDefault: false;
|
|
1777
|
+
enumValues: undefined;
|
|
1778
|
+
baseColumn: never;
|
|
1779
|
+
identity: undefined;
|
|
1780
|
+
generated: undefined;
|
|
1781
|
+
}, {}>;
|
|
1748
1782
|
syncIntervalS: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
1749
1783
|
name: string;
|
|
1750
1784
|
tableName: "account";
|
|
@@ -2450,6 +2484,22 @@ declare const accountSelectSchema: drizzle_orm_zod.BuildSchema<"select", {
|
|
|
2450
2484
|
identity: undefined;
|
|
2451
2485
|
generated: undefined;
|
|
2452
2486
|
}, {}>;
|
|
2487
|
+
syncEnabled: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
2488
|
+
name: string;
|
|
2489
|
+
tableName: "account";
|
|
2490
|
+
dataType: "boolean";
|
|
2491
|
+
data: boolean;
|
|
2492
|
+
driverParam: number;
|
|
2493
|
+
notNull: true;
|
|
2494
|
+
hasDefault: true;
|
|
2495
|
+
isPrimaryKey: false;
|
|
2496
|
+
isAutoincrement: false;
|
|
2497
|
+
hasRuntimeDefault: false;
|
|
2498
|
+
enumValues: undefined;
|
|
2499
|
+
baseColumn: never;
|
|
2500
|
+
identity: undefined;
|
|
2501
|
+
generated: undefined;
|
|
2502
|
+
}, {}>;
|
|
2453
2503
|
syncIntervalS: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
2454
2504
|
name: string;
|
|
2455
2505
|
tableName: "account";
|
|
@@ -3606,7 +3656,7 @@ declare const payment: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
|
|
|
3606
3656
|
name: string;
|
|
3607
3657
|
tableName: "payment";
|
|
3608
3658
|
dataType: "string enum";
|
|
3609
|
-
data: "PROCESSING" | "
|
|
3659
|
+
data: "PROCESSING" | "BOOKED" | "REJECTED" | "PENDING" | "CANCELLED" | "SCHEDULED" | "HOLD" | "INFO";
|
|
3610
3660
|
driverParam: string;
|
|
3611
3661
|
notNull: true;
|
|
3612
3662
|
hasDefault: false;
|
|
@@ -4110,7 +4160,7 @@ declare const paymentRequest: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
|
|
|
4110
4160
|
name: string;
|
|
4111
4161
|
tableName: "payment_request";
|
|
4112
4162
|
dataType: "string enum";
|
|
4113
|
-
data: "AUTHORIZED" | "COMPLETED" | "
|
|
4163
|
+
data: "AUTHORIZED" | "COMPLETED" | "OPENED" | "BOOKED" | "SETTLED" | "REJECTED" | "CLOSED";
|
|
4114
4164
|
driverParam: string;
|
|
4115
4165
|
notNull: true;
|
|
4116
4166
|
hasDefault: false;
|
|
@@ -4810,6 +4860,22 @@ declare const account: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
|
|
|
4810
4860
|
identity: undefined;
|
|
4811
4861
|
generated: undefined;
|
|
4812
4862
|
}, {}>;
|
|
4863
|
+
syncEnabled: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
4864
|
+
name: string;
|
|
4865
|
+
tableName: "account";
|
|
4866
|
+
dataType: "boolean";
|
|
4867
|
+
data: boolean;
|
|
4868
|
+
driverParam: number;
|
|
4869
|
+
notNull: true;
|
|
4870
|
+
hasDefault: true;
|
|
4871
|
+
isPrimaryKey: false;
|
|
4872
|
+
isAutoincrement: false;
|
|
4873
|
+
hasRuntimeDefault: false;
|
|
4874
|
+
enumValues: undefined;
|
|
4875
|
+
baseColumn: never;
|
|
4876
|
+
identity: undefined;
|
|
4877
|
+
generated: undefined;
|
|
4878
|
+
}, {}>;
|
|
4813
4879
|
syncIntervalS: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
4814
4880
|
name: string;
|
|
4815
4881
|
tableName: "account";
|