@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
package/dist/base.cjs CHANGED
@@ -1,35 +1,51 @@
1
1
  'use strict';
2
2
 
3
3
  const backendSdk = require('@develit-io/backend-sdk');
4
- const paymentDirection = require('./shared/bank.CQjuudum.cjs');
4
+ const paymentDirection = require('./shared/bank.BsWBG0gb.cjs');
5
+ const bank = require('./shared/bank.DBTtRCcf.cjs');
5
6
  const drizzleOrm = require('drizzle-orm');
6
7
  const cloudflare_workers = require('cloudflare:workers');
7
8
  const d1 = require('drizzle-orm/d1');
8
9
  require('jose');
9
- const bank = require('./shared/bank.B0DNtuUM.cjs');
10
10
  const zod = require('zod');
11
- const database_schema = require('./shared/bank.9Yw4KHyl.cjs');
11
+ const database_schema = require('./shared/bank.Ca3jzmIb.cjs');
12
12
  const generalCodes = require('@develit-io/general-codes');
13
13
  require('date-fns');
14
14
  require('node:crypto');
15
15
  require('drizzle-orm/zod');
16
16
  require('drizzle-orm/sqlite-core');
17
17
 
18
+ const STALE_SYNC_INTERVAL_MULTIPLIER = 3;
19
+ function assessSyncStaleness({ syncIntervalS, lastSyncAt, createdAt }, nowMs, minGraceMs) {
20
+ const baseline = lastSyncAt ?? createdAt;
21
+ if (baseline == null) return null;
22
+ return {
23
+ staleForMs: nowMs - baseline.getTime(),
24
+ thresholdMs: Math.max(
25
+ syncIntervalS * STALE_SYNC_INTERVAL_MULTIPLIER * 1e3,
26
+ minGraceMs
27
+ )
28
+ };
29
+ }
30
+
18
31
  const DEAD_STATUSES = /* @__PURE__ */ new Set([
19
32
  "complete",
20
33
  "terminated",
21
34
  "errored",
22
35
  "unknown"
23
36
  ]);
37
+ const STALE_MIN_GRACE_MS = 15 * 60 * 1e3;
24
38
  async function heartbeatSyncWorkflows({
25
39
  entities,
26
40
  resetAfterIterations,
27
41
  getInstance,
28
42
  createInstance,
29
- logger
43
+ logger,
44
+ now = Date.now
30
45
  }) {
31
46
  await Promise.all(
32
- entities.map(async ({ id, iterationCount }) => {
47
+ entities.map(async (entity) => {
48
+ const { id, iterationCount, lastSyncAt } = entity;
33
49
  try {
34
50
  const instance = await getInstance(id);
35
51
  const { status } = await instance.status();
@@ -41,6 +57,24 @@ async function heartbeatSyncWorkflows({
41
57
  });
42
58
  return;
43
59
  }
60
+ const staleness = assessSyncStaleness(entity, now(), STALE_MIN_GRACE_MS);
61
+ if (staleness == null) {
62
+ logger.warn("sync-workflow.heartbeat.sync-age-unknown", {
63
+ id,
64
+ status
65
+ });
66
+ } else if (staleness.staleForMs > staleness.thresholdMs) {
67
+ await instance.restart();
68
+ logger.info("sync-workflow.heartbeat.stale-recovery", {
69
+ id,
70
+ status,
71
+ iterationCount,
72
+ lastSyncAt: lastSyncAt?.toISOString() ?? null,
73
+ staleForS: Math.round(staleness.staleForMs / 1e3),
74
+ thresholdS: Math.round(staleness.thresholdMs / 1e3)
75
+ });
76
+ return;
77
+ }
44
78
  if (iterationCount == null) {
45
79
  logger.warn("sync-workflow.heartbeat.iteration-count-missing", {
46
80
  id,
@@ -73,6 +107,88 @@ async function heartbeatSyncWorkflows({
73
107
  );
74
108
  }
75
109
 
110
+ const DISPATCH_RECOVERY_WINDOW_MS = 5 * 60 * 1e3;
111
+ function buildDispatchInstanceId(accountId, nowMs, recoveryWindowMs = DISPATCH_RECOVERY_WINDOW_MS) {
112
+ return `${accountId}-${Math.floor(nowMs / recoveryWindowMs)}`;
113
+ }
114
+ function buildDispatchCreateOptions(instanceId, accountId, maxIterations) {
115
+ return {
116
+ id: instanceId,
117
+ // Omitted (not undefined) when unresolved: the workflow treats a missing
118
+ // budget as unbounded — degraded but syncing.
119
+ params: maxIterations == null ? { accountId } : { accountId, maxIterations },
120
+ // Dispatch mints ~24 instances per account per day and step outputs carry
121
+ // bank payloads — the default 30-day retention would pile up billable
122
+ // storage for state nobody reads after success.
123
+ retention: {
124
+ successRetention: "1 day",
125
+ errorRetention: "7 days"
126
+ }
127
+ };
128
+ }
129
+ function isAlreadyExists(err) {
130
+ const message = err instanceof Error ? err.message : typeof err === "object" && err !== null && "message" in err ? String(err.message) : "";
131
+ return /already exists/i.test(message);
132
+ }
133
+
134
+ const DEFAULT_MIN_GRACE_MS = 3 * 60 * 1e3;
135
+ async function dispatchSyncWorkflows({
136
+ entities,
137
+ createInstance,
138
+ logger,
139
+ now = Date.now,
140
+ minGraceMs = DEFAULT_MIN_GRACE_MS,
141
+ recoveryWindowMs = DISPATCH_RECOVERY_WINDOW_MS
142
+ }) {
143
+ const at = now();
144
+ await Promise.all(
145
+ entities.map(async (entity) => {
146
+ const { id, lastSyncAt } = entity;
147
+ const staleness = assessSyncStaleness(entity, at, minGraceMs);
148
+ if (staleness == null) {
149
+ logger.warn("sync-workflow.dispatch.sync-age-unknown", { id });
150
+ return;
151
+ }
152
+ if (staleness.staleForMs <= staleness.thresholdMs) return;
153
+ const instanceId = buildDispatchInstanceId(id, at, recoveryWindowMs);
154
+ try {
155
+ await createInstance(instanceId, id);
156
+ logger.info("sync-workflow.dispatch.created", {
157
+ id,
158
+ instanceId,
159
+ lastSyncAt: lastSyncAt?.toISOString() ?? null,
160
+ staleForS: Math.round(staleness.staleForMs / 1e3),
161
+ thresholdS: Math.round(staleness.thresholdMs / 1e3)
162
+ });
163
+ } catch (err) {
164
+ if (isAlreadyExists(err)) {
165
+ logger.info("sync-workflow.dispatch.already-running", {
166
+ id,
167
+ instanceId
168
+ });
169
+ return;
170
+ }
171
+ logger.error("sync-workflow.dispatch.failed", {
172
+ id,
173
+ instanceId,
174
+ error: err instanceof Error ? err.message : String(err)
175
+ });
176
+ }
177
+ })
178
+ );
179
+ }
180
+
181
+ function isDispatchEnabled(accountId, selection) {
182
+ const raw = selection?.trim();
183
+ if (!raw) return false;
184
+ if (raw.toLowerCase() === "all") return true;
185
+ return raw.split(",").map((id) => id.trim()).includes(accountId);
186
+ }
187
+ function isDispatchActive(accountId, selection, dispatchCron) {
188
+ if (!dispatchCron?.trim()) return false;
189
+ return isDispatchEnabled(accountId, selection);
190
+ }
191
+
76
192
  const FINITE_STATUSES = /* @__PURE__ */ new Set([
77
193
  "complete",
78
194
  "errored",
@@ -92,6 +208,26 @@ function isAlreadyFiniteError(err) {
92
208
  return /cannot_terminate|finite state/i.test(message);
93
209
  }
94
210
 
211
+ async function disableAccountSync({
212
+ accountId,
213
+ persistDisabled,
214
+ terminateInstance,
215
+ logger
216
+ }) {
217
+ await persistDisabled();
218
+ try {
219
+ const instanceId = await terminateInstance();
220
+ logger.info("sync-workflow.disable.terminated", { accountId, instanceId });
221
+ return { terminated: true, instanceId };
222
+ } catch (err) {
223
+ logger.warn("sync-workflow.disable.terminate-skipped", {
224
+ accountId,
225
+ error: err instanceof Error ? err.message : String(err)
226
+ });
227
+ return { terminated: false, instanceId: null };
228
+ }
229
+ }
230
+
95
231
  const upsertAccountCommand = (db, { account }) => {
96
232
  const id = account.id || backendSdk.uuidv4();
97
233
  const { id: _id, ...accountWithoutId } = account;
@@ -179,6 +315,13 @@ const createPaymentRequestCommand = (db, { paymentRequest }) => {
179
315
  };
180
316
  };
181
317
 
318
+ const updateAccountSyncEnabledCommand = (db, { accountId, syncEnabled }) => {
319
+ const command = db.update(paymentDirection.tables.account).set({ syncEnabled }).where(drizzleOrm.eq(paymentDirection.tables.account.id, accountId)).returning();
320
+ return {
321
+ command
322
+ };
323
+ };
324
+
182
325
  const getAccountBatchCountsQuery = async (db, { accountId }) => {
183
326
  const result = await db.select({
184
327
  totalCount: drizzleOrm.sql`COUNT(*)`.as("totalCount"),
@@ -448,6 +591,21 @@ const getPaymentRequestsQuery = async (db, params) => {
448
591
  return { paymentRequests, totalCount };
449
592
  };
450
593
 
594
+ const getSyncCandidateAccountsQuery = async (db) => {
595
+ return await db.select({
596
+ id: paymentDirection.tables.account.id,
597
+ syncIntervalS: paymentDirection.tables.account.syncIntervalS,
598
+ lastSyncAt: paymentDirection.tables.account.lastSyncAt,
599
+ lastSyncMetadata: paymentDirection.tables.account.lastSyncMetadata,
600
+ createdAt: paymentDirection.tables.account.createdAt
601
+ }).from(paymentDirection.tables.account).where(
602
+ drizzleOrm.and(
603
+ drizzleOrm.eq(paymentDirection.tables.account.status, "AUTHORIZED"),
604
+ drizzleOrm.eq(paymentDirection.tables.account.syncEnabled, true)
605
+ )
606
+ ).all();
607
+ };
608
+
451
609
  const SIGNAL_MAP = {
452
610
  OPENED: "paymentRequestOpened",
453
611
  AUTHORIZED: "paymentRequestAuthorized",
@@ -674,7 +832,9 @@ const syncAccountTerminateInputSchema = zod.z.object({
674
832
  accountId: zod.z.uuid()
675
833
  });
676
834
  zod.z.object({
677
- instanceId: zod.z.string()
835
+ terminated: zod.z.boolean(),
836
+ // Null when the flag was persisted but no live instance existed to kill.
837
+ instanceId: zod.z.string().nullable()
678
838
  });
679
839
 
680
840
  const updateAccountInputSchema = zod.z.object({
@@ -750,6 +910,16 @@ var __decorateClass = (decorators, target, key, kind) => {
750
910
  if (kind && result) __defProp(target, key, result);
751
911
  return result;
752
912
  };
913
+ const accountNotFoundError = () => backendSdk.createInternalError(null, {
914
+ message: "Account not found",
915
+ code: "DB-B-005",
916
+ status: 404
917
+ });
918
+ const syncEventConsoleLogger = {
919
+ info: (event, data) => console.log(JSON.stringify({ level: "info", event, ...data })),
920
+ warn: (event, data) => console.warn(JSON.stringify({ level: "warn", event, ...data })),
921
+ error: (event, data) => console.error(JSON.stringify({ level: "error", event, ...data }))
922
+ };
753
923
  exports.BankServiceBase = class BankServiceBase extends backendSdk.develitWorker(cloudflare_workers.WorkerEntrypoint) {
754
924
  constructor(ctx, env, config) {
755
925
  super(ctx, env);
@@ -852,6 +1022,40 @@ exports.BankServiceBase = class BankServiceBase extends backendSdk.develitWorker
852
1022
  { data: input, schema: syncAccountInputSchema },
853
1023
  { successMessage: "Account sync workflow started" },
854
1024
  async ({ accountId }) => {
1025
+ const account = await bank.getAccountByIdQuery(this.db, { accountId });
1026
+ if (!account) {
1027
+ throw accountNotFoundError();
1028
+ }
1029
+ if (!account.syncEnabled) {
1030
+ throw backendSdk.createInternalError(null, {
1031
+ message: `Account sync is disabled for ${accountId}. Enable it via restart-sync first.`,
1032
+ code: "VALID-B-018",
1033
+ status: 422
1034
+ });
1035
+ }
1036
+ if (isDispatchActive(
1037
+ accountId,
1038
+ this.env.SYNC_DISPATCH_ACCOUNT_IDS,
1039
+ this.env.CRON_SYNC_WORKFLOW_DISPATCH
1040
+ )) {
1041
+ const instanceId = buildDispatchInstanceId(accountId, Date.now());
1042
+ const maxIterations = bank.parseIterationBudgetParam(
1043
+ this.env.SYNC_WORKFLOW_MAX_ITERATIONS
1044
+ );
1045
+ let instance2;
1046
+ try {
1047
+ instance2 = await this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.create(
1048
+ buildDispatchCreateOptions(instanceId, accountId, maxIterations)
1049
+ );
1050
+ } catch (err) {
1051
+ if (!isAlreadyExists(err)) throw err;
1052
+ instance2 = await this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.get(instanceId);
1053
+ }
1054
+ return {
1055
+ instanceId: instance2.id,
1056
+ details: await instance2.status()
1057
+ };
1058
+ }
855
1059
  const instance = await this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.create({
856
1060
  id: accountId,
857
1061
  params: {
@@ -870,7 +1074,7 @@ exports.BankServiceBase = class BankServiceBase extends backendSdk.develitWorker
870
1074
  { data: input, schema: syncAccountStatusInputSchema },
871
1075
  { successMessage: "Account sync workflow status retrieved" },
872
1076
  async ({ accountId }) => {
873
- const instance = await this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.get(accountId);
1077
+ const instance = await this.getCurrentSyncInstance(accountId);
874
1078
  return {
875
1079
  instanceId: instance.id,
876
1080
  details: await instance.status()
@@ -883,7 +1087,8 @@ exports.BankServiceBase = class BankServiceBase extends backendSdk.develitWorker
883
1087
  { data: input, schema: syncAccountRestartInputSchema },
884
1088
  { successMessage: "Account sync workflow restarted" },
885
1089
  async ({ accountId }) => {
886
- const instance = await this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.get(accountId);
1090
+ await this.setSyncEnabledOrThrow(accountId, true);
1091
+ const instance = await this.getCurrentSyncInstance(accountId);
887
1092
  await instance.restart();
888
1093
  return {
889
1094
  instanceId: instance.id,
@@ -897,11 +1102,16 @@ exports.BankServiceBase = class BankServiceBase extends backendSdk.develitWorker
897
1102
  { data: input, schema: syncAccountTerminateInputSchema },
898
1103
  { successMessage: "Account sync workflow terminated" },
899
1104
  async ({ accountId }) => {
900
- const instance = await this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.get(accountId);
901
- await terminateSyncWorkflow(instance);
902
- return {
903
- instanceId: instance.id
904
- };
1105
+ return disableAccountSync({
1106
+ accountId,
1107
+ persistDisabled: () => this.setSyncEnabledOrThrow(accountId, false),
1108
+ terminateInstance: async () => {
1109
+ const instance = await this.getCurrentSyncInstance(accountId);
1110
+ await terminateSyncWorkflow(instance);
1111
+ return instance.id;
1112
+ },
1113
+ logger: syncEventConsoleLogger
1114
+ });
905
1115
  }
906
1116
  );
907
1117
  }
@@ -1181,18 +1391,61 @@ exports.BankServiceBase = class BankServiceBase extends backendSdk.develitWorker
1181
1391
  if (controller.cron === this.env.CRON_PAYMENT_STATUSES) {
1182
1392
  console.log("Scheduled CRON payment request statuses");
1183
1393
  await this.updatePaymentRequestStatuses();
1184
- return;
1394
+ }
1395
+ if (controller.cron === this.env.CRON_SYNC_WORKFLOW_DISPATCH) {
1396
+ await this.dispatchSyncWorkflows();
1185
1397
  }
1186
1398
  if (controller.cron === this.env.CRON_SYNC_WORKFLOW_HEARTBEAT) {
1187
1399
  console.log("Scheduled CRON sync workflow heartbeat");
1188
1400
  await this.heartbeatSyncWorkflows();
1189
1401
  }
1190
1402
  }
1403
+ /**
1404
+ * Lifecycle actions must target the instance that actually syncs the
1405
+ * account — under dispatch that is the windowed instance recorded with the
1406
+ * last sync write, not the legacy canonical id.
1407
+ */
1408
+ async setSyncEnabledOrThrow(accountId, syncEnabled) {
1409
+ const [updated] = await updateAccountSyncEnabledCommand(this.db, {
1410
+ accountId,
1411
+ syncEnabled
1412
+ }).command;
1413
+ if (!updated) {
1414
+ throw accountNotFoundError();
1415
+ }
1416
+ }
1417
+ async getCurrentSyncInstance(accountId) {
1418
+ const account = await bank.getAccountByIdQuery(this.db, { accountId });
1419
+ return this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.get(
1420
+ bank.resolveCurrentSyncInstanceId(
1421
+ accountId,
1422
+ account?.lastSyncMetadata?.instanceId
1423
+ )
1424
+ );
1425
+ }
1426
+ async dispatchSyncWorkflows() {
1427
+ const selection = this.env.SYNC_DISPATCH_ACCOUNT_IDS;
1428
+ if (!selection?.trim()) return;
1429
+ const accounts = await getSyncCandidateAccountsQuery(this.db);
1430
+ const maxIterations = bank.parseIterationBudgetParam(
1431
+ this.env.SYNC_WORKFLOW_MAX_ITERATIONS
1432
+ );
1433
+ if (maxIterations == null) {
1434
+ syncEventConsoleLogger.error(
1435
+ "sync-workflow.dispatch.invalid-max-iterations",
1436
+ { value: this.env.SYNC_WORKFLOW_MAX_ITERATIONS }
1437
+ );
1438
+ }
1439
+ await dispatchSyncWorkflows({
1440
+ entities: accounts.filter((a) => isDispatchEnabled(a.id, selection)),
1441
+ createInstance: (instanceId, accountId) => this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.create(
1442
+ buildDispatchCreateOptions(instanceId, accountId, maxIterations)
1443
+ ),
1444
+ logger: syncEventConsoleLogger
1445
+ });
1446
+ }
1191
1447
  async heartbeatSyncWorkflows() {
1192
- const accounts = await this.db.select({
1193
- id: paymentDirection.tables.account.id,
1194
- lastSyncMetadata: paymentDirection.tables.account.lastSyncMetadata
1195
- }).from(paymentDirection.tables.account).where(drizzleOrm.eq(paymentDirection.tables.account.status, "AUTHORIZED")).all();
1448
+ const accounts = await getSyncCandidateAccountsQuery(this.db);
1196
1449
  const resetAfterIterations = Number(
1197
1450
  this.env.SYNC_WORKFLOW_RESET_AFTER_ITERATIONS
1198
1451
  );
@@ -1204,9 +1457,18 @@ exports.BankServiceBase = class BankServiceBase extends backendSdk.develitWorker
1204
1457
  return;
1205
1458
  }
1206
1459
  await heartbeatSyncWorkflows({
1207
- entities: accounts.map((a) => ({
1460
+ entities: accounts.filter(
1461
+ (a) => !isDispatchActive(
1462
+ a.id,
1463
+ this.env.SYNC_DISPATCH_ACCOUNT_IDS,
1464
+ this.env.CRON_SYNC_WORKFLOW_DISPATCH
1465
+ )
1466
+ ).map((a) => ({
1208
1467
  id: a.id,
1209
- iterationCount: a.lastSyncMetadata?.iterationCount
1468
+ iterationCount: a.lastSyncMetadata?.iterationCount,
1469
+ syncIntervalS: a.syncIntervalS,
1470
+ lastSyncAt: a.lastSyncAt,
1471
+ createdAt: a.createdAt
1210
1472
  })),
1211
1473
  resetAfterIterations,
1212
1474
  getInstance: (id) => this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.get(id),
@@ -1214,11 +1476,7 @@ exports.BankServiceBase = class BankServiceBase extends backendSdk.develitWorker
1214
1476
  id,
1215
1477
  params: { accountId: id }
1216
1478
  }),
1217
- logger: {
1218
- info: (event, data) => console.log(JSON.stringify({ level: "info", event, ...data })),
1219
- warn: (event, data) => console.warn(JSON.stringify({ level: "warn", event, ...data })),
1220
- error: (event, data) => console.error(JSON.stringify({ level: "error", event, ...data }))
1221
- }
1479
+ logger: syncEventConsoleLogger
1222
1480
  });
1223
1481
  }
1224
1482
  async handleAuthorizationCallback(input) {
@@ -1977,7 +2235,12 @@ exports.BankServiceBase = class BankServiceBase extends backendSdk.develitWorker
1977
2235
  if (includeWorkflow) {
1978
2236
  let status;
1979
2237
  try {
1980
- const instance = await this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.get(a.id);
2238
+ const instance = await this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.get(
2239
+ bank.resolveCurrentSyncInstanceId(
2240
+ a.id,
2241
+ a.lastSyncMetadata?.instanceId
2242
+ )
2243
+ );
1981
2244
  status = await instance.status();
1982
2245
  } catch (_) {
1983
2246
  status = null;
@@ -2074,14 +2337,15 @@ exports.BankServiceBase = class BankServiceBase extends backendSdk.develitWorker
2074
2337
  async ({ accountId }) => {
2075
2338
  const account = await bank.getAccountByIdQuery(this.db, { accountId });
2076
2339
  if (!account) {
2077
- throw backendSdk.createInternalError(null, {
2078
- message: "Account not found",
2079
- code: "DB-B-005",
2080
- status: 404
2081
- });
2340
+ throw accountNotFoundError();
2082
2341
  }
2083
2342
  try {
2084
- const instance = await this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.get(accountId);
2343
+ const instance = await this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.get(
2344
+ bank.resolveCurrentSyncInstanceId(
2345
+ accountId,
2346
+ account.lastSyncMetadata?.instanceId
2347
+ )
2348
+ );
2085
2349
  await terminateSyncWorkflow(instance);
2086
2350
  } catch (error) {
2087
2351
  this.log({
package/dist/base.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { B as BatchSelectType, P as PaymentRequestSelectType, A as AccountSelectType, a as PaymentSelectType, L as LastSyncMetadata, C as ConnectorConfig, t as tables, b as ConnectorKey, c as ConfigEnvironmentBank, I as IBankConnector, d as PaymentType, e as CurrencyCode, H as HandleAuthorizationCallbackInput, f as HandleAuthorizationCallbackOutput } from './shared/bank.BQwwtIR4.cjs';
2
- import { S as SendPaymentInput, a as SendPaymentOutput, b as SendPaymentSyncInput, c as SendPaymentSyncOutput, F as FinbricksSupportedBanksResponse } from './shared/bank.BUzoc8p6.cjs';
1
+ import { B as BatchSelectType, P as PaymentRequestSelectType, A as AccountSelectType, a as PaymentSelectType, L as LastSyncMetadata, C as ConnectorConfig, t as tables, b as ConnectorKey, c as ConfigEnvironmentBank, I as IBankConnector, d as PaymentType, e as CurrencyCode, H as HandleAuthorizationCallbackInput, f as HandleAuthorizationCallbackOutput } from './shared/bank.mkq4IlpC.cjs';
2
+ import { S as SendPaymentInput, a as SendPaymentOutput, b as SendPaymentSyncInput, c as SendPaymentSyncOutput, F as FinbricksSupportedBanksResponse } from './shared/bank.BhyIbhvK.cjs';
3
3
  import * as _develit_io_backend_sdk from '@develit-io/backend-sdk';
4
4
  import { WorkflowInstanceStatus, IRPCResponse } from '@develit-io/backend-sdk';
5
5
  import { WorkerEntrypoint } from 'cloudflare:workers';
@@ -2957,19 +2957,19 @@ declare const getPaymentsInputSchema: z.ZodObject<{
2957
2957
  filterPaymentDateTo: z.ZodOptional<z.ZodDate>;
2958
2958
  filterPaymentStatus: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
2959
2959
  PROCESSING: "PROCESSING";
2960
- PENDING: "PENDING";
2961
2960
  BOOKED: "BOOKED";
2962
- CANCELLED: "CANCELLED";
2963
2961
  REJECTED: "REJECTED";
2962
+ PENDING: "PENDING";
2963
+ CANCELLED: "CANCELLED";
2964
2964
  SCHEDULED: "SCHEDULED";
2965
2965
  HOLD: "HOLD";
2966
2966
  INFO: "INFO";
2967
2967
  }>, z.ZodArray<z.ZodEnum<{
2968
2968
  PROCESSING: "PROCESSING";
2969
- PENDING: "PENDING";
2970
2969
  BOOKED: "BOOKED";
2971
- CANCELLED: "CANCELLED";
2972
2970
  REJECTED: "REJECTED";
2971
+ PENDING: "PENDING";
2972
+ CANCELLED: "CANCELLED";
2973
2973
  SCHEDULED: "SCHEDULED";
2974
2974
  HOLD: "HOLD";
2975
2975
  INFO: "INFO";
@@ -3114,7 +3114,8 @@ declare const syncAccountTerminateInputSchema: z.ZodObject<{
3114
3114
  accountId: z.ZodUUID;
3115
3115
  }, z.core.$strip>;
3116
3116
  declare const syncAccountTerminateOutputSchema: z.ZodObject<{
3117
- instanceId: z.ZodString;
3117
+ terminated: z.ZodBoolean;
3118
+ instanceId: z.ZodNullable<z.ZodString>;
3118
3119
  }, z.core.$strip>;
3119
3120
  interface SyncAccountTerminateInput extends z.infer<typeof syncAccountTerminateInputSchema> {
3120
3121
  }
@@ -3266,6 +3267,22 @@ declare const updateAccountInputSchema: z.ZodObject<{
3266
3267
  identity: undefined;
3267
3268
  generated: undefined;
3268
3269
  }, {}>;
3270
+ syncEnabled: drizzle_orm_sqlite_core.SQLiteColumn<{
3271
+ name: string;
3272
+ tableName: "account";
3273
+ dataType: "boolean";
3274
+ data: boolean;
3275
+ driverParam: number;
3276
+ notNull: true;
3277
+ hasDefault: true;
3278
+ isPrimaryKey: false;
3279
+ isAutoincrement: false;
3280
+ hasRuntimeDefault: false;
3281
+ enumValues: undefined;
3282
+ baseColumn: never;
3283
+ identity: undefined;
3284
+ generated: undefined;
3285
+ }, {}>;
3269
3286
  syncIntervalS: drizzle_orm_sqlite_core.SQLiteColumn<{
3270
3287
  name: string;
3271
3288
  tableName: "account";
@@ -3888,18 +3905,18 @@ declare const getPaymentRequestsInputSchema: z.ZodObject<{
3888
3905
  filterStatus: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
3889
3906
  AUTHORIZED: "AUTHORIZED";
3890
3907
  COMPLETED: "COMPLETED";
3891
- BOOKED: "BOOKED";
3892
- REJECTED: "REJECTED";
3893
3908
  OPENED: "OPENED";
3909
+ BOOKED: "BOOKED";
3894
3910
  SETTLED: "SETTLED";
3911
+ REJECTED: "REJECTED";
3895
3912
  CLOSED: "CLOSED";
3896
3913
  }>, z.ZodArray<z.ZodEnum<{
3897
3914
  AUTHORIZED: "AUTHORIZED";
3898
3915
  COMPLETED: "COMPLETED";
3899
- BOOKED: "BOOKED";
3900
- REJECTED: "REJECTED";
3901
3916
  OPENED: "OPENED";
3917
+ BOOKED: "BOOKED";
3902
3918
  SETTLED: "SETTLED";
3919
+ REJECTED: "REJECTED";
3903
3920
  CLOSED: "CLOSED";
3904
3921
  }>>]>>;
3905
3922
  filterPaymentType: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
@@ -4092,6 +4109,7 @@ declare class BankServiceBase extends BankServiceBase_base {
4092
4109
  status: "AUTHORIZED" | "DISABLED" | "EXPIRED";
4093
4110
  bankRefId: string;
4094
4111
  batchSizeLimit: number;
4112
+ syncEnabled: boolean;
4095
4113
  syncIntervalS: number;
4096
4114
  lastSyncAt: Date | null;
4097
4115
  lastSyncMetadata: LastSyncMetadata | null;
@@ -4127,6 +4145,7 @@ declare class BankServiceBase extends BankServiceBase_base {
4127
4145
  status: "AUTHORIZED" | "DISABLED" | "EXPIRED";
4128
4146
  bankRefId: string;
4129
4147
  batchSizeLimit: number;
4148
+ syncEnabled: boolean;
4130
4149
  syncIntervalS: number;
4131
4150
  lastSyncAt: Date | null;
4132
4151
  lastSyncMetadata: LastSyncMetadata | null;
@@ -4193,6 +4212,14 @@ declare class BankServiceBase extends BankServiceBase_base {
4193
4212
  statusChanged: number;
4194
4213
  }>>;
4195
4214
  scheduled(controller: ScheduledController): Promise<void>;
4215
+ /**
4216
+ * Lifecycle actions must target the instance that actually syncs the
4217
+ * account — under dispatch that is the windowed instance recorded with the
4218
+ * last sync write, not the legacy canonical id.
4219
+ */
4220
+ private setSyncEnabledOrThrow;
4221
+ private getCurrentSyncInstance;
4222
+ private dispatchSyncWorkflows;
4196
4223
  private heartbeatSyncWorkflows;
4197
4224
  handleAuthorizationCallback(input: HandleAuthorizationCallbackInput): Promise<IRPCResponse<HandleAuthorizationCallbackOutput>>;
4198
4225
  closePaymentRequest(input: ClosePaymentRequestInput): Promise<IRPCResponse<ClosePaymentRequestOutput>>;