@wopr-network/platform-core 1.49.3 → 1.49.4

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.
@@ -218,6 +218,59 @@ describe("key-server routes", () => {
218
218
  expect(callCount).toBe(2);
219
219
  expect(body.index).toBe(1); // skipped index 0
220
220
  });
221
+ it("POST /address retries on Drizzle-wrapped collision error (cause.code)", async () => {
222
+ // Drizzle wraps PG errors: err.code is undefined, err.cause.code has "23505"
223
+ const pgError = Object.assign(new Error("unique_violation"), { code: "23505" });
224
+ const drizzleError = Object.assign(new Error("DrizzleQueryError"), { cause: pgError });
225
+ let callCount = 0;
226
+ const mockMethod = {
227
+ id: "eth",
228
+ type: "native",
229
+ token: "ETH",
230
+ chain: "base",
231
+ xpub: "xpub6CUGRUonZSQ4TWtTMmzXdrXDtypWKiKrhko4egpiMZbpiaQL2jkwSB1icqYh2cfDfVxdx4df189oLKnC5fSwqPfgyP3hooxujYzAu3fDVmz",
232
+ nextIndex: 0,
233
+ decimals: 18,
234
+ addressType: "evm",
235
+ confirmations: 1,
236
+ };
237
+ const db = {
238
+ update: vi.fn().mockImplementation(() => ({
239
+ set: vi.fn().mockReturnValue({
240
+ where: vi.fn().mockReturnValue({
241
+ returning: vi.fn().mockImplementation(() => {
242
+ callCount++;
243
+ return Promise.resolve([{ ...mockMethod, nextIndex: callCount }]);
244
+ }),
245
+ }),
246
+ }),
247
+ })),
248
+ insert: vi.fn().mockImplementation(() => ({
249
+ values: vi.fn().mockImplementation(() => {
250
+ if (callCount <= 1)
251
+ throw drizzleError;
252
+ return { onConflictDoNothing: vi.fn().mockResolvedValue({ rowCount: 1 }) };
253
+ }),
254
+ })),
255
+ select: vi.fn().mockReturnValue({
256
+ from: vi.fn().mockReturnValue({ where: vi.fn().mockResolvedValue([]) }),
257
+ }),
258
+ transaction: vi.fn().mockImplementation(async (fn) => fn(db)),
259
+ };
260
+ const deps = mockDeps();
261
+ deps.db = db;
262
+ const app = createKeyServerApp(deps);
263
+ const res = await app.request("/address", {
264
+ method: "POST",
265
+ headers: { "Content-Type": "application/json" },
266
+ body: JSON.stringify({ chain: "eth" }),
267
+ });
268
+ expect(res.status).toBe(201);
269
+ const body = await res.json();
270
+ expect(body.address).toMatch(/^0x/);
271
+ expect(callCount).toBe(2);
272
+ expect(body.index).toBe(1);
273
+ });
221
274
  it("POST /charges creates a charge", async () => {
222
275
  const app = createKeyServerApp(mockDeps());
223
276
  const res = await app.request("/charges", {
@@ -71,7 +71,8 @@ async function deriveNextAddress(db, chainId, tenantId) {
71
71
  return { address, index, chain: method.chain, token: method.token };
72
72
  }
73
73
  catch (err) {
74
- const code = err.code;
74
+ // Drizzle wraps PG errors — check both top-level and cause for the constraint violation code
75
+ const code = err.code ?? err.cause?.code;
75
76
  if (code === "23505" && attempt < maxRetries)
76
77
  continue; // collision — index already advanced, retry
77
78
  throw err;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wopr-network/platform-core",
3
- "version": "1.49.3",
3
+ "version": "1.49.4",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -239,6 +239,64 @@ describe("key-server routes", () => {
239
239
  expect(body.index).toBe(1); // skipped index 0
240
240
  });
241
241
 
242
+ it("POST /address retries on Drizzle-wrapped collision error (cause.code)", async () => {
243
+ // Drizzle wraps PG errors: err.code is undefined, err.cause.code has "23505"
244
+ const pgError = Object.assign(new Error("unique_violation"), { code: "23505" });
245
+ const drizzleError = Object.assign(new Error("DrizzleQueryError"), { cause: pgError });
246
+ let callCount = 0;
247
+
248
+ const mockMethod = {
249
+ id: "eth",
250
+ type: "native",
251
+ token: "ETH",
252
+ chain: "base",
253
+ xpub: "xpub6CUGRUonZSQ4TWtTMmzXdrXDtypWKiKrhko4egpiMZbpiaQL2jkwSB1icqYh2cfDfVxdx4df189oLKnC5fSwqPfgyP3hooxujYzAu3fDVmz",
254
+ nextIndex: 0,
255
+ decimals: 18,
256
+ addressType: "evm",
257
+ confirmations: 1,
258
+ };
259
+
260
+ const db = {
261
+ update: vi.fn().mockImplementation(() => ({
262
+ set: vi.fn().mockReturnValue({
263
+ where: vi.fn().mockReturnValue({
264
+ returning: vi.fn().mockImplementation(() => {
265
+ callCount++;
266
+ return Promise.resolve([{ ...mockMethod, nextIndex: callCount }]);
267
+ }),
268
+ }),
269
+ }),
270
+ })),
271
+ insert: vi.fn().mockImplementation(() => ({
272
+ values: vi.fn().mockImplementation(() => {
273
+ if (callCount <= 1) throw drizzleError;
274
+ return { onConflictDoNothing: vi.fn().mockResolvedValue({ rowCount: 1 }) };
275
+ }),
276
+ })),
277
+ select: vi.fn().mockReturnValue({
278
+ from: vi.fn().mockReturnValue({ where: vi.fn().mockResolvedValue([]) }),
279
+ }),
280
+ transaction: vi.fn().mockImplementation(async (fn: (tx: unknown) => unknown) => fn(db)),
281
+ };
282
+
283
+ const deps = mockDeps();
284
+ (deps as unknown as { db: unknown }).db = db;
285
+ const app = createKeyServerApp(deps);
286
+
287
+ const res = await app.request("/address", {
288
+ method: "POST",
289
+ headers: { "Content-Type": "application/json" },
290
+ body: JSON.stringify({ chain: "eth" }),
291
+ });
292
+
293
+ expect(res.status).toBe(201);
294
+ const body = await res.json();
295
+ expect(body.address).toMatch(/^0x/);
296
+ expect(callCount).toBe(2);
297
+ expect(body.index).toBe(1);
298
+ });
299
+
242
300
  it("POST /charges creates a charge", async () => {
243
301
  const app = createKeyServerApp(mockDeps());
244
302
  const res = await app.request("/charges", {
@@ -98,7 +98,8 @@ async function deriveNextAddress(
98
98
  });
99
99
  return { address, index, chain: method.chain, token: method.token };
100
100
  } catch (err: unknown) {
101
- const code = (err as { code?: string }).code;
101
+ // Drizzle wraps PG errors check both top-level and cause for the constraint violation code
102
+ const code = (err as { code?: string }).code ?? (err as { cause?: { code?: string } }).cause?.code;
102
103
  if (code === "23505" && attempt < maxRetries) continue; // collision — index already advanced, retry
103
104
  throw err;
104
105
  }