@wopr-network/platform-core 1.39.0 → 1.39.1
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/credits/ledger.js
CHANGED
|
@@ -182,8 +182,11 @@ export class DrizzleLedger {
|
|
|
182
182
|
});
|
|
183
183
|
// Phase 1: resolve all account IDs with row locks so concurrent transactions
|
|
184
184
|
// are serialized before any balance check or update.
|
|
185
|
+
// Sort by accountCode to establish a consistent global lock ordering and
|
|
186
|
+
// prevent deadlocks when concurrent transactions lock overlapping accounts.
|
|
187
|
+
const sortedLines = [...input.lines].sort((a, b) => a.accountCode < b.accountCode ? -1 : a.accountCode > b.accountCode ? 1 : 0);
|
|
185
188
|
const resolvedLines = [];
|
|
186
|
-
for (const line of
|
|
189
|
+
for (const line of sortedLines) {
|
|
187
190
|
let accountId;
|
|
188
191
|
if (line.accountCode.startsWith("2000:")) {
|
|
189
192
|
accountId = await this.ensureTenantAccountLocked(tx, line.accountCode.slice(5));
|
|
@@ -111,6 +111,27 @@ describe("DrizzleLedger", () => {
|
|
|
111
111
|
});
|
|
112
112
|
expect(entry.lines).toHaveLength(3);
|
|
113
113
|
});
|
|
114
|
+
it("acquires locks in consistent order regardless of input line order", async () => {
|
|
115
|
+
// Post a 3-line entry with accounts in descending order.
|
|
116
|
+
// If lock ordering works, this should succeed without deadlock
|
|
117
|
+
// even when concurrent transactions use a different line order.
|
|
118
|
+
const entry = await ledger.post({
|
|
119
|
+
entryType: "correction",
|
|
120
|
+
tenantId: "t1",
|
|
121
|
+
lines: [
|
|
122
|
+
{ accountCode: "5070", amount: Credit.fromCents(500), side: "debit" },
|
|
123
|
+
{ accountCode: "2000:t1", amount: Credit.fromCents(200), side: "credit" },
|
|
124
|
+
{ accountCode: "1000", amount: Credit.fromCents(300), side: "credit" },
|
|
125
|
+
],
|
|
126
|
+
});
|
|
127
|
+
// Entry should succeed and contain all 3 lines
|
|
128
|
+
expect(entry.lines).toHaveLength(3);
|
|
129
|
+
// Verify balances are correct (lock order doesn't affect correctness)
|
|
130
|
+
const cashBal = await ledger.accountBalance("1000");
|
|
131
|
+
expect(cashBal.toCentsRounded()).toBe(-300); // credit side on debit-normal = negative
|
|
132
|
+
const tb = await ledger.trialBalance();
|
|
133
|
+
expect(tb.balanced).toBe(true);
|
|
134
|
+
});
|
|
114
135
|
});
|
|
115
136
|
// -----------------------------------------------------------------------
|
|
116
137
|
// credit() — convenience
|
package/package.json
CHANGED
|
@@ -139,6 +139,31 @@ describe("DrizzleLedger", () => {
|
|
|
139
139
|
|
|
140
140
|
expect(entry.lines).toHaveLength(3);
|
|
141
141
|
});
|
|
142
|
+
|
|
143
|
+
it("acquires locks in consistent order regardless of input line order", async () => {
|
|
144
|
+
// Post a 3-line entry with accounts in descending order.
|
|
145
|
+
// If lock ordering works, this should succeed without deadlock
|
|
146
|
+
// even when concurrent transactions use a different line order.
|
|
147
|
+
const entry = await ledger.post({
|
|
148
|
+
entryType: "correction",
|
|
149
|
+
tenantId: "t1",
|
|
150
|
+
lines: [
|
|
151
|
+
{ accountCode: "5070", amount: Credit.fromCents(500), side: "debit" },
|
|
152
|
+
{ accountCode: "2000:t1", amount: Credit.fromCents(200), side: "credit" },
|
|
153
|
+
{ accountCode: "1000", amount: Credit.fromCents(300), side: "credit" },
|
|
154
|
+
],
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Entry should succeed and contain all 3 lines
|
|
158
|
+
expect(entry.lines).toHaveLength(3);
|
|
159
|
+
|
|
160
|
+
// Verify balances are correct (lock order doesn't affect correctness)
|
|
161
|
+
const cashBal = await ledger.accountBalance("1000");
|
|
162
|
+
expect(cashBal.toCentsRounded()).toBe(-300); // credit side on debit-normal = negative
|
|
163
|
+
|
|
164
|
+
const tb = await ledger.trialBalance();
|
|
165
|
+
expect(tb.balanced).toBe(true);
|
|
166
|
+
});
|
|
142
167
|
});
|
|
143
168
|
|
|
144
169
|
// -----------------------------------------------------------------------
|
package/src/credits/ledger.ts
CHANGED
|
@@ -387,8 +387,13 @@ export class DrizzleLedger implements ILedger {
|
|
|
387
387
|
|
|
388
388
|
// Phase 1: resolve all account IDs with row locks so concurrent transactions
|
|
389
389
|
// are serialized before any balance check or update.
|
|
390
|
+
// Sort by accountCode to establish a consistent global lock ordering and
|
|
391
|
+
// prevent deadlocks when concurrent transactions lock overlapping accounts.
|
|
392
|
+
const sortedLines = [...input.lines].sort((a, b) =>
|
|
393
|
+
a.accountCode < b.accountCode ? -1 : a.accountCode > b.accountCode ? 1 : 0,
|
|
394
|
+
);
|
|
390
395
|
const resolvedLines: Array<JournalLine & { accountId: string }> = [];
|
|
391
|
-
for (const line of
|
|
396
|
+
for (const line of sortedLines) {
|
|
392
397
|
let accountId: string;
|
|
393
398
|
if (line.accountCode.startsWith("2000:")) {
|
|
394
399
|
accountId = await this.ensureTenantAccountLocked(tx, line.accountCode.slice(5));
|