@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.
Files changed (36) hide show
  1. package/dist/base.cjs +296 -32
  2. package/dist/base.d.cts +38 -11
  3. package/dist/base.d.mts +38 -11
  4. package/dist/base.d.ts +38 -11
  5. package/dist/base.mjs +296 -32
  6. package/dist/database/schema.cjs +1 -1
  7. package/dist/database/schema.d.cts +1 -1
  8. package/dist/database/schema.d.mts +1 -1
  9. package/dist/database/schema.d.ts +1 -1
  10. package/dist/database/schema.mjs +1 -1
  11. package/dist/export/workflows.cjs +213 -42
  12. package/dist/export/workflows.d.cts +1 -0
  13. package/dist/export/workflows.d.mts +1 -0
  14. package/dist/export/workflows.d.ts +1 -0
  15. package/dist/export/workflows.mjs +214 -43
  16. package/dist/service.d.cts +3 -0
  17. package/dist/service.d.mts +3 -0
  18. package/dist/service.d.ts +3 -0
  19. package/dist/shared/{bank.BzDNLxB_.mjs → bank.BT7HayCV.mjs} +4 -0
  20. package/dist/shared/{bank.BUzoc8p6.d.cts → bank.Bhh_O_5-.d.mts} +1 -1
  21. package/dist/shared/{bank.DZ3Ow4bP.d.mts → bank.BhyIbhvK.d.cts} +1 -1
  22. package/dist/shared/{bank.CBpXmaTL.d.ts → bank.Bnc_DoG4.d.ts} +1 -1
  23. package/dist/shared/{bank.CQjuudum.cjs → bank.BsWBG0gb.cjs} +1 -1
  24. package/dist/shared/{bank.9Yw4KHyl.cjs → bank.Ca3jzmIb.cjs} +4 -0
  25. package/dist/shared/{bank.DLU1sOBm.mjs → bank.DAjqsBQN.mjs} +30 -3
  26. package/dist/shared/{bank.B0DNtuUM.cjs → bank.DBTtRCcf.cjs} +33 -2
  27. package/dist/shared/{bank.BVXtqHnq.mjs → bank.kz-PKVi5.mjs} +1 -1
  28. package/dist/shared/{bank.BQwwtIR4.d.ts → bank.mkq4IlpC.d.cts} +68 -2
  29. package/dist/shared/{bank.BQwwtIR4.d.cts → bank.mkq4IlpC.d.mts} +68 -2
  30. package/dist/shared/{bank.BQwwtIR4.d.mts → bank.mkq4IlpC.d.ts} +68 -2
  31. package/dist/types.cjs +2 -2
  32. package/dist/types.d.cts +4 -4
  33. package/dist/types.d.mts +4 -4
  34. package/dist/types.d.ts +4 -4
  35. package/dist/types.mjs +2 -2
  36. 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.BVXtqHnq.mjs';
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 { eq, and, 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 createPaymentCommand } from '../shared/bank.DLU1sOBm.mjs';
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.BzDNLxB_.mjs';
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
- while (true) {
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
- const payments = await step.do(
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 result = await connector.getAllAccountPayments({
407
- account,
408
- filter: { dateFrom: account.lastSyncAt }
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
- const identifiable = result.filter(
416
- (p) => p.parsed.bankRefId?.trim()
417
- );
418
- const skipped = result.length - identifiable.length;
419
- if (skipped > 0) {
420
- logger.warn("payments.fetch.skipped_blank_bank_ref", {
421
- accountId,
422
- connectorKey: account.connectorKey,
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
- throw new Error(message);
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
- lastSyncAt: now,
533
- lastSyncMetadata
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: now
719
+ newLastSyncAt: effectiveWindow.dateTo
551
720
  };
552
721
  }
553
722
  );
554
- await step.sleep(
555
- "Sleep for next sync",
556
- `${account.syncIntervalS} seconds`
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
  }
@@ -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: {
@@ -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.BQwwtIR4.cjs';
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.BQwwtIR4.mjs';
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.BQwwtIR4.js';
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.9Yw4KHyl.cjs');
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.BVXtqHnq.mjs';
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.BzDNLxB_.mjs';
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, createPaymentCommand as f, getBatchByIdQuery as g, encrypt as h, initiateConnector as i, importAesKey as j, upsertBatchCommand as u };
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.CQjuudum.cjs');
4
+ const paymentDirection = require('./bank.BsWBG0gb.cjs');
5
5
  const backendSdk = require('@develit-io/backend-sdk');
6
- require('./bank.9Yw4KHyl.cjs');
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.BzDNLxB_.mjs';
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" | "PENDING" | "BOOKED" | "CANCELLED" | "REJECTED" | "SCHEDULED" | "HOLD" | "INFO";
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" | "BOOKED" | "REJECTED" | "OPENED" | "SETTLED" | "CLOSED";
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";