ponder 0.14.13 → 0.15.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 (237) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/esm/bin/commands/createViews.js +28 -11
  3. package/dist/esm/bin/commands/createViews.js.map +1 -1
  4. package/dist/esm/bin/commands/dev.js +42 -22
  5. package/dist/esm/bin/commands/dev.js.map +1 -1
  6. package/dist/esm/bin/commands/prune.js +3 -0
  7. package/dist/esm/bin/commands/prune.js.map +1 -1
  8. package/dist/esm/bin/commands/serve.js +4 -1
  9. package/dist/esm/bin/commands/serve.js.map +1 -1
  10. package/dist/esm/bin/commands/start.js +18 -6
  11. package/dist/esm/bin/commands/start.js.map +1 -1
  12. package/dist/esm/bin/isolatedController.js +200 -0
  13. package/dist/esm/bin/isolatedController.js.map +1 -0
  14. package/dist/esm/bin/isolatedWorker.js +146 -0
  15. package/dist/esm/bin/isolatedWorker.js.map +1 -0
  16. package/dist/esm/build/config.js +322 -402
  17. package/dist/esm/build/config.js.map +1 -1
  18. package/dist/esm/build/index.js +8 -11
  19. package/dist/esm/build/index.js.map +1 -1
  20. package/dist/esm/build/pre.js +1 -4
  21. package/dist/esm/build/pre.js.map +1 -1
  22. package/dist/esm/build/schema.js +25 -3
  23. package/dist/esm/build/schema.js.map +1 -1
  24. package/dist/esm/client/index.js +306 -42
  25. package/dist/esm/client/index.js.map +1 -1
  26. package/dist/esm/database/actions.js +264 -104
  27. package/dist/esm/database/actions.js.map +1 -1
  28. package/dist/esm/database/index.js +39 -33
  29. package/dist/esm/database/index.js.map +1 -1
  30. package/dist/esm/database/queryBuilder.js +1 -0
  31. package/dist/esm/database/queryBuilder.js.map +1 -1
  32. package/dist/esm/drizzle/index.js +11 -7
  33. package/dist/esm/drizzle/index.js.map +1 -1
  34. package/dist/esm/drizzle/onchain.js +18 -0
  35. package/dist/esm/drizzle/onchain.js.map +1 -1
  36. package/dist/esm/indexing/client.js +32 -25
  37. package/dist/esm/indexing/client.js.map +1 -1
  38. package/dist/esm/indexing/index.js +110 -178
  39. package/dist/esm/indexing/index.js.map +1 -1
  40. package/dist/esm/indexing/profile.js +1 -1
  41. package/dist/esm/indexing/profile.js.map +1 -1
  42. package/dist/esm/indexing-store/cache.js +196 -274
  43. package/dist/esm/indexing-store/cache.js.map +1 -1
  44. package/dist/esm/indexing-store/historical.js +17 -13
  45. package/dist/esm/indexing-store/historical.js.map +1 -1
  46. package/dist/esm/indexing-store/index.js +10 -1
  47. package/dist/esm/indexing-store/index.js.map +1 -1
  48. package/dist/esm/indexing-store/profile.js +3 -3
  49. package/dist/esm/indexing-store/profile.js.map +1 -1
  50. package/dist/esm/indexing-store/realtime.js +27 -2
  51. package/dist/esm/indexing-store/realtime.js.map +1 -1
  52. package/dist/esm/internal/errors.js +28 -0
  53. package/dist/esm/internal/errors.js.map +1 -1
  54. package/dist/esm/internal/metrics.js +279 -82
  55. package/dist/esm/internal/metrics.js.map +1 -1
  56. package/dist/esm/internal/options.js +1 -0
  57. package/dist/esm/internal/options.js.map +1 -1
  58. package/dist/esm/internal/telemetry.js +1 -1
  59. package/dist/esm/internal/telemetry.js.map +1 -1
  60. package/dist/esm/rpc/http.js +130 -0
  61. package/dist/esm/rpc/http.js.map +1 -0
  62. package/dist/esm/rpc/index.js +38 -7
  63. package/dist/esm/rpc/index.js.map +1 -1
  64. package/dist/esm/runtime/events.js +179 -212
  65. package/dist/esm/runtime/events.js.map +1 -1
  66. package/dist/esm/runtime/filter.js +71 -0
  67. package/dist/esm/runtime/filter.js.map +1 -1
  68. package/dist/esm/runtime/fragments.js +78 -73
  69. package/dist/esm/runtime/fragments.js.map +1 -1
  70. package/dist/esm/runtime/historical.js +306 -130
  71. package/dist/esm/runtime/historical.js.map +1 -1
  72. package/dist/esm/runtime/index.js +183 -58
  73. package/dist/esm/runtime/index.js.map +1 -1
  74. package/dist/esm/runtime/isolated.js +462 -0
  75. package/dist/esm/runtime/isolated.js.map +1 -0
  76. package/dist/esm/runtime/multichain.js +80 -73
  77. package/dist/esm/runtime/multichain.js.map +1 -1
  78. package/dist/esm/runtime/omnichain.js +82 -75
  79. package/dist/esm/runtime/omnichain.js.map +1 -1
  80. package/dist/esm/runtime/realtime.js +198 -66
  81. package/dist/esm/runtime/realtime.js.map +1 -1
  82. package/dist/esm/sync-historical/index.js +416 -457
  83. package/dist/esm/sync-historical/index.js.map +1 -1
  84. package/dist/esm/sync-realtime/bloom.js +3 -3
  85. package/dist/esm/sync-realtime/bloom.js.map +1 -1
  86. package/dist/esm/sync-realtime/index.js +27 -46
  87. package/dist/esm/sync-realtime/index.js.map +1 -1
  88. package/dist/esm/sync-store/index.js +112 -63
  89. package/dist/esm/sync-store/index.js.map +1 -1
  90. package/dist/esm/utils/abi.js +20 -32
  91. package/dist/esm/utils/abi.js.map +1 -1
  92. package/dist/esm/utils/chunk.js +8 -0
  93. package/dist/esm/utils/chunk.js.map +1 -0
  94. package/dist/esm/utils/promiseAllSettledWithThrow.js +19 -0
  95. package/dist/esm/utils/promiseAllSettledWithThrow.js.map +1 -0
  96. package/dist/esm/{client/parse.js → utils/sql-parse.js} +94 -80
  97. package/dist/esm/utils/sql-parse.js.map +1 -0
  98. package/dist/types/bin/commands/createViews.d.ts.map +1 -1
  99. package/dist/types/bin/commands/dev.d.ts.map +1 -1
  100. package/dist/types/bin/commands/prune.d.ts.map +1 -1
  101. package/dist/types/bin/commands/serve.d.ts.map +1 -1
  102. package/dist/types/bin/commands/start.d.ts.map +1 -1
  103. package/dist/types/bin/isolatedController.d.ts +13 -0
  104. package/dist/types/bin/isolatedController.d.ts.map +1 -0
  105. package/dist/types/bin/isolatedWorker.d.ts +9 -0
  106. package/dist/types/bin/isolatedWorker.d.ts.map +1 -0
  107. package/dist/types/build/config.d.ts +29 -11
  108. package/dist/types/build/config.d.ts.map +1 -1
  109. package/dist/types/build/index.d.ts +3 -2
  110. package/dist/types/build/index.d.ts.map +1 -1
  111. package/dist/types/build/pre.d.ts +1 -1
  112. package/dist/types/build/pre.d.ts.map +1 -1
  113. package/dist/types/build/schema.d.ts +5 -3
  114. package/dist/types/build/schema.d.ts.map +1 -1
  115. package/dist/types/client/index.d.ts +1 -1
  116. package/dist/types/client/index.d.ts.map +1 -1
  117. package/dist/types/config/index.d.ts +3 -3
  118. package/dist/types/config/index.d.ts.map +1 -1
  119. package/dist/types/database/actions.d.ts +53 -7
  120. package/dist/types/database/actions.d.ts.map +1 -1
  121. package/dist/types/database/index.d.ts +21 -21
  122. package/dist/types/database/index.d.ts.map +1 -1
  123. package/dist/types/database/queryBuilder.d.ts.map +1 -1
  124. package/dist/types/drizzle/index.d.ts +4 -5
  125. package/dist/types/drizzle/index.d.ts.map +1 -1
  126. package/dist/types/drizzle/onchain.d.ts +6 -0
  127. package/dist/types/drizzle/onchain.d.ts.map +1 -1
  128. package/dist/types/indexing/client.d.ts.map +1 -1
  129. package/dist/types/indexing/index.d.ts +2 -5
  130. package/dist/types/indexing/index.d.ts.map +1 -1
  131. package/dist/types/indexing-store/cache.d.ts +3 -2
  132. package/dist/types/indexing-store/cache.d.ts.map +1 -1
  133. package/dist/types/indexing-store/historical.d.ts +2 -1
  134. package/dist/types/indexing-store/historical.d.ts.map +1 -1
  135. package/dist/types/indexing-store/index.d.ts +1 -0
  136. package/dist/types/indexing-store/index.d.ts.map +1 -1
  137. package/dist/types/indexing-store/realtime.d.ts +2 -1
  138. package/dist/types/indexing-store/realtime.d.ts.map +1 -1
  139. package/dist/types/internal/errors.d.ts +5 -0
  140. package/dist/types/internal/errors.d.ts.map +1 -1
  141. package/dist/types/internal/metrics.d.ts +21 -0
  142. package/dist/types/internal/metrics.d.ts.map +1 -1
  143. package/dist/types/internal/options.d.ts +2 -0
  144. package/dist/types/internal/options.d.ts.map +1 -1
  145. package/dist/types/internal/types.d.ts +66 -58
  146. package/dist/types/internal/types.d.ts.map +1 -1
  147. package/dist/types/rpc/http.d.ts +17 -0
  148. package/dist/types/rpc/http.d.ts.map +1 -0
  149. package/dist/types/rpc/index.d.ts.map +1 -1
  150. package/dist/types/runtime/events.d.ts +4 -4
  151. package/dist/types/runtime/events.d.ts.map +1 -1
  152. package/dist/types/runtime/filter.d.ts +5 -1
  153. package/dist/types/runtime/filter.d.ts.map +1 -1
  154. package/dist/types/runtime/fragments.d.ts +4 -3
  155. package/dist/types/runtime/fragments.d.ts.map +1 -1
  156. package/dist/types/runtime/historical.d.ts +29 -13
  157. package/dist/types/runtime/historical.d.ts.map +1 -1
  158. package/dist/types/runtime/index.d.ts +49 -6
  159. package/dist/types/runtime/index.d.ts.map +1 -1
  160. package/dist/types/runtime/init.d.ts +5 -5
  161. package/dist/types/runtime/init.d.ts.map +1 -1
  162. package/dist/types/runtime/isolated.d.ts +14 -0
  163. package/dist/types/runtime/isolated.d.ts.map +1 -0
  164. package/dist/types/runtime/multichain.d.ts.map +1 -1
  165. package/dist/types/runtime/omnichain.d.ts.map +1 -1
  166. package/dist/types/runtime/realtime.d.ts +21 -10
  167. package/dist/types/runtime/realtime.d.ts.map +1 -1
  168. package/dist/types/sync-historical/index.d.ts +18 -8
  169. package/dist/types/sync-historical/index.d.ts.map +1 -1
  170. package/dist/types/sync-realtime/bloom.d.ts.map +1 -1
  171. package/dist/types/sync-realtime/index.d.ts +2 -2
  172. package/dist/types/sync-realtime/index.d.ts.map +1 -1
  173. package/dist/types/sync-store/index.d.ts +9 -9
  174. package/dist/types/sync-store/index.d.ts.map +1 -1
  175. package/dist/types/utils/abi.d.ts +3 -34
  176. package/dist/types/utils/abi.d.ts.map +1 -1
  177. package/dist/types/utils/chunk.d.ts +2 -0
  178. package/dist/types/utils/chunk.d.ts.map +1 -0
  179. package/dist/types/utils/promiseAllSettledWithThrow.d.ts +8 -0
  180. package/dist/types/utils/promiseAllSettledWithThrow.d.ts.map +1 -0
  181. package/dist/types/utils/sql-parse.d.ts +21 -0
  182. package/dist/types/utils/sql-parse.d.ts.map +1 -0
  183. package/package.json +2 -2
  184. package/src/bin/commands/createViews.ts +35 -15
  185. package/src/bin/commands/dev.ts +43 -21
  186. package/src/bin/commands/prune.ts +6 -0
  187. package/src/bin/commands/serve.ts +4 -1
  188. package/src/bin/commands/start.ts +20 -5
  189. package/src/bin/isolatedController.ts +300 -0
  190. package/src/bin/isolatedWorker.ts +192 -0
  191. package/src/build/config.ts +570 -632
  192. package/src/build/index.ts +14 -14
  193. package/src/build/pre.ts +1 -4
  194. package/src/build/schema.ts +49 -4
  195. package/src/client/index.ts +386 -48
  196. package/src/config/index.ts +3 -3
  197. package/src/database/actions.ts +469 -120
  198. package/src/database/index.ts +85 -58
  199. package/src/database/queryBuilder.ts +1 -0
  200. package/src/drizzle/index.ts +15 -7
  201. package/src/drizzle/onchain.ts +19 -0
  202. package/src/indexing/client.ts +38 -25
  203. package/src/indexing/index.ts +137 -230
  204. package/src/indexing/profile.ts +1 -1
  205. package/src/indexing-store/cache.ts +285 -414
  206. package/src/indexing-store/historical.ts +20 -10
  207. package/src/indexing-store/index.ts +16 -0
  208. package/src/indexing-store/profile.ts +3 -3
  209. package/src/indexing-store/realtime.ts +28 -0
  210. package/src/internal/errors.ts +26 -0
  211. package/src/internal/metrics.ts +341 -111
  212. package/src/internal/options.ts +4 -0
  213. package/src/internal/telemetry.ts +1 -1
  214. package/src/internal/types.ts +70 -87
  215. package/src/rpc/http.ts +164 -0
  216. package/src/rpc/index.ts +39 -7
  217. package/src/runtime/events.ts +195 -240
  218. package/src/runtime/filter.ts +85 -1
  219. package/src/runtime/fragments.ts +109 -113
  220. package/src/runtime/historical.ts +467 -189
  221. package/src/runtime/index.ts +337 -69
  222. package/src/runtime/init.ts +5 -5
  223. package/src/runtime/isolated.ts +768 -0
  224. package/src/runtime/multichain.ts +137 -102
  225. package/src/runtime/omnichain.ts +138 -106
  226. package/src/runtime/realtime.ts +322 -123
  227. package/src/sync-historical/index.ts +556 -692
  228. package/src/sync-realtime/bloom.ts +7 -3
  229. package/src/sync-realtime/index.ts +31 -46
  230. package/src/sync-store/index.ts +189 -95
  231. package/src/utils/abi.ts +33 -90
  232. package/src/utils/chunk.ts +7 -0
  233. package/src/utils/promiseAllSettledWithThrow.ts +27 -0
  234. package/src/{client/parse.ts → utils/sql-parse.ts} +100 -90
  235. package/dist/esm/client/parse.js.map +0 -1
  236. package/dist/types/client/parse.d.ts +0 -14
  237. package/dist/types/client/parse.d.ts.map +0 -1
@@ -1,12 +1,11 @@
1
1
  import type { QB } from "@/database/queryBuilder.js";
2
- import { getPrimaryKeyColumns } from "@/drizzle/index.js";
2
+ import { getPartitionName, getPrimaryKeyColumns } from "@/drizzle/index.js";
3
3
  import { getColumnCasing } from "@/drizzle/kit/index.js";
4
4
  import { addErrorMeta, toErrorMeta } from "@/indexing/index.js";
5
5
  import type { Common } from "@/internal/common.js";
6
6
  import {
7
7
  CopyFlushError,
8
8
  DelayedInsertError,
9
- RetryableError,
10
9
  ShutdownError,
11
10
  } from "@/internal/errors.js";
12
11
  import type {
@@ -16,6 +15,7 @@ import type {
16
15
  } from "@/internal/types.js";
17
16
  import { dedupe } from "@/utils/dedupe.js";
18
17
  import { prettyPrint } from "@/utils/print.js";
18
+ import { promiseAllSettledWithThrow } from "@/utils/promiseAllSettledWithThrow.js";
19
19
  import { startClock } from "@/utils/timer.js";
20
20
  import {
21
21
  type Table,
@@ -274,14 +274,12 @@ export const getCopyText = (table: Table, rows: Row[]) => {
274
274
  return result;
275
275
  };
276
276
 
277
- export const getCopyHelper = (qb: QB) => {
277
+ export const getCopyHelper = (qb: QB, chainId?: number) => {
278
278
  if (qb.$dialect === "pglite") {
279
279
  return async (table: Table, text: string, includeSchema = true) => {
280
280
  const target = includeSchema
281
- ? `"${getTableConfig(table).schema ?? "public"}"."${getTableName(
282
- table,
283
- )}"`
284
- : `"${getTableName(table)}"`;
281
+ ? `"${getTableConfig(table).schema ?? "public"}"."${chainId === undefined ? getTableName(table) : getPartitionName(table, chainId)}"`
282
+ : `"${chainId === undefined ? getTableName(table) : getPartitionName(table, chainId)}"`;
285
283
  await qb.$client
286
284
  .query(`COPY ${target} FROM '/dev/blob'`, [], {
287
285
  blob: new Blob([text]),
@@ -295,10 +293,8 @@ export const getCopyHelper = (qb: QB) => {
295
293
  } else {
296
294
  return async (table: Table, text: string, includeSchema = true) => {
297
295
  const target = includeSchema
298
- ? `"${getTableConfig(table).schema ?? "public"}"."${getTableName(
299
- table,
300
- )}"`
301
- : `"${getTableName(table)}"`;
296
+ ? `"${getTableConfig(table).schema ?? "public"}"."${chainId === undefined ? getTableName(table) : getPartitionName(table, chainId)}"`
297
+ : `"${chainId === undefined ? getTableName(table) : getPartitionName(table, chainId)}"`;
302
298
  const copyStream = qb.$client.query(
303
299
  copy.from(`COPY ${target} FROM STDIN`),
304
300
  );
@@ -348,11 +344,13 @@ export const createIndexingCache = ({
348
344
  schemaBuild: { schema },
349
345
  crashRecoveryCheckpoint,
350
346
  eventCount,
347
+ chainId,
351
348
  }: {
352
349
  common: Common;
353
350
  schemaBuild: Pick<SchemaBuild, "schema">;
354
351
  crashRecoveryCheckpoint: CrashRecoveryCheckpoint;
355
352
  eventCount: { [eventName: string]: number };
353
+ chainId?: number;
356
354
  }): IndexingCache => {
357
355
  let event: Event | undefined;
358
356
  let qb: QB = undefined!;
@@ -363,8 +361,6 @@ export const createIndexingCache = ({
363
361
  /** Profiling data about access patterns for each event. */
364
362
  const profile: Profile = new Map();
365
363
 
366
- let isFlushRetry = false;
367
-
368
364
  const tables = Object.values(schema).filter(isTable);
369
365
  const primaryKeyCache = getPrimaryKeyCache(tables);
370
366
 
@@ -393,11 +389,14 @@ export const createIndexingCache = ({
393
389
  );
394
390
  },
395
391
  async get({ table, key }) {
396
- if (event && eventCount[event.name]! % SAMPLING_RATE === 1) {
397
- if (profile.has(event.name) === false) {
398
- profile.set(event.name, new Map());
392
+ if (
393
+ event &&
394
+ eventCount[event.eventCallback.name]! % SAMPLING_RATE === 1
395
+ ) {
396
+ if (profile.has(event.eventCallback.name) === false) {
397
+ profile.set(event.eventCallback.name, new Map());
399
398
  for (const table of tables) {
400
- profile.get(event.name)!.set(table, new Map());
399
+ profile.get(event.eventCallback.name)!.set(table, new Map());
401
400
  }
402
401
  }
403
402
 
@@ -405,18 +404,19 @@ export const createIndexingCache = ({
405
404
  event,
406
405
  table,
407
406
  key,
408
- Array.from(profile.get(event.name)!.get(table)!.values()).map(
409
- ({ pattern }) => pattern,
410
- ),
407
+ Array.from(
408
+ profile.get(event.eventCallback.name)!.get(table)!.values(),
409
+ ).map(({ pattern }) => pattern),
411
410
  primaryKeyCache,
412
411
  );
413
412
  if (pattern) {
414
413
  const key = getProfilePatternKey(pattern);
415
- if (profile.get(event.name)!.get(table)!.has(key)) {
416
- profile.get(event.name)!.get(table)!.get(key)!.count++;
414
+ if (profile.get(event.eventCallback.name)!.get(table)!.has(key)) {
415
+ profile.get(event.eventCallback.name)!.get(table)!.get(key)!
416
+ .count++;
417
417
  } else {
418
418
  profile
419
- .get(event.name)!
419
+ .get(event.eventCallback.name)!
420
420
  .get(table)!
421
421
  .set(key, { pattern, count: 1 });
422
422
  }
@@ -530,150 +530,86 @@ export const createIndexingCache = ({
530
530
  };
531
531
  const flushEndClock = startClock();
532
532
 
533
- const copy = getCopyHelper(qb);
534
-
535
- // Note `isFlushRetry` is true when the previous flush failed. When `isFlushRetry` is false, this
536
- // function takes an optimized fast path, with support for small batch sizes. PGlite always takes
537
- // the fast path because it doesn't support delayed insert errors.
538
-
539
- if (isFlushRetry && qb.$dialect === "postgres") {
540
- for (const table of cache.keys()) {
541
- const shouldRecordBytes = cache.get(table)!.isCacheComplete;
542
- if (
543
- tableNames !== undefined &&
544
- tableNames.has(getTableName(table)) === false
545
- ) {
546
- continue;
547
- }
548
-
549
- const tableCache = cache.get(table)!;
550
-
551
- const insertValues = Array.from(insertBuffer.get(table)!.values());
552
- const updateValues = Array.from(updateBuffer.get(table)!.values());
553
-
554
- if (insertValues.length > 0) {
555
- const endClock = startClock();
533
+ const copy = getCopyHelper(qb, chainId);
556
534
 
557
- await qb.wrap((db) => db.execute("SAVEPOINT flush"), context);
535
+ const flushTable = async (table: Table) => {
536
+ const shouldRecordBytes = cache.get(table)!.isCacheComplete;
537
+ if (
538
+ tableNames !== undefined &&
539
+ tableNames.has(getTableName(table)) === false
540
+ ) {
541
+ return;
542
+ }
558
543
 
559
- try {
560
- const text = getCopyText(
561
- table,
562
- insertValues.map(({ row }) => row),
563
- );
544
+ const tableCache = cache.get(table)!;
564
545
 
565
- await new Promise(setImmediate);
566
-
567
- await copy(table, text);
568
- } catch (_error) {
569
- let error = _error as Error;
570
- const result = await recoverBatchError(
571
- insertValues,
572
- async (values) => {
573
- await qb.wrap(
574
- (db) => db.execute("ROLLBACK to flush"),
575
- context,
576
- );
577
- const text = getCopyText(
578
- table,
579
- values.map(({ row }) => row),
580
- );
581
- await copy(table, text);
582
-
583
- await qb.wrap((db) => db.execute("SAVEPOINT flush"), context);
584
- },
585
- );
546
+ const target =
547
+ chainId === undefined
548
+ ? getTableName(table)
549
+ : getPartitionName(table, chainId);
586
550
 
587
- if (result.status === "error") {
588
- error = new DelayedInsertError(result.error.message);
589
- error.stack = undefined;
551
+ const insertValues = Array.from(insertBuffer.get(table)!.values());
552
+ const updateValues = Array.from(updateBuffer.get(table)!.values());
590
553
 
591
- addErrorMeta(
592
- error,
593
- `db.insert arguments:\n${prettyPrint(result.value.row)}`,
594
- );
554
+ if (insertValues.length > 0) {
555
+ const endClock = startClock();
595
556
 
596
- if (result.value.metadata.event) {
597
- addErrorMeta(error, toErrorMeta(result.value.metadata.event));
598
-
599
- common.logger.warn({
600
- msg: "Failed to write cached database rows",
601
- event: result.value.metadata.event.name,
602
- type: "insert",
603
- table: getTableName(table),
604
- row_count: insertValues.length,
605
- duration: endClock(),
606
- error,
607
- });
608
- } else {
609
- common.logger.warn({
610
- msg: "Failed to write cached database rows",
611
- type: "insert",
612
- table: getTableName(table),
613
- row_count: insertValues.length,
614
- duration: endClock(),
615
- error,
616
- });
617
- }
557
+ if (insertValues.length > LOW_BATCH_THRESHOLD) {
558
+ const text = getCopyText(
559
+ table,
560
+ insertValues.map(({ row }) => row),
561
+ );
618
562
 
619
- // @ts-ignore remove meta from error
620
- error.meta = undefined;
621
- } else {
622
- error.stack = undefined;
563
+ await new Promise(setImmediate);
623
564
 
624
- common.logger.warn({
625
- msg: "Failed to write cached database rows",
626
- type: "insert",
627
- table: getTableName(table),
628
- row_count: insertValues.length,
629
- duration: endClock(),
630
- error,
631
- });
632
- }
565
+ await copy(table, text);
566
+ } else {
567
+ await qb.wrap(
568
+ (db) =>
569
+ db.insert(table).values(insertValues.map(({ row }) => row)),
570
+ context,
571
+ );
572
+ }
633
573
 
634
- throw error;
574
+ common.metrics.ponder_indexing_cache_query_duration.observe(
575
+ {
576
+ table: getTableName(table),
577
+ method: "flush",
578
+ },
579
+ endClock(),
580
+ );
581
+
582
+ let bytes = 0;
583
+ for (const [key, entry] of insertBuffer.get(table)!) {
584
+ if (shouldRecordBytes && tableCache.cache.has(key) === false) {
585
+ bytes += getBytes(entry.row) + getBytes(key);
635
586
  }
587
+ tableCache.cache.set(key, entry.row);
588
+ }
589
+ tableCache.bytes += bytes;
590
+ insertBuffer.get(table)!.clear();
636
591
 
637
- common.metrics.ponder_indexing_cache_query_duration.observe(
638
- {
639
- table: getTableName(table),
640
- method: "flush",
641
- },
642
- endClock(),
643
- );
592
+ await new Promise(setImmediate);
593
+ }
644
594
 
645
- let bytes = 0;
646
- for (const [key, entry] of insertBuffer.get(table)!) {
647
- if (shouldRecordBytes && tableCache.cache.has(key) === false) {
648
- bytes += getBytes(entry.row) + getBytes(key);
649
- }
650
- tableCache.cache.set(key, entry.row);
651
- }
652
- tableCache.bytes += bytes;
653
- insertBuffer.get(table)!.clear();
595
+ if (updateValues.length > 0) {
596
+ const primaryKeys = getPrimaryKeyColumns(table);
654
597
 
655
- await new Promise(setImmediate);
656
- }
598
+ const endClock = startClock();
657
599
 
658
- if (updateValues.length > 0) {
600
+ if (updateValues.length > LOW_BATCH_THRESHOLD) {
659
601
  // Steps for flushing "update" entries:
660
602
  // 1. Create temp table
661
603
  // 2. Copy into temp table
662
604
  // 3. Update target table with data from temp
663
605
 
664
- const primaryKeys = getPrimaryKeyColumns(table);
665
-
666
606
  const createTempTableQuery = `
667
- CREATE TEMP TABLE IF NOT EXISTS "${getTableName(table)}"
668
- AS SELECT * FROM "${
669
- getTableConfig(table).schema ?? "public"
670
- }"."${getTableName(table)}"
671
- WITH NO DATA;
672
- `;
607
+ CREATE TEMP TABLE IF NOT EXISTS "${target}"
608
+ AS SELECT * FROM "${getTableConfig(table).schema ?? "public"}"."${target}"
609
+ WITH NO DATA;`;
610
+
673
611
  const updateQuery = `
674
- UPDATE "${
675
- getTableConfig(table).schema ?? "public"
676
- }"."${getTableName(table)}" as target
612
+ UPDATE "${getTableConfig(table).schema ?? "public"}"."${target}" as target
677
613
  SET ${Object.values(getTableColumns(table))
678
614
  .map(
679
615
  (column) =>
@@ -683,321 +619,255 @@ export const createIndexingCache = ({
683
619
  )}" = source."${getColumnCasing(column, "snake_case")}"`,
684
620
  )
685
621
  .join(",\n")}
686
- FROM "${getTableName(table)}" source
622
+ FROM "${target}" source
687
623
  WHERE ${primaryKeys
688
624
  .map(({ sql }) => `target."${sql}" = source."${sql}"`)
689
- .join(" AND ")};
690
- `;
691
-
692
- const endClock = startClock();
625
+ .join(" AND ")};`;
693
626
 
694
627
  await qb.wrap((db) => db.execute(createTempTableQuery), context);
695
- await qb.wrap((db) => db.execute("SAVEPOINT flush"), context);
696
-
697
- try {
698
- const text = getCopyText(
699
- table,
700
- updateValues.map(({ row }) => row),
701
- );
702
-
703
- await new Promise(setImmediate);
704
-
705
- await copy(table, text, false);
706
- } catch (_error) {
707
- let error = _error as Error;
708
- const result = await recoverBatchError(
709
- updateValues,
710
- async (values) => {
711
- await qb.wrap(
712
- (db) => db.execute("ROLLBACK to flush"),
713
- context,
714
- );
715
- const text = getCopyText(
716
- table,
717
- values.map(({ row }) => row),
718
- );
719
- await copy(table, text, false);
720
-
721
- await qb.wrap((db) => db.execute("SAVEPOINT flush"), context);
722
- },
723
- );
724
-
725
- if (result.status === "error") {
726
- error = new DelayedInsertError(result.error.message);
727
- error.stack = undefined;
728
-
729
- addErrorMeta(
730
- error,
731
- `db.update arguments:\n${prettyPrint(result.value.row)}`,
732
- );
733
-
734
- if (result.value.metadata.event) {
735
- addErrorMeta(error, toErrorMeta(result.value.metadata.event));
736
-
737
- common.logger.warn({
738
- msg: "Failed to write cached database rows",
739
- event: result.value.metadata.event.name,
740
- type: "update",
741
- table: getTableName(table),
742
- row_count: updateValues.length,
743
- duration: endClock(),
744
- error,
745
- });
746
- } else {
747
- common.logger.warn({
748
- msg: "Failed to write cached database rows",
749
- type: "update",
750
- table: getTableName(table),
751
- row_count: updateValues.length,
752
- duration: endClock(),
753
- error,
754
- });
755
- }
756
-
757
- // @ts-ignore remove meta from error
758
- error.meta = undefined;
759
- } else {
760
- error.stack = undefined;
761
-
762
- common.logger.warn({
763
- msg: "Failed to write cached database rows",
764
- type: "update",
765
- table: getTableName(table),
766
- row_count: updateValues.length,
767
- duration: endClock(),
768
- error,
769
- });
770
- }
771
628
 
772
- throw error;
773
- }
629
+ const text = getCopyText(
630
+ table,
631
+ updateValues.map(({ row }) => row),
632
+ );
633
+ await new Promise(setImmediate);
774
634
 
635
+ await copy(table, text, false);
775
636
  await qb.wrap((db) => db.execute(updateQuery), context);
776
637
  await qb.wrap(
777
- (db) => db.execute(`TRUNCATE TABLE "${getTableName(table)}"`),
638
+ (db) => db.execute(`TRUNCATE TABLE "${target}"`),
778
639
  context,
779
640
  );
780
-
781
- common.metrics.ponder_indexing_cache_query_duration.observe(
782
- {
783
- table: getTableName(table),
784
- method: "flush",
785
- },
786
- endClock(),
641
+ } else {
642
+ await qb.wrap(
643
+ (db) =>
644
+ db
645
+ .insert(table)
646
+ .values(updateValues.map(({ row }) => row))
647
+ .onConflictDoUpdate({
648
+ // @ts-ignore
649
+ target: primaryKeys.map(({ js }) => table[js]!),
650
+ set: Object.fromEntries(
651
+ Object.entries(getTableColumns(table)).map(
652
+ ([columnName, column]) => [
653
+ columnName,
654
+ sql.raw(
655
+ `excluded."${getColumnCasing(column, "snake_case")}"`,
656
+ ),
657
+ ],
658
+ ),
659
+ ),
660
+ }),
661
+ context,
787
662
  );
663
+ }
788
664
 
789
- let bytes = 0;
790
- for (const [key, entry] of updateBuffer.get(table)!) {
791
- if (shouldRecordBytes && tableCache.cache.has(key) === false) {
792
- bytes += getBytes(entry.row) + getBytes(key);
793
- }
794
- tableCache.cache.set(key, entry.row);
665
+ common.metrics.ponder_indexing_cache_query_duration.observe(
666
+ {
667
+ table: getTableName(table),
668
+ method: "flush",
669
+ },
670
+ endClock(),
671
+ );
672
+
673
+ let bytes = 0;
674
+ for (const [key, entry] of updateBuffer.get(table)!) {
675
+ if (shouldRecordBytes && tableCache.cache.has(key) === false) {
676
+ bytes += getBytes(entry.row) + getBytes(key);
795
677
  }
796
- tableCache.bytes += bytes;
797
- updateBuffer.get(table)!.clear();
798
-
799
- await new Promise(setImmediate);
678
+ tableCache.cache.set(key, entry.row);
800
679
  }
680
+ tableCache.bytes += bytes;
681
+ updateBuffer.get(table)!.clear();
801
682
 
802
- if (insertValues.length > 0 || updateValues.length > 0) {
803
- common.logger.debug({
804
- msg: "Wrote database rows",
805
- table: getTableName(table),
806
- row_count: insertValues.length + updateValues.length,
807
- duration: flushEndClock(),
808
- });
683
+ await new Promise(setImmediate);
684
+ }
809
685
 
810
- await qb.wrap((db) => db.execute("RELEASE flush"), context);
686
+ if (insertValues.length > 0 || updateValues.length > 0) {
687
+ common.logger.debug({
688
+ msg: "Wrote database rows",
689
+ table: target,
690
+ row_count: insertValues.length + updateValues.length,
691
+ duration: flushEndClock(),
692
+ });
693
+ }
694
+ };
695
+
696
+ try {
697
+ if (qb.$dialect === "postgres") {
698
+ await qb.wrap((db) => db.execute("SAVEPOINT flush"), context);
699
+
700
+ await promiseAllSettledWithThrow(
701
+ Array.from(cache.keys()).map(flushTable),
702
+ );
703
+
704
+ await qb.wrap((db) => db.execute("RELEASE flush"), context);
705
+ } else {
706
+ // Note: pglite must run sequentially
707
+ for (const table of cache.keys()) {
708
+ await flushTable(table);
811
709
  }
812
710
  }
813
- } else {
814
- isFlushRetry = true;
815
-
816
- // Note: Must use `Promise.allSettled` to avoid short-circuiting while queries are running.
817
-
818
- const results = await Promise.allSettled(
819
- Array.from(cache.keys()).map(async (table) => {
820
- const shouldRecordBytes = cache.get(table)!.isCacheComplete;
821
- if (
822
- tableNames !== undefined &&
823
- tableNames.has(getTableName(table)) === false
824
- ) {
825
- return;
826
- }
711
+ } catch (_error) {
712
+ let error = _error as Error;
713
+ if (error instanceof ShutdownError || qb.$dialect === "pglite") {
714
+ throw error;
715
+ }
716
+
717
+ // Note `isFlushRetry` is true when the previous flush failed. When `isFlushRetry` is false, this
718
+ // function takes an optimized fast path, with support for small batch sizes. PGlite always takes
719
+ // the fast path because it doesn't support delayed insert errors.
720
+
721
+ await qb.wrap((db) => db.execute("ROLLBACK to flush"), context);
722
+
723
+ for (const table of cache.keys()) {
724
+ if (
725
+ tableNames !== undefined &&
726
+ tableNames.has(getTableName(table)) === false
727
+ ) {
728
+ continue;
729
+ }
827
730
 
828
- const tableCache = cache.get(table)!;
731
+ const target =
732
+ chainId === undefined
733
+ ? getTableName(table)
734
+ : getPartitionName(table, chainId);
735
+
736
+ const insertValues = Array.from(insertBuffer.get(table)!.values());
737
+ const updateValues = Array.from(updateBuffer.get(table)!.values());
829
738
 
830
- const insertValues = Array.from(insertBuffer.get(table)!.values());
831
- const updateValues = Array.from(updateBuffer.get(table)!.values());
739
+ if (insertValues.length > 0) {
740
+ const endClock = startClock();
832
741
 
833
- if (insertValues.length > 0) {
834
- const endClock = startClock();
742
+ await qb.wrap((db) => db.execute("SAVEPOINT flush"), context);
835
743
 
836
- if (insertValues.length > LOW_BATCH_THRESHOLD) {
744
+ const result = await recoverBatchError(
745
+ insertValues,
746
+ async (values) => {
747
+ await qb.wrap((db) => db.execute("ROLLBACK to flush"), context);
837
748
  const text = getCopyText(
838
749
  table,
839
- insertValues.map(({ row }) => row),
750
+ values.map(({ row }) => row),
840
751
  );
752
+ await copy(table, text);
841
753
 
842
- await new Promise(setImmediate);
754
+ await qb.wrap((db) => db.execute("SAVEPOINT flush"), context);
755
+ },
756
+ );
843
757
 
844
- await copy(table, text);
845
- } else {
846
- await qb.wrap(
847
- (db) =>
848
- db.insert(table).values(insertValues.map(({ row }) => row)),
849
- context,
850
- );
851
- }
758
+ if (result.status === "error") {
759
+ error = new DelayedInsertError(result.error.message);
760
+ error.stack = undefined;
852
761
 
853
- common.metrics.ponder_indexing_cache_query_duration.observe(
854
- {
855
- table: getTableName(table),
856
- method: "flush",
857
- },
858
- endClock(),
762
+ addErrorMeta(
763
+ error,
764
+ `db.insert arguments:\n${prettyPrint(result.value.row)}`,
859
765
  );
860
766
 
861
- let bytes = 0;
862
- for (const [key, entry] of insertBuffer.get(table)!) {
863
- if (shouldRecordBytes && tableCache.cache.has(key) === false) {
864
- bytes += getBytes(entry.row) + getBytes(key);
865
- }
866
- tableCache.cache.set(key, entry.row);
767
+ if (result.value.metadata.event) {
768
+ addErrorMeta(error, toErrorMeta(result.value.metadata.event));
769
+
770
+ common.logger.warn({
771
+ msg: "Failed to write cached database rows",
772
+ event: result.value.metadata.event.eventCallback.name,
773
+ type: "insert",
774
+ table: getTableName(table),
775
+ row_count: insertValues.length,
776
+ duration: endClock(),
777
+ error,
778
+ });
779
+ } else {
780
+ common.logger.warn({
781
+ msg: "Failed to write cached database rows",
782
+ type: "insert",
783
+ table: getTableName(table),
784
+ row_count: insertValues.length,
785
+ duration: endClock(),
786
+ error,
787
+ });
867
788
  }
868
- tableCache.bytes += bytes;
869
- insertBuffer.get(table)!.clear();
870
789
 
871
- await new Promise(setImmediate);
790
+ throw error;
872
791
  }
792
+ }
873
793
 
874
- if (updateValues.length > 0) {
875
- const primaryKeys = getPrimaryKeyColumns(table);
876
-
877
- const endClock = startClock();
878
-
879
- if (updateValues.length > LOW_BATCH_THRESHOLD) {
880
- // Steps for flushing "update" entries:
881
- // 1. Create temp table
882
- // 2. Copy into temp table
883
- // 3. Update target table with data from temp
884
-
885
- const createTempTableQuery = `
886
- CREATE TEMP TABLE IF NOT EXISTS "${getTableName(table)}"
887
- AS SELECT * FROM "${
888
- getTableConfig(table).schema ?? "public"
889
- }"."${getTableName(table)}"
890
- WITH NO DATA;
891
- `;
892
-
893
- const updateQuery = `
894
- UPDATE "${
895
- getTableConfig(table).schema ?? "public"
896
- }"."${getTableName(table)}" as target
897
- SET ${Object.values(getTableColumns(table))
898
- .map(
899
- (column) =>
900
- `"${getColumnCasing(
901
- column,
902
- "snake_case",
903
- )}" = source."${getColumnCasing(column, "snake_case")}"`,
904
- )
905
- .join(",\n")}
906
- FROM "${getTableName(table)}" source
907
- WHERE ${primaryKeys
908
- .map(({ sql }) => `target."${sql}" = source."${sql}"`)
909
- .join(" AND ")};
910
- `;
911
-
912
- await qb.wrap(
913
- (db) => db.execute(createTempTableQuery),
914
- context,
915
- );
794
+ if (updateValues.length > 0) {
795
+ // Steps for flushing "update" entries:
796
+ // 1. Create temp table
797
+ // 2. Copy into temp table
798
+ // 3. Update target table with data from temp
799
+
800
+ const createTempTableQuery = `
801
+ CREATE TEMP TABLE IF NOT EXISTS "${target}"
802
+ AS SELECT * FROM "${getTableConfig(table).schema ?? "public"}"."${target}"
803
+ WITH NO DATA;
804
+ `;
916
805
 
806
+ const endClock = startClock();
807
+
808
+ await qb.wrap((db) => db.execute(createTempTableQuery), context);
809
+ await qb.wrap((db) => db.execute("SAVEPOINT flush"), context);
810
+
811
+ const result = await recoverBatchError(
812
+ updateValues,
813
+ async (values) => {
814
+ await qb.wrap((db) => db.execute("ROLLBACK to flush"), context);
917
815
  const text = getCopyText(
918
816
  table,
919
- updateValues.map(({ row }) => row),
817
+ values.map(({ row }) => row),
920
818
  );
921
-
922
- await new Promise(setImmediate);
923
-
924
819
  await copy(table, text, false);
925
820
 
926
- await qb.wrap((db) => db.execute(updateQuery), context);
821
+ await qb.wrap((db) => db.execute("SAVEPOINT flush"), context);
822
+ },
823
+ );
927
824
 
928
- await qb.wrap(
929
- (db) => db.execute(`TRUNCATE TABLE "${getTableName(table)}"`),
930
- context,
931
- );
932
- } else {
933
- await qb.wrap(
934
- (db) =>
935
- db
936
- .insert(table)
937
- .values(updateValues.map(({ row }) => row))
938
- .onConflictDoUpdate({
939
- // @ts-ignore
940
- target: primaryKeys.map(({ js }) => table[js]!),
941
- set: Object.fromEntries(
942
- Object.entries(getTableColumns(table)).map(
943
- ([columnName, column]) => [
944
- columnName,
945
- sql.raw(
946
- `excluded."${getColumnCasing(column, "snake_case")}"`,
947
- ),
948
- ],
949
- ),
950
- ),
951
- }),
952
- context,
953
- );
954
- }
825
+ await qb.wrap(
826
+ (db) => db.execute(`TRUNCATE TABLE "${target}"`),
827
+ context,
828
+ );
955
829
 
956
- common.metrics.ponder_indexing_cache_query_duration.observe(
957
- {
958
- table: getTableName(table),
959
- method: "flush",
960
- },
961
- endClock(),
830
+ if (result.status === "error") {
831
+ error = new DelayedInsertError(result.error.message);
832
+ error.stack = undefined;
833
+
834
+ addErrorMeta(
835
+ error,
836
+ `db.update arguments:\n${prettyPrint(result.value.row)}`,
962
837
  );
963
838
 
964
- let bytes = 0;
965
- for (const [key, entry] of updateBuffer.get(table)!) {
966
- if (shouldRecordBytes && tableCache.cache.has(key) === false) {
967
- bytes += getBytes(entry.row) + getBytes(key);
968
- }
969
- tableCache.cache.set(key, entry.row);
970
- }
971
- tableCache.bytes += bytes;
972
- updateBuffer.get(table)!.clear();
839
+ if (result.value.metadata.event) {
840
+ addErrorMeta(error, toErrorMeta(result.value.metadata.event));
973
841
 
974
- await new Promise(setImmediate);
975
- }
842
+ common.logger.warn({
843
+ msg: "Failed to write cached database rows",
844
+ event: result.value.metadata.event.eventCallback.name,
845
+ type: "update",
846
+ table: getTableName(table),
847
+ row_count: updateValues.length,
848
+ duration: endClock(),
849
+ error,
850
+ });
851
+ } else {
852
+ common.logger.warn({
853
+ msg: "Failed to write cached database rows",
854
+ type: "update",
855
+ table: getTableName(table),
856
+ row_count: updateValues.length,
857
+ duration: endClock(),
858
+ error,
859
+ });
860
+ }
976
861
 
977
- if (insertValues.length > 0 || updateValues.length > 0) {
978
- common.logger.debug({
979
- msg: "Wrote database rows",
980
- table: getTableName(table),
981
- row_count: insertValues.length + updateValues.length,
982
- duration: flushEndClock(),
983
- });
862
+ throw error;
984
863
  }
985
- }),
986
- );
987
-
988
- if (results.some((result) => result.status === "rejected")) {
989
- const rejected = results.find(
990
- (result): result is PromiseRejectedResult =>
991
- result.status === "rejected",
992
- )!;
993
- if (rejected.reason instanceof ShutdownError) {
994
- throw rejected.reason;
995
864
  }
996
- throw new RetryableError(rejected.reason.message);
997
865
  }
998
- }
999
866
 
1000
- isFlushRetry = false;
867
+ // Note: if we weren't able to find the exact row that caused the error,
868
+ // throw the original error.
869
+ throw error;
870
+ }
1001
871
  },
1002
872
  async prefetch({ events }) {
1003
873
  const context = {
@@ -1050,14 +920,15 @@ export const createIndexingCache = ({
1050
920
  }
1051
921
 
1052
922
  for (const event of events) {
1053
- if (profile.has(event.name)) {
923
+ if (profile.has(event.eventCallback.name)) {
1054
924
  for (const table of tables) {
1055
925
  if (cache.get(table)!.isCacheComplete) continue;
1056
926
  for (const [, { count, pattern }] of profile
1057
- .get(event.name)!
927
+ .get(event.eventCallback.name)!
1058
928
  .get(table)!) {
1059
929
  // Expected value of times the prediction will be used.
1060
- const ev = (count * SAMPLING_RATE) / eventCount[event.name]!;
930
+ const ev =
931
+ (count * SAMPLING_RATE) / eventCount[event.eventCallback.name]!;
1061
932
  if (ev > PREDICTION_THRESHOLD) {
1062
933
  const row = recoverProfilePattern(pattern, event);
1063
934
  const key = getCacheKey(table, row, primaryKeyCache);