@develit-services/bank 5.9.1 → 6.0.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 +45 -60
- package/dist/base.mjs +41 -56
- package/dist/export/workflows.cjs +19 -10
- package/dist/export/workflows.d.cts +0 -1
- package/dist/export/workflows.d.mts +0 -1
- package/dist/export/workflows.d.ts +0 -1
- package/dist/export/workflows.mjs +19 -10
- package/dist/service.d.cts +0 -1
- package/dist/service.d.mts +0 -1
- package/dist/service.d.ts +0 -1
- package/dist/shared/{bank.DAjqsBQN.mjs → bank.D9DoIABz.mjs} +33 -13
- package/dist/shared/{bank.DBTtRCcf.cjs → bank.DQZxSHKV.cjs} +38 -14
- package/package.json +1 -1
package/dist/base.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const backendSdk = require('@develit-io/backend-sdk');
|
|
4
4
|
const paymentDirection = require('./shared/bank.BsWBG0gb.cjs');
|
|
5
|
-
const bank = require('./shared/bank.
|
|
5
|
+
const bank = require('./shared/bank.DQZxSHKV.cjs');
|
|
6
6
|
const drizzleOrm = require('drizzle-orm');
|
|
7
7
|
const cloudflare_workers = require('cloudflare:workers');
|
|
8
8
|
const d1 = require('drizzle-orm/d1');
|
|
@@ -107,67 +107,31 @@ async function heartbeatSyncWorkflows({
|
|
|
107
107
|
);
|
|
108
108
|
}
|
|
109
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
110
|
async function dispatchSyncWorkflows({
|
|
136
111
|
entities,
|
|
137
112
|
createInstance,
|
|
138
113
|
logger,
|
|
139
114
|
now = Date.now,
|
|
140
|
-
|
|
141
|
-
recoveryWindowMs = DISPATCH_RECOVERY_WINDOW_MS
|
|
115
|
+
recoveryWindowMs = bank.DISPATCH_RECOVERY_WINDOW_MS
|
|
142
116
|
}) {
|
|
143
117
|
const at = now();
|
|
144
118
|
await Promise.all(
|
|
145
119
|
entities.map(async (entity) => {
|
|
146
120
|
const { id, lastSyncAt } = entity;
|
|
147
|
-
|
|
148
|
-
if (staleness == null) {
|
|
121
|
+
if (!lastSyncAt) {
|
|
149
122
|
logger.warn("sync-workflow.dispatch.sync-age-unknown", { id });
|
|
150
123
|
return;
|
|
151
124
|
}
|
|
152
|
-
|
|
153
|
-
const instanceId = buildDispatchInstanceId(id, at, recoveryWindowMs);
|
|
125
|
+
const instanceId = bank.buildDispatchInstanceId(id, at, recoveryWindowMs);
|
|
154
126
|
try {
|
|
155
127
|
await createInstance(instanceId, id);
|
|
156
128
|
logger.info("sync-workflow.dispatch.created", {
|
|
157
129
|
id,
|
|
158
130
|
instanceId,
|
|
159
|
-
lastSyncAt: lastSyncAt
|
|
160
|
-
staleForS: Math.round(staleness.staleForMs / 1e3),
|
|
161
|
-
thresholdS: Math.round(staleness.thresholdMs / 1e3)
|
|
131
|
+
lastSyncAt: lastSyncAt.toISOString()
|
|
162
132
|
});
|
|
163
133
|
} catch (err) {
|
|
164
|
-
if (isAlreadyExists(err))
|
|
165
|
-
logger.info("sync-workflow.dispatch.already-running", {
|
|
166
|
-
id,
|
|
167
|
-
instanceId
|
|
168
|
-
});
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
134
|
+
if (bank.isAlreadyExists(err)) return;
|
|
171
135
|
logger.error("sync-workflow.dispatch.failed", {
|
|
172
136
|
id,
|
|
173
137
|
instanceId,
|
|
@@ -1028,17 +992,14 @@ exports.BankServiceBase = class BankServiceBase extends backendSdk.develitWorker
|
|
|
1028
992
|
this.env.SYNC_DISPATCH_ACCOUNT_IDS,
|
|
1029
993
|
this.env.CRON_SYNC_WORKFLOW_DISPATCH
|
|
1030
994
|
)) {
|
|
1031
|
-
const instanceId = buildDispatchInstanceId(accountId, Date.now());
|
|
1032
|
-
const maxIterations = bank.parseIterationBudgetParam(
|
|
1033
|
-
this.env.SYNC_WORKFLOW_MAX_ITERATIONS
|
|
1034
|
-
);
|
|
995
|
+
const instanceId = bank.buildDispatchInstanceId(accountId, Date.now());
|
|
1035
996
|
let instance2;
|
|
1036
997
|
try {
|
|
1037
998
|
instance2 = await this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.create(
|
|
1038
|
-
buildDispatchCreateOptions(instanceId, accountId
|
|
999
|
+
bank.buildDispatchCreateOptions(instanceId, accountId)
|
|
1039
1000
|
);
|
|
1040
1001
|
} catch (err) {
|
|
1041
|
-
if (!isAlreadyExists(err)) throw err;
|
|
1002
|
+
if (!bank.isAlreadyExists(err)) throw err;
|
|
1042
1003
|
instance2 = await this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.get(instanceId);
|
|
1043
1004
|
}
|
|
1044
1005
|
return {
|
|
@@ -1078,8 +1039,41 @@ exports.BankServiceBase = class BankServiceBase extends backendSdk.develitWorker
|
|
|
1078
1039
|
{ successMessage: "Account sync workflow restarted" },
|
|
1079
1040
|
async ({ accountId }) => {
|
|
1080
1041
|
await this.setSyncEnabledOrThrow(accountId, true);
|
|
1081
|
-
|
|
1082
|
-
|
|
1042
|
+
if (isDispatchActive(
|
|
1043
|
+
accountId,
|
|
1044
|
+
this.env.SYNC_DISPATCH_ACCOUNT_IDS,
|
|
1045
|
+
this.env.CRON_SYNC_WORKFLOW_DISPATCH
|
|
1046
|
+
)) {
|
|
1047
|
+
const instanceId = bank.buildDispatchInstanceId(accountId, Date.now());
|
|
1048
|
+
let instance2;
|
|
1049
|
+
try {
|
|
1050
|
+
instance2 = await this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.create(
|
|
1051
|
+
bank.buildDispatchCreateOptions(instanceId, accountId)
|
|
1052
|
+
);
|
|
1053
|
+
} catch (err) {
|
|
1054
|
+
if (!bank.isAlreadyExists(err)) throw err;
|
|
1055
|
+
instance2 = await this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.get(instanceId);
|
|
1056
|
+
await instance2.restart();
|
|
1057
|
+
}
|
|
1058
|
+
return {
|
|
1059
|
+
instanceId: instance2.id,
|
|
1060
|
+
details: await instance2.status()
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
let instance;
|
|
1064
|
+
try {
|
|
1065
|
+
const existing = await this.getCurrentSyncInstance(accountId);
|
|
1066
|
+
await existing.restart();
|
|
1067
|
+
instance = existing;
|
|
1068
|
+
} catch (err) {
|
|
1069
|
+
if (!bank.isInstanceNotFound(err)) throw err;
|
|
1070
|
+
}
|
|
1071
|
+
if (!instance) {
|
|
1072
|
+
instance = await this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.create({
|
|
1073
|
+
id: accountId,
|
|
1074
|
+
params: { accountId }
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1083
1077
|
return {
|
|
1084
1078
|
instanceId: instance.id,
|
|
1085
1079
|
details: await instance.status()
|
|
@@ -1417,19 +1411,10 @@ exports.BankServiceBase = class BankServiceBase extends backendSdk.develitWorker
|
|
|
1417
1411
|
const selection = this.env.SYNC_DISPATCH_ACCOUNT_IDS;
|
|
1418
1412
|
if (!selection?.trim()) return;
|
|
1419
1413
|
const accounts = await getSyncCandidateAccountsQuery(this.db);
|
|
1420
|
-
const maxIterations = bank.parseIterationBudgetParam(
|
|
1421
|
-
this.env.SYNC_WORKFLOW_MAX_ITERATIONS
|
|
1422
|
-
);
|
|
1423
|
-
if (maxIterations == null) {
|
|
1424
|
-
syncEventConsoleLogger.error(
|
|
1425
|
-
"sync-workflow.dispatch.invalid-max-iterations",
|
|
1426
|
-
{ value: this.env.SYNC_WORKFLOW_MAX_ITERATIONS }
|
|
1427
|
-
);
|
|
1428
|
-
}
|
|
1429
1414
|
await dispatchSyncWorkflows({
|
|
1430
1415
|
entities: accounts.filter((a) => isDispatchEnabled(a.id, selection)),
|
|
1431
1416
|
createInstance: (instanceId, accountId) => this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.create(
|
|
1432
|
-
buildDispatchCreateOptions(instanceId, accountId
|
|
1417
|
+
bank.buildDispatchCreateOptions(instanceId, accountId)
|
|
1433
1418
|
),
|
|
1434
1419
|
logger: syncEventConsoleLogger
|
|
1435
1420
|
});
|
package/dist/base.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { uuidv4, first, buildMultiFilterConditions as buildMultiFilterConditions$1, bankAccountMetadataSchema, structuredAddressSchema, workflowInstanceStatusSchema, develitWorker, createInternalError, action, service } from '@develit-io/backend-sdk';
|
|
2
2
|
import { G as tables, g as accountInsertSchema, H as relations, o as isProcessedStatus, p as isTerminalStatus, L as getNonTerminalPaymentRequestsQuery, x as toIncomingPayment, N as calculateCzechIban, j as assignAccount, u as toBatchedPayment, y as toPaymentRequestInsert, a as FinbricksClient, F as FINBRICKS_ENDPOINTS } from './shared/bank.kz-PKVi5.mjs';
|
|
3
|
-
import {
|
|
3
|
+
import { k as isAlreadyExists, D as DISPATCH_RECOVERY_WINDOW_MS, l as buildDispatchInstanceId, m as encrypt, d as createCredentialsResolver, i as initiateConnector, n as buildDispatchCreateOptions, o as isInstanceNotFound, e as updatePaymentRequestStatusCommand, a as getPaymentRequestsByBatchIdQuery, g as getBatchByIdQuery, u as upsertBatchCommand, b as getAccountByIdQuery, r as resolveCurrentSyncInstanceId, p as importAesKey, j as createPaymentCommand } from './shared/bank.D9DoIABz.mjs';
|
|
4
4
|
import { eq, sql, and, like, asc, desc, inArray, gte, lte, isNull, count } from 'drizzle-orm';
|
|
5
5
|
import { WorkerEntrypoint } from 'cloudflare:workers';
|
|
6
6
|
import { drizzle } from 'drizzle-orm/d1';
|
|
@@ -105,67 +105,31 @@ async function heartbeatSyncWorkflows({
|
|
|
105
105
|
);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
const DISPATCH_RECOVERY_WINDOW_MS = 5 * 60 * 1e3;
|
|
109
|
-
function buildDispatchInstanceId(accountId, nowMs, recoveryWindowMs = DISPATCH_RECOVERY_WINDOW_MS) {
|
|
110
|
-
return `${accountId}-${Math.floor(nowMs / recoveryWindowMs)}`;
|
|
111
|
-
}
|
|
112
|
-
function buildDispatchCreateOptions(instanceId, accountId, maxIterations) {
|
|
113
|
-
return {
|
|
114
|
-
id: instanceId,
|
|
115
|
-
// Omitted (not undefined) when unresolved: the workflow treats a missing
|
|
116
|
-
// budget as unbounded — degraded but syncing.
|
|
117
|
-
params: maxIterations == null ? { accountId } : { accountId, maxIterations },
|
|
118
|
-
// Dispatch mints ~24 instances per account per day and step outputs carry
|
|
119
|
-
// bank payloads — the default 30-day retention would pile up billable
|
|
120
|
-
// storage for state nobody reads after success.
|
|
121
|
-
retention: {
|
|
122
|
-
successRetention: "1 day",
|
|
123
|
-
errorRetention: "7 days"
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
function isAlreadyExists(err) {
|
|
128
|
-
const message = err instanceof Error ? err.message : typeof err === "object" && err !== null && "message" in err ? String(err.message) : "";
|
|
129
|
-
return /already exists/i.test(message);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const DEFAULT_MIN_GRACE_MS = 3 * 60 * 1e3;
|
|
133
108
|
async function dispatchSyncWorkflows({
|
|
134
109
|
entities,
|
|
135
110
|
createInstance,
|
|
136
111
|
logger,
|
|
137
112
|
now = Date.now,
|
|
138
|
-
minGraceMs = DEFAULT_MIN_GRACE_MS,
|
|
139
113
|
recoveryWindowMs = DISPATCH_RECOVERY_WINDOW_MS
|
|
140
114
|
}) {
|
|
141
115
|
const at = now();
|
|
142
116
|
await Promise.all(
|
|
143
117
|
entities.map(async (entity) => {
|
|
144
118
|
const { id, lastSyncAt } = entity;
|
|
145
|
-
|
|
146
|
-
if (staleness == null) {
|
|
119
|
+
if (!lastSyncAt) {
|
|
147
120
|
logger.warn("sync-workflow.dispatch.sync-age-unknown", { id });
|
|
148
121
|
return;
|
|
149
122
|
}
|
|
150
|
-
if (staleness.staleForMs <= staleness.thresholdMs) return;
|
|
151
123
|
const instanceId = buildDispatchInstanceId(id, at, recoveryWindowMs);
|
|
152
124
|
try {
|
|
153
125
|
await createInstance(instanceId, id);
|
|
154
126
|
logger.info("sync-workflow.dispatch.created", {
|
|
155
127
|
id,
|
|
156
128
|
instanceId,
|
|
157
|
-
lastSyncAt: lastSyncAt
|
|
158
|
-
staleForS: Math.round(staleness.staleForMs / 1e3),
|
|
159
|
-
thresholdS: Math.round(staleness.thresholdMs / 1e3)
|
|
129
|
+
lastSyncAt: lastSyncAt.toISOString()
|
|
160
130
|
});
|
|
161
131
|
} catch (err) {
|
|
162
|
-
if (isAlreadyExists(err))
|
|
163
|
-
logger.info("sync-workflow.dispatch.already-running", {
|
|
164
|
-
id,
|
|
165
|
-
instanceId
|
|
166
|
-
});
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
132
|
+
if (isAlreadyExists(err)) return;
|
|
169
133
|
logger.error("sync-workflow.dispatch.failed", {
|
|
170
134
|
id,
|
|
171
135
|
instanceId,
|
|
@@ -1027,13 +991,10 @@ let BankServiceBase = class extends develitWorker(WorkerEntrypoint) {
|
|
|
1027
991
|
this.env.CRON_SYNC_WORKFLOW_DISPATCH
|
|
1028
992
|
)) {
|
|
1029
993
|
const instanceId = buildDispatchInstanceId(accountId, Date.now());
|
|
1030
|
-
const maxIterations = parseIterationBudgetParam(
|
|
1031
|
-
this.env.SYNC_WORKFLOW_MAX_ITERATIONS
|
|
1032
|
-
);
|
|
1033
994
|
let instance2;
|
|
1034
995
|
try {
|
|
1035
996
|
instance2 = await this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.create(
|
|
1036
|
-
buildDispatchCreateOptions(instanceId, accountId
|
|
997
|
+
buildDispatchCreateOptions(instanceId, accountId)
|
|
1037
998
|
);
|
|
1038
999
|
} catch (err) {
|
|
1039
1000
|
if (!isAlreadyExists(err)) throw err;
|
|
@@ -1076,8 +1037,41 @@ let BankServiceBase = class extends develitWorker(WorkerEntrypoint) {
|
|
|
1076
1037
|
{ successMessage: "Account sync workflow restarted" },
|
|
1077
1038
|
async ({ accountId }) => {
|
|
1078
1039
|
await this.setSyncEnabledOrThrow(accountId, true);
|
|
1079
|
-
|
|
1080
|
-
|
|
1040
|
+
if (isDispatchActive(
|
|
1041
|
+
accountId,
|
|
1042
|
+
this.env.SYNC_DISPATCH_ACCOUNT_IDS,
|
|
1043
|
+
this.env.CRON_SYNC_WORKFLOW_DISPATCH
|
|
1044
|
+
)) {
|
|
1045
|
+
const instanceId = buildDispatchInstanceId(accountId, Date.now());
|
|
1046
|
+
let instance2;
|
|
1047
|
+
try {
|
|
1048
|
+
instance2 = await this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.create(
|
|
1049
|
+
buildDispatchCreateOptions(instanceId, accountId)
|
|
1050
|
+
);
|
|
1051
|
+
} catch (err) {
|
|
1052
|
+
if (!isAlreadyExists(err)) throw err;
|
|
1053
|
+
instance2 = await this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.get(instanceId);
|
|
1054
|
+
await instance2.restart();
|
|
1055
|
+
}
|
|
1056
|
+
return {
|
|
1057
|
+
instanceId: instance2.id,
|
|
1058
|
+
details: await instance2.status()
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
let instance;
|
|
1062
|
+
try {
|
|
1063
|
+
const existing = await this.getCurrentSyncInstance(accountId);
|
|
1064
|
+
await existing.restart();
|
|
1065
|
+
instance = existing;
|
|
1066
|
+
} catch (err) {
|
|
1067
|
+
if (!isInstanceNotFound(err)) throw err;
|
|
1068
|
+
}
|
|
1069
|
+
if (!instance) {
|
|
1070
|
+
instance = await this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.create({
|
|
1071
|
+
id: accountId,
|
|
1072
|
+
params: { accountId }
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1081
1075
|
return {
|
|
1082
1076
|
instanceId: instance.id,
|
|
1083
1077
|
details: await instance.status()
|
|
@@ -1415,19 +1409,10 @@ let BankServiceBase = class extends develitWorker(WorkerEntrypoint) {
|
|
|
1415
1409
|
const selection = this.env.SYNC_DISPATCH_ACCOUNT_IDS;
|
|
1416
1410
|
if (!selection?.trim()) return;
|
|
1417
1411
|
const accounts = await getSyncCandidateAccountsQuery(this.db);
|
|
1418
|
-
const maxIterations = parseIterationBudgetParam(
|
|
1419
|
-
this.env.SYNC_WORKFLOW_MAX_ITERATIONS
|
|
1420
|
-
);
|
|
1421
|
-
if (maxIterations == null) {
|
|
1422
|
-
syncEventConsoleLogger.error(
|
|
1423
|
-
"sync-workflow.dispatch.invalid-max-iterations",
|
|
1424
|
-
{ value: this.env.SYNC_WORKFLOW_MAX_ITERATIONS }
|
|
1425
|
-
);
|
|
1426
|
-
}
|
|
1427
1412
|
await dispatchSyncWorkflows({
|
|
1428
1413
|
entities: accounts.filter((a) => isDispatchEnabled(a.id, selection)),
|
|
1429
1414
|
createInstance: (instanceId, accountId) => this.env.SYNC_ACCOUNT_PAYMENTS_WORKFLOW.create(
|
|
1430
|
-
buildDispatchCreateOptions(instanceId, accountId
|
|
1415
|
+
buildDispatchCreateOptions(instanceId, accountId)
|
|
1431
1416
|
),
|
|
1432
1417
|
logger: syncEventConsoleLogger
|
|
1433
1418
|
});
|
|
@@ -4,7 +4,7 @@ const backendSdk = require('@develit-io/backend-sdk');
|
|
|
4
4
|
const paymentDirection = require('../shared/bank.BsWBG0gb.cjs');
|
|
5
5
|
const batchLifecycle = require('../shared/bank.NF8bZBy0.cjs');
|
|
6
6
|
const drizzleOrm = require('drizzle-orm');
|
|
7
|
-
const bank = require('../shared/bank.
|
|
7
|
+
const bank = require('../shared/bank.DQZxSHKV.cjs');
|
|
8
8
|
const cloudflare_workers = require('cloudflare:workers');
|
|
9
9
|
const cloudflare_workflows = require('cloudflare:workflows');
|
|
10
10
|
const d1 = require('drizzle-orm/d1');
|
|
@@ -425,18 +425,18 @@ async function pushToQueue(queue, message) {
|
|
|
425
425
|
}
|
|
426
426
|
class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
|
|
427
427
|
async run(event, step) {
|
|
428
|
-
const { accountId
|
|
429
|
-
const iterationBudget = bank.resolveIterationBudget(maxIterations);
|
|
428
|
+
const { accountId } = event.payload;
|
|
430
429
|
const db = d1.drizzle(this.env.BANK_D1, { schema: paymentDirection.tables, relations: paymentDirection.relations });
|
|
431
430
|
const logger = createWorkflowLogger(event.instanceId);
|
|
432
431
|
if (!accountId) {
|
|
433
432
|
throw new cloudflare_workflows.NonRetryableError(`Haven't obtained accountId to load.`);
|
|
434
433
|
}
|
|
434
|
+
const deadlineMs = bank.computeWindowDeadline(event.instanceId, accountId);
|
|
435
435
|
const workflowStartedAt = await step.do(
|
|
436
436
|
"capture workflow start time",
|
|
437
437
|
async () => Date.now()
|
|
438
438
|
);
|
|
439
|
-
for (let iteration = 0;
|
|
439
|
+
for (let iteration = 0; ; iteration++) {
|
|
440
440
|
const account = await step.do("load account", async () => {
|
|
441
441
|
const account2 = await bank.getAccountByIdQuery(db, { accountId });
|
|
442
442
|
if (!account2) {
|
|
@@ -481,6 +481,15 @@ class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
|
|
|
481
481
|
isCatchUp: window.isCatchUp
|
|
482
482
|
};
|
|
483
483
|
});
|
|
484
|
+
if (deadlineMs != null && windowMs.dateFromMs >= deadlineMs) {
|
|
485
|
+
logger.info("sync.window-elapsed", {
|
|
486
|
+
accountId,
|
|
487
|
+
instanceId: event.instanceId,
|
|
488
|
+
iteration,
|
|
489
|
+
deadline: new Date(deadlineMs).toISOString()
|
|
490
|
+
});
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
484
493
|
const syncWindow = {
|
|
485
494
|
dateFrom: new Date(windowMs.dateFromMs),
|
|
486
495
|
dateTo: new Date(windowMs.dateToMs),
|
|
@@ -695,6 +704,12 @@ class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
|
|
|
695
704
|
lastSyncMetadata,
|
|
696
705
|
seenInstanceId: account.lastSyncMetadata?.instanceId ?? null
|
|
697
706
|
}).command;
|
|
707
|
+
if (eventsToEmit.length) {
|
|
708
|
+
await pushToQueue(
|
|
709
|
+
this.env.QUEUE_BUS_QUEUE,
|
|
710
|
+
eventsToEmit
|
|
711
|
+
);
|
|
712
|
+
}
|
|
698
713
|
let syncStateRows;
|
|
699
714
|
if (createCommands.length) {
|
|
700
715
|
const [updateResult] = await db.batch(
|
|
@@ -710,12 +725,6 @@ class BankSyncAccountPayments extends cloudflare_workers.WorkflowEntrypoint {
|
|
|
710
725
|
instanceId: event.instanceId
|
|
711
726
|
});
|
|
712
727
|
}
|
|
713
|
-
if (eventsToEmit.length) {
|
|
714
|
-
await pushToQueue(
|
|
715
|
-
this.env.QUEUE_BUS_QUEUE,
|
|
716
|
-
eventsToEmit
|
|
717
|
-
);
|
|
718
|
-
}
|
|
719
728
|
return {
|
|
720
729
|
...lastSyncMetadata,
|
|
721
730
|
newLastSyncAt: effectiveWindow.dateTo
|
|
@@ -9,7 +9,6 @@ declare class BankProcessBatch extends WorkflowEntrypoint<BankEnv, Params$1> {
|
|
|
9
9
|
|
|
10
10
|
type Params = {
|
|
11
11
|
accountId: string;
|
|
12
|
-
maxIterations?: number;
|
|
13
12
|
};
|
|
14
13
|
declare class BankSyncAccountPayments extends WorkflowEntrypoint<BankEnv, Params> {
|
|
15
14
|
run(event: WorkflowEvent<Params>, step: WorkflowStep): Promise<void>;
|
|
@@ -9,7 +9,6 @@ declare class BankProcessBatch extends WorkflowEntrypoint<BankEnv, Params$1> {
|
|
|
9
9
|
|
|
10
10
|
type Params = {
|
|
11
11
|
accountId: string;
|
|
12
|
-
maxIterations?: number;
|
|
13
12
|
};
|
|
14
13
|
declare class BankSyncAccountPayments extends WorkflowEntrypoint<BankEnv, Params> {
|
|
15
14
|
run(event: WorkflowEvent<Params>, step: WorkflowStep): Promise<void>;
|
|
@@ -9,7 +9,6 @@ declare class BankProcessBatch extends WorkflowEntrypoint<BankEnv, Params$1> {
|
|
|
9
9
|
|
|
10
10
|
type Params = {
|
|
11
11
|
accountId: string;
|
|
12
|
-
maxIterations?: number;
|
|
13
12
|
};
|
|
14
13
|
declare class BankSyncAccountPayments extends WorkflowEntrypoint<BankEnv, Params> {
|
|
15
14
|
run(event: WorkflowEvent<Params>, step: WorkflowStep): Promise<void>;
|
|
@@ -2,7 +2,7 @@ import { first, uuidv4, asNonEmpty } from '@develit-io/backend-sdk';
|
|
|
2
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
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
|
|
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 computeWindowDeadline, h as isSupersededBy, j as createPaymentCommand } from '../shared/bank.D9DoIABz.mjs';
|
|
6
6
|
import { WorkflowEntrypoint } from 'cloudflare:workers';
|
|
7
7
|
import { NonRetryableError } from 'cloudflare:workflows';
|
|
8
8
|
import { drizzle } from 'drizzle-orm/d1';
|
|
@@ -423,18 +423,18 @@ async function pushToQueue(queue, message) {
|
|
|
423
423
|
}
|
|
424
424
|
class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
425
425
|
async run(event, step) {
|
|
426
|
-
const { accountId
|
|
427
|
-
const iterationBudget = resolveIterationBudget(maxIterations);
|
|
426
|
+
const { accountId } = event.payload;
|
|
428
427
|
const db = drizzle(this.env.BANK_D1, { schema: tables, relations });
|
|
429
428
|
const logger = createWorkflowLogger(event.instanceId);
|
|
430
429
|
if (!accountId) {
|
|
431
430
|
throw new NonRetryableError(`Haven't obtained accountId to load.`);
|
|
432
431
|
}
|
|
432
|
+
const deadlineMs = computeWindowDeadline(event.instanceId, accountId);
|
|
433
433
|
const workflowStartedAt = await step.do(
|
|
434
434
|
"capture workflow start time",
|
|
435
435
|
async () => Date.now()
|
|
436
436
|
);
|
|
437
|
-
for (let iteration = 0;
|
|
437
|
+
for (let iteration = 0; ; iteration++) {
|
|
438
438
|
const account = await step.do("load account", async () => {
|
|
439
439
|
const account2 = await getAccountByIdQuery(db, { accountId });
|
|
440
440
|
if (!account2) {
|
|
@@ -479,6 +479,15 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
|
479
479
|
isCatchUp: window.isCatchUp
|
|
480
480
|
};
|
|
481
481
|
});
|
|
482
|
+
if (deadlineMs != null && windowMs.dateFromMs >= deadlineMs) {
|
|
483
|
+
logger.info("sync.window-elapsed", {
|
|
484
|
+
accountId,
|
|
485
|
+
instanceId: event.instanceId,
|
|
486
|
+
iteration,
|
|
487
|
+
deadline: new Date(deadlineMs).toISOString()
|
|
488
|
+
});
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
482
491
|
const syncWindow = {
|
|
483
492
|
dateFrom: new Date(windowMs.dateFromMs),
|
|
484
493
|
dateTo: new Date(windowMs.dateToMs),
|
|
@@ -693,6 +702,12 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
|
693
702
|
lastSyncMetadata,
|
|
694
703
|
seenInstanceId: account.lastSyncMetadata?.instanceId ?? null
|
|
695
704
|
}).command;
|
|
705
|
+
if (eventsToEmit.length) {
|
|
706
|
+
await pushToQueue(
|
|
707
|
+
this.env.QUEUE_BUS_QUEUE,
|
|
708
|
+
eventsToEmit
|
|
709
|
+
);
|
|
710
|
+
}
|
|
696
711
|
let syncStateRows;
|
|
697
712
|
if (createCommands.length) {
|
|
698
713
|
const [updateResult] = await db.batch(
|
|
@@ -708,12 +723,6 @@ class BankSyncAccountPayments extends WorkflowEntrypoint {
|
|
|
708
723
|
instanceId: event.instanceId
|
|
709
724
|
});
|
|
710
725
|
}
|
|
711
|
-
if (eventsToEmit.length) {
|
|
712
|
-
await pushToQueue(
|
|
713
|
-
this.env.QUEUE_BUS_QUEUE,
|
|
714
|
-
eventsToEmit
|
|
715
|
-
);
|
|
716
|
-
}
|
|
717
726
|
return {
|
|
718
727
|
...lastSyncMetadata,
|
|
719
728
|
newLastSyncAt: effectiveWindow.dateTo
|
package/dist/service.d.cts
CHANGED
|
@@ -19,7 +19,6 @@ interface BankServiceVariables {
|
|
|
19
19
|
SYNC_WORKFLOW_RESET_AFTER_ITERATIONS: string;
|
|
20
20
|
CRON_SYNC_WORKFLOW_DISPATCH: string;
|
|
21
21
|
SYNC_DISPATCH_ACCOUNT_IDS: string;
|
|
22
|
-
SYNC_WORKFLOW_MAX_ITERATIONS: string;
|
|
23
22
|
[key: string]: string | number | boolean;
|
|
24
23
|
}
|
|
25
24
|
declare const BANK_SERVICE_BINDINGS: {
|
package/dist/service.d.mts
CHANGED
|
@@ -19,7 +19,6 @@ interface BankServiceVariables {
|
|
|
19
19
|
SYNC_WORKFLOW_RESET_AFTER_ITERATIONS: string;
|
|
20
20
|
CRON_SYNC_WORKFLOW_DISPATCH: string;
|
|
21
21
|
SYNC_DISPATCH_ACCOUNT_IDS: string;
|
|
22
|
-
SYNC_WORKFLOW_MAX_ITERATIONS: string;
|
|
23
22
|
[key: string]: string | number | boolean;
|
|
24
23
|
}
|
|
25
24
|
declare const BANK_SERVICE_BINDINGS: {
|
package/dist/service.d.ts
CHANGED
|
@@ -19,7 +19,6 @@ interface BankServiceVariables {
|
|
|
19
19
|
SYNC_WORKFLOW_RESET_AFTER_ITERATIONS: string;
|
|
20
20
|
CRON_SYNC_WORKFLOW_DISPATCH: string;
|
|
21
21
|
SYNC_DISPATCH_ACCOUNT_IDS: string;
|
|
22
|
-
SYNC_WORKFLOW_MAX_ITERATIONS: string;
|
|
23
22
|
[key: string]: string | number | boolean;
|
|
24
23
|
}
|
|
25
24
|
declare const BANK_SERVICE_BINDINGS: {
|
|
@@ -7,6 +7,7 @@ import 'jose';
|
|
|
7
7
|
import '@develit-io/general-codes';
|
|
8
8
|
import { createHash } from 'node:crypto';
|
|
9
9
|
|
|
10
|
+
const DISPATCH_RECOVERY_WINDOW_MS = 5 * 60 * 1e3;
|
|
10
11
|
function parseInstanceWindow(instanceId, accountId) {
|
|
11
12
|
const prefix = `${accountId}-`;
|
|
12
13
|
if (!instanceId.startsWith(prefix)) return -1;
|
|
@@ -14,6 +15,37 @@ function parseInstanceWindow(instanceId, accountId) {
|
|
|
14
15
|
if (!/^\d+$/.test(suffix)) return -1;
|
|
15
16
|
return Number(suffix);
|
|
16
17
|
}
|
|
18
|
+
function buildDispatchInstanceId(accountId, nowMs, recoveryWindowMs = DISPATCH_RECOVERY_WINDOW_MS) {
|
|
19
|
+
return `${accountId}-${Math.floor(nowMs / recoveryWindowMs)}`;
|
|
20
|
+
}
|
|
21
|
+
function buildDispatchCreateOptions(instanceId, accountId) {
|
|
22
|
+
return {
|
|
23
|
+
id: instanceId,
|
|
24
|
+
params: { accountId },
|
|
25
|
+
// Dispatch mints ~24 instances per account per day and step outputs carry
|
|
26
|
+
// bank payloads — the default 30-day retention would pile up billable
|
|
27
|
+
// storage for state nobody reads after success.
|
|
28
|
+
retention: {
|
|
29
|
+
successRetention: "1 day",
|
|
30
|
+
errorRetention: "7 days"
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function computeWindowDeadline(instanceId, accountId, recoveryWindowMs = DISPATCH_RECOVERY_WINDOW_MS) {
|
|
35
|
+
const window = parseInstanceWindow(instanceId, accountId);
|
|
36
|
+
if (window < 0) return null;
|
|
37
|
+
return (window + 1) * recoveryWindowMs;
|
|
38
|
+
}
|
|
39
|
+
function isAlreadyExists(err) {
|
|
40
|
+
return /already exists/i.test(extractMessage(err));
|
|
41
|
+
}
|
|
42
|
+
function isInstanceNotFound(err) {
|
|
43
|
+
return /instance.*not.?found|not.?found.*instance/i.test(extractMessage(err));
|
|
44
|
+
}
|
|
45
|
+
function extractMessage(err) {
|
|
46
|
+
return err instanceof Error ? err.message : typeof err === "object" && err !== null && "message" in err ? String(err.message) : "";
|
|
47
|
+
}
|
|
48
|
+
|
|
17
49
|
function resolveCurrentSyncInstanceId(accountId, recordedInstanceId) {
|
|
18
50
|
return recordedInstanceId ?? accountId;
|
|
19
51
|
}
|
|
@@ -22,18 +54,6 @@ function isSupersededBy(myInstanceId, recordedInstanceId, accountId) {
|
|
|
22
54
|
return parseInstanceWindow(recordedInstanceId, accountId) > parseInstanceWindow(myInstanceId, accountId);
|
|
23
55
|
}
|
|
24
56
|
|
|
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
|
-
|
|
37
57
|
const createPaymentCommand = (db, { payment }) => {
|
|
38
58
|
return {
|
|
39
59
|
command: db.insert(tables.payment).values({
|
|
@@ -386,4 +406,4 @@ const initiateConnector = async ({
|
|
|
386
406
|
}
|
|
387
407
|
};
|
|
388
408
|
|
|
389
|
-
export { getPaymentRequestsByBatchIdQuery as a, getAccountByIdQuery as b, checksum as c, createCredentialsResolver as d, updatePaymentRequestStatusCommand as e,
|
|
409
|
+
export { DISPATCH_RECOVERY_WINDOW_MS as D, getPaymentRequestsByBatchIdQuery as a, getAccountByIdQuery as b, checksum as c, createCredentialsResolver as d, updatePaymentRequestStatusCommand as e, computeWindowDeadline as f, getBatchByIdQuery as g, isSupersededBy as h, initiateConnector as i, createPaymentCommand as j, isAlreadyExists as k, buildDispatchInstanceId as l, encrypt as m, buildDispatchCreateOptions as n, isInstanceNotFound as o, importAesKey as p, resolveCurrentSyncInstanceId as r, upsertBatchCommand as u };
|
|
@@ -9,6 +9,7 @@ require('jose');
|
|
|
9
9
|
require('@develit-io/general-codes');
|
|
10
10
|
const node_crypto = require('node:crypto');
|
|
11
11
|
|
|
12
|
+
const DISPATCH_RECOVERY_WINDOW_MS = 5 * 60 * 1e3;
|
|
12
13
|
function parseInstanceWindow(instanceId, accountId) {
|
|
13
14
|
const prefix = `${accountId}-`;
|
|
14
15
|
if (!instanceId.startsWith(prefix)) return -1;
|
|
@@ -16,6 +17,37 @@ function parseInstanceWindow(instanceId, accountId) {
|
|
|
16
17
|
if (!/^\d+$/.test(suffix)) return -1;
|
|
17
18
|
return Number(suffix);
|
|
18
19
|
}
|
|
20
|
+
function buildDispatchInstanceId(accountId, nowMs, recoveryWindowMs = DISPATCH_RECOVERY_WINDOW_MS) {
|
|
21
|
+
return `${accountId}-${Math.floor(nowMs / recoveryWindowMs)}`;
|
|
22
|
+
}
|
|
23
|
+
function buildDispatchCreateOptions(instanceId, accountId) {
|
|
24
|
+
return {
|
|
25
|
+
id: instanceId,
|
|
26
|
+
params: { accountId },
|
|
27
|
+
// Dispatch mints ~24 instances per account per day and step outputs carry
|
|
28
|
+
// bank payloads — the default 30-day retention would pile up billable
|
|
29
|
+
// storage for state nobody reads after success.
|
|
30
|
+
retention: {
|
|
31
|
+
successRetention: "1 day",
|
|
32
|
+
errorRetention: "7 days"
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function computeWindowDeadline(instanceId, accountId, recoveryWindowMs = DISPATCH_RECOVERY_WINDOW_MS) {
|
|
37
|
+
const window = parseInstanceWindow(instanceId, accountId);
|
|
38
|
+
if (window < 0) return null;
|
|
39
|
+
return (window + 1) * recoveryWindowMs;
|
|
40
|
+
}
|
|
41
|
+
function isAlreadyExists(err) {
|
|
42
|
+
return /already exists/i.test(extractMessage(err));
|
|
43
|
+
}
|
|
44
|
+
function isInstanceNotFound(err) {
|
|
45
|
+
return /instance.*not.?found|not.?found.*instance/i.test(extractMessage(err));
|
|
46
|
+
}
|
|
47
|
+
function extractMessage(err) {
|
|
48
|
+
return err instanceof Error ? err.message : typeof err === "object" && err !== null && "message" in err ? String(err.message) : "";
|
|
49
|
+
}
|
|
50
|
+
|
|
19
51
|
function resolveCurrentSyncInstanceId(accountId, recordedInstanceId) {
|
|
20
52
|
return recordedInstanceId ?? accountId;
|
|
21
53
|
}
|
|
@@ -24,18 +56,6 @@ function isSupersededBy(myInstanceId, recordedInstanceId, accountId) {
|
|
|
24
56
|
return parseInstanceWindow(recordedInstanceId, accountId) > parseInstanceWindow(myInstanceId, accountId);
|
|
25
57
|
}
|
|
26
58
|
|
|
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
|
-
|
|
39
59
|
const createPaymentCommand = (db, { payment }) => {
|
|
40
60
|
return {
|
|
41
61
|
command: db.insert(paymentDirection.tables.payment).values({
|
|
@@ -388,7 +408,11 @@ const initiateConnector = async ({
|
|
|
388
408
|
}
|
|
389
409
|
};
|
|
390
410
|
|
|
411
|
+
exports.DISPATCH_RECOVERY_WINDOW_MS = DISPATCH_RECOVERY_WINDOW_MS;
|
|
412
|
+
exports.buildDispatchCreateOptions = buildDispatchCreateOptions;
|
|
413
|
+
exports.buildDispatchInstanceId = buildDispatchInstanceId;
|
|
391
414
|
exports.checksum = checksum;
|
|
415
|
+
exports.computeWindowDeadline = computeWindowDeadline;
|
|
392
416
|
exports.createCredentialsResolver = createCredentialsResolver;
|
|
393
417
|
exports.createPaymentCommand = createPaymentCommand;
|
|
394
418
|
exports.encrypt = encrypt;
|
|
@@ -397,9 +421,9 @@ exports.getBatchByIdQuery = getBatchByIdQuery;
|
|
|
397
421
|
exports.getPaymentRequestsByBatchIdQuery = getPaymentRequestsByBatchIdQuery;
|
|
398
422
|
exports.importAesKey = importAesKey;
|
|
399
423
|
exports.initiateConnector = initiateConnector;
|
|
424
|
+
exports.isAlreadyExists = isAlreadyExists;
|
|
425
|
+
exports.isInstanceNotFound = isInstanceNotFound;
|
|
400
426
|
exports.isSupersededBy = isSupersededBy;
|
|
401
|
-
exports.parseIterationBudgetParam = parseIterationBudgetParam;
|
|
402
427
|
exports.resolveCurrentSyncInstanceId = resolveCurrentSyncInstanceId;
|
|
403
|
-
exports.resolveIterationBudget = resolveIterationBudget;
|
|
404
428
|
exports.updatePaymentRequestStatusCommand = updatePaymentRequestStatusCommand;
|
|
405
429
|
exports.upsertBatchCommand = upsertBatchCommand;
|