@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
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.
|
|
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.
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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(
|
|
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
|
|
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(
|
|
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.
|
|
2
|
-
import { S as SendPaymentInput, a as SendPaymentOutput, b as SendPaymentSyncInput, c as SendPaymentSyncOutput, F as FinbricksSupportedBanksResponse } from './shared/bank.
|
|
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
|
-
|
|
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>>;
|