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,6 +1,19 @@
1
- import { getPrimaryKeyColumns } from "@/drizzle/index.js";
2
- import { getTableNames } from "@/drizzle/index.js";
1
+ import {
2
+ getPartitionName,
3
+ getPrimaryKeyColumns,
4
+ getReorgProcedureName,
5
+ getReorgTableName,
6
+ getReorgTriggerName,
7
+ } from "@/drizzle/index.js";
3
8
  import { getColumnCasing, getReorgTable } from "@/drizzle/kit/index.js";
9
+ import {
10
+ getLiveQueryChannelName,
11
+ getLiveQueryNotifyProcedureName,
12
+ getLiveQueryNotifyTriggerName,
13
+ getLiveQueryProcedureName,
14
+ getLiveQueryTriggerName,
15
+ getViewsLiveQueryNotifyTriggerName,
16
+ } from "@/drizzle/onchain.js";
4
17
  import type { Logger } from "@/internal/logger.js";
5
18
  import type {
6
19
  NamespaceBuild,
@@ -9,12 +22,16 @@ import type {
9
22
  } from "@/internal/types.js";
10
23
  import { MAX_CHECKPOINT_STRING, decodeCheckpoint } from "@/utils/checkpoint.js";
11
24
  import {
25
+ type SQL,
12
26
  type Table,
13
27
  type View,
28
+ and,
14
29
  eq,
15
30
  getTableColumns,
16
31
  getTableName,
17
32
  getViewName,
33
+ lte,
34
+ sql,
18
35
  } from "drizzle-orm";
19
36
  import { getTableConfig } from "drizzle-orm/pg-core";
20
37
  import { getPonderCheckpointTable } from "./index.js";
@@ -41,7 +58,7 @@ export const createIndexes = async (
41
58
 
42
59
  export const createTriggers = async (
43
60
  qb: QB,
44
- { tables }: { tables: Table[] },
61
+ { tables, chainId }: { tables: Table[]; chainId?: number },
45
62
  context?: { logger?: Logger },
46
63
  ) => {
47
64
  await qb.transaction(
@@ -58,17 +75,17 @@ export const createTriggers = async (
58
75
  await tx.wrap({ label: "create_trigger" }, (tx) =>
59
76
  tx.execute(
60
77
  `
61
- CREATE OR REPLACE FUNCTION "${schema}".${getTableNames(table).triggerFn}
78
+ CREATE OR REPLACE FUNCTION "${schema}".${getReorgProcedureName(table)}
62
79
  RETURNS TRIGGER AS $$
63
80
  BEGIN
64
81
  IF TG_OP = 'INSERT' THEN
65
- INSERT INTO "${schema}"."${getTableName(getReorgTable(table))}" (${columnNames.join(",")}, operation, checkpoint)
82
+ INSERT INTO "${schema}"."${getReorgTableName(table)}" (${columnNames.join(",")}, operation, checkpoint)
66
83
  VALUES (${columnNames.map((name) => `NEW.${name}`).join(",")}, 0, '${MAX_CHECKPOINT_STRING}');
67
84
  ELSIF TG_OP = 'UPDATE' THEN
68
- INSERT INTO "${schema}"."${getTableName(getReorgTable(table))}" (${columnNames.join(",")}, operation, checkpoint)
85
+ INSERT INTO "${schema}"."${getReorgTableName(table)}" (${columnNames.join(",")}, operation, checkpoint)
69
86
  VALUES (${columnNames.map((name) => `OLD.${name}`).join(",")}, 1, '${MAX_CHECKPOINT_STRING}');
70
87
  ELSIF TG_OP = 'DELETE' THEN
71
- INSERT INTO "${schema}"."${getTableName(getReorgTable(table))}" (${columnNames.join(",")}, operation, checkpoint)
88
+ INSERT INTO "${schema}"."${getReorgTableName(table)}" (${columnNames.join(",")}, operation, checkpoint)
72
89
  VALUES (${columnNames.map((name) => `OLD.${name}`).join(",")}, 2, '${MAX_CHECKPOINT_STRING}');
73
90
  END IF;
74
91
  RETURN NULL;
@@ -80,9 +97,9 @@ export const createTriggers = async (
80
97
  await tx.wrap({ label: "create_trigger" }, (tx) =>
81
98
  tx.execute(
82
99
  `
83
- CREATE OR REPLACE TRIGGER "${getTableNames(table).trigger}"
84
- AFTER INSERT OR UPDATE OR DELETE ON "${schema}"."${getTableName(table)}"
85
- FOR EACH ROW EXECUTE FUNCTION "${schema}".${getTableNames(table).triggerFn};
100
+ CREATE OR REPLACE TRIGGER "${getReorgTriggerName()}"
101
+ AFTER INSERT OR UPDATE OR DELETE ON "${schema}"."${chainId === undefined ? getTableName(table) : getPartitionName(table, chainId)}"
102
+ FOR EACH ROW EXECUTE FUNCTION "${schema}".${getReorgProcedureName(table)};
86
103
  `,
87
104
  ),
88
105
  );
@@ -96,7 +113,7 @@ export const createTriggers = async (
96
113
 
97
114
  export const dropTriggers = async (
98
115
  qb: QB,
99
- { tables }: { tables: Table[] },
116
+ { tables, chainId }: { tables: Table[]; chainId?: number },
100
117
  context?: { logger?: Logger },
101
118
  ) => {
102
119
  await qb.transaction(
@@ -107,7 +124,7 @@ export const dropTriggers = async (
107
124
 
108
125
  await tx.wrap({ label: "drop_trigger" }, (tx) =>
109
126
  tx.execute(
110
- `DROP TRIGGER IF EXISTS "${getTableNames(table).trigger}" ON "${schema}"."${getTableName(table)}"`,
127
+ `DROP TRIGGER IF EXISTS "${getReorgTriggerName()}" ON "${schema}"."${chainId === undefined ? getTableName(table) : getPartitionName(table, chainId)}"`,
111
128
  ),
112
129
  );
113
130
  }),
@@ -118,6 +135,155 @@ export const dropTriggers = async (
118
135
  );
119
136
  };
120
137
 
138
+ export const createLiveQueryTriggers = async (
139
+ qb: QB,
140
+ {
141
+ namespaceBuild,
142
+ tables,
143
+ chainId,
144
+ }: { namespaceBuild: NamespaceBuild; tables: Table[]; chainId?: number },
145
+ context?: { logger?: Logger },
146
+ ) => {
147
+ await qb.transaction(
148
+ async (tx) => {
149
+ const notifyProcedure = getLiveQueryNotifyProcedureName();
150
+ const notifyTrigger = getLiveQueryNotifyTriggerName();
151
+
152
+ await tx.wrap((tx) =>
153
+ tx.execute(
154
+ `
155
+ CREATE OR REPLACE TRIGGER "${notifyTrigger}"
156
+ AFTER INSERT OR UPDATE OR DELETE
157
+ ON "${namespaceBuild.schema}"._ponder_checkpoint
158
+ FOR EACH STATEMENT
159
+ EXECUTE PROCEDURE "${namespaceBuild.schema}".${notifyProcedure};`,
160
+ ),
161
+ );
162
+
163
+ const trigger = getLiveQueryTriggerName();
164
+ const procedure = getLiveQueryProcedureName();
165
+
166
+ for (const table of tables) {
167
+ const schema = getTableConfig(table).schema ?? "public";
168
+
169
+ await tx.wrap((tx) =>
170
+ tx.execute(
171
+ `
172
+ CREATE OR REPLACE TRIGGER "${trigger}"
173
+ AFTER INSERT OR UPDATE OR DELETE
174
+ ON "${schema}"."${chainId === undefined ? getTableName(table) : getPartitionName(table, chainId)}"
175
+ FOR EACH STATEMENT
176
+ EXECUTE PROCEDURE "${schema}".${procedure};`,
177
+ ),
178
+ );
179
+ }
180
+ },
181
+ undefined,
182
+ context,
183
+ );
184
+ };
185
+
186
+ export const dropLiveQueryTriggers = async (
187
+ qb: QB,
188
+ {
189
+ namespaceBuild,
190
+ tables,
191
+ chainId,
192
+ }: { namespaceBuild: NamespaceBuild; tables: Table[]; chainId?: number },
193
+ context?: { logger?: Logger },
194
+ ) => {
195
+ await qb.transaction(
196
+ async (tx) => {
197
+ const notifyTrigger = getLiveQueryNotifyTriggerName();
198
+ await tx.wrap((tx) =>
199
+ tx.execute(
200
+ `DROP TRIGGER IF EXISTS "${notifyTrigger}" ON "${namespaceBuild.schema}"._ponder_checkpoint;`,
201
+ ),
202
+ );
203
+
204
+ const trigger = getLiveQueryTriggerName();
205
+ for (const table of tables) {
206
+ const schema = getTableConfig(table).schema ?? "public";
207
+
208
+ await tx.wrap((tx) =>
209
+ tx.execute(
210
+ `DROP TRIGGER IF EXISTS "${trigger}" ON "${schema}"."${chainId === undefined ? getTableName(table) : getPartitionName(table, chainId)}";`,
211
+ ),
212
+ );
213
+ }
214
+ },
215
+ undefined,
216
+ context,
217
+ );
218
+ };
219
+
220
+ export const createLiveQueryProcedures = async (
221
+ qb: QB,
222
+ { namespaceBuild }: { namespaceBuild: NamespaceBuild },
223
+ context?: { logger?: Logger },
224
+ ) => {
225
+ await qb.transaction(
226
+ async (tx) => {
227
+ const schema = namespaceBuild.schema;
228
+ const procedure = getLiveQueryProcedureName();
229
+
230
+ await tx.wrap(
231
+ (tx) =>
232
+ tx.execute(
233
+ `
234
+ CREATE OR REPLACE FUNCTION "${schema}".${procedure}
235
+ RETURNS TRIGGER LANGUAGE plpgsql
236
+ AS $$
237
+ BEGIN
238
+ INSERT INTO live_query_tables (table_name)
239
+ VALUES (TG_TABLE_NAME)
240
+ ON CONFLICT (table_name) DO NOTHING;
241
+ RETURN NULL;
242
+ END;
243
+ $$;`,
244
+ ),
245
+ context,
246
+ );
247
+
248
+ const notifyProcedure = getLiveQueryNotifyProcedureName();
249
+ const channel = getLiveQueryChannelName(namespaceBuild.schema);
250
+
251
+ await tx.wrap(
252
+ (tx) =>
253
+ tx.execute(`
254
+ CREATE OR REPLACE FUNCTION "${schema}".${notifyProcedure}
255
+ RETURNS TRIGGER LANGUAGE plpgsql
256
+ AS $$
257
+ DECLARE
258
+ table_names json;
259
+ table_exists boolean := false;
260
+ BEGIN
261
+ SELECT EXISTS (
262
+ SELECT 1
263
+ FROM information_schema.tables
264
+ WHERE table_name = 'live_query_tables'
265
+ AND table_type = 'LOCAL TEMPORARY'
266
+ ) INTO table_exists;
267
+
268
+ IF table_exists THEN
269
+ SELECT json_agg(table_name) INTO table_names
270
+ FROM live_query_tables;
271
+
272
+ table_names := COALESCE(table_names, '[]'::json);
273
+ PERFORM pg_notify('${channel}', table_names::text);
274
+ END IF;
275
+
276
+ RETURN NULL;
277
+ END;
278
+ $$;`),
279
+ context,
280
+ );
281
+ },
282
+ undefined,
283
+ context,
284
+ );
285
+ };
286
+
121
287
  export const createViews = async (
122
288
  qb: QB,
123
289
  {
@@ -191,30 +357,51 @@ export const createViews = async (
191
357
  ),
192
358
  );
193
359
 
194
- const trigger = `status_${namespaceBuild.viewsSchema}_trigger`;
195
- const notification = "status_notify()";
196
- const channel = `${namespaceBuild.viewsSchema}_status_channel`;
360
+ const notifyProcedure = getLiveQueryNotifyProcedureName();
361
+ const channel = getLiveQueryChannelName(namespaceBuild.viewsSchema!);
197
362
 
198
363
  await tx.wrap((tx) =>
199
364
  tx.execute(`
200
- CREATE OR REPLACE FUNCTION "${namespaceBuild.viewsSchema}".${notification}
201
- RETURNS TRIGGER
202
- LANGUAGE plpgsql
365
+ CREATE OR REPLACE FUNCTION "${namespaceBuild.viewsSchema}".${notifyProcedure}
366
+ RETURNS TRIGGER LANGUAGE plpgsql
203
367
  AS $$
204
- BEGIN
205
- NOTIFY "${channel}";
206
- RETURN NULL;
207
- END;
368
+ DECLARE
369
+ table_names json;
370
+ table_exists boolean := false;
371
+ BEGIN
372
+ SELECT EXISTS (
373
+ SELECT 1
374
+ FROM information_schema.tables
375
+ WHERE table_name = 'live_query_tables'
376
+ AND table_type = 'LOCAL TEMPORARY'
377
+ ) INTO table_exists;
378
+
379
+ IF table_exists THEN
380
+ SELECT json_agg(table_name) INTO table_names
381
+ FROM live_query_tables;
382
+
383
+ table_names := COALESCE(table_names, '[]'::json);
384
+ PERFORM pg_notify('${channel}', table_names::text);
385
+ END IF;
386
+
387
+ RETURN NULL;
388
+ END;
208
389
  $$;`),
209
390
  );
210
391
 
392
+ const trigger = getViewsLiveQueryNotifyTriggerName(
393
+ namespaceBuild.viewsSchema,
394
+ );
395
+
211
396
  await tx.wrap((tx) =>
212
- tx.execute(`
397
+ tx.execute(
398
+ `
213
399
  CREATE OR REPLACE TRIGGER "${trigger}"
214
400
  AFTER INSERT OR UPDATE OR DELETE
215
- ON "${namespaceBuild.schema}"._ponder_checkpoint
401
+ ON "${namespaceBuild.schema!}"._ponder_checkpoint
216
402
  FOR EACH STATEMENT
217
- EXECUTE PROCEDURE "${namespaceBuild.viewsSchema}".${notification};`),
403
+ EXECUTE PROCEDURE "${namespaceBuild.viewsSchema}".${notifyProcedure};`,
404
+ ),
218
405
  );
219
406
  },
220
407
  undefined,
@@ -222,16 +409,65 @@ EXECUTE PROCEDURE "${namespaceBuild.viewsSchema}".${notification};`),
222
409
  );
223
410
  };
224
411
 
225
- export const revert = async (
412
+ export const revertOmnichain = async (
413
+ qb: QB,
414
+ {
415
+ checkpoint,
416
+ tables,
417
+ }: {
418
+ checkpoint: string;
419
+ tables: Table[];
420
+ },
421
+ context?: { logger?: Logger },
422
+ ): Promise<number[]> => {
423
+ if (tables.length === 0) return [];
424
+
425
+ return qb.transaction(
426
+ { label: "revert" },
427
+ async (tx) => {
428
+ const counts: number[] = [];
429
+
430
+ for (const table of tables) {
431
+ const primaryKeyColumns = getPrimaryKeyColumns(table);
432
+ const schema = getTableConfig(table).schema ?? "public";
433
+
434
+ const result = await tx.wrap((tx) =>
435
+ tx.execute(`
436
+ WITH reverted1 AS (
437
+ DELETE FROM "${schema}"."${getReorgTableName(table)}"
438
+ WHERE checkpoint > '${checkpoint}' RETURNING *
439
+ ), reverted2 AS (
440
+ SELECT ${primaryKeyColumns.map(({ sql }) => `"${sql}"`).join(", ")}, MIN(operation_id) AS operation_id FROM reverted1
441
+ GROUP BY ${primaryKeyColumns.map(({ sql }) => `"${sql}"`).join(", ")}
442
+ ), reverted3 AS (
443
+ SELECT ${Object.values(getTableColumns(table))
444
+ .map((column) => `reverted1."${getColumnCasing(column, "snake_case")}"`)
445
+ .join(", ")}, reverted1.operation FROM reverted2
446
+ INNER JOIN reverted1
447
+ ON ${primaryKeyColumns.map(({ sql }) => `reverted2."${sql}" = reverted1."${sql}"`).join("AND ")}
448
+ AND reverted2.operation_id = reverted1.operation_id
449
+ ), ${getRevertSql({ table })};`),
450
+ );
451
+
452
+ // @ts-ignore
453
+ counts.push(result.rows[0]!.count);
454
+ }
455
+
456
+ return counts;
457
+ },
458
+ undefined,
459
+ context,
460
+ );
461
+ };
462
+
463
+ export const revertMultichain = async (
226
464
  qb: QB,
227
465
  {
228
466
  checkpoint,
229
467
  tables,
230
- preBuild,
231
468
  }: {
232
469
  checkpoint: string;
233
470
  tables: Table[];
234
- preBuild: Pick<PreBuild, "ordering">;
235
471
  },
236
472
  context?: { logger?: Logger },
237
473
  ): Promise<number[]> => {
@@ -241,33 +477,33 @@ export const revert = async (
241
477
  { label: "revert" },
242
478
  async (tx) => {
243
479
  const counts: number[] = [];
244
- if (preBuild.ordering === "multichain") {
245
- const minOperationId = await tx
246
- .wrap((tx) =>
247
- tx.execute(`
480
+
481
+ const minOperationId = await tx
482
+ .wrap((tx) =>
483
+ tx.execute(`
248
484
  SELECT MIN(operation_id) AS operation_id FROM (
249
485
  ${tables
250
486
  .map(
251
487
  (table) => `
252
- SELECT MIN(operation_id) AS operation_id FROM "${getTableConfig(table).schema ?? "public"}"."${getTableName(getReorgTable(table))}"
488
+ SELECT MIN(operation_id) AS operation_id FROM "${getTableConfig(table).schema ?? "public"}"."${getReorgTableName(table)}"
253
489
  WHERE SUBSTRING(checkpoint, 11, 16)::numeric = ${String(decodeCheckpoint(checkpoint).chainId)}
254
490
  AND checkpoint > '${checkpoint}'`,
255
491
  )
256
492
  .join(" UNION ALL ")}) AS all_mins;`),
257
- )
258
- .then((result) => {
259
- // @ts-ignore
260
- return result.rows[0]?.operation_id as string | null;
261
- });
262
-
263
- for (const table of tables) {
264
- const primaryKeyColumns = getPrimaryKeyColumns(table);
265
- const schema = getTableConfig(table).schema ?? "public";
493
+ )
494
+ .then((result) => {
495
+ // @ts-ignore
496
+ return result.rows[0]?.operation_id as string | null;
497
+ });
266
498
 
267
- const result = await tx.wrap((tx) =>
268
- tx.execute(`
499
+ for (const table of tables) {
500
+ const primaryKeyColumns = getPrimaryKeyColumns(table);
501
+ const schema = getTableConfig(table).schema ?? "public";
502
+
503
+ const result = await tx.wrap((tx) =>
504
+ tx.execute(`
269
505
  WITH reverted1 AS (
270
- DELETE FROM "${schema}"."${getTableName(getReorgTable(table))}"
506
+ DELETE FROM "${schema}"."${getReorgTableName(table)}"
271
507
  WHERE ${minOperationId!} IS NOT NULL AND operation_id >= ${minOperationId!}
272
508
  RETURNING *
273
509
  ), reverted2 AS (
@@ -281,21 +517,46 @@ WITH reverted1 AS (
281
517
  ON ${primaryKeyColumns.map(({ sql }) => `reverted2."${sql}" = reverted1."${sql}"`).join("AND ")}
282
518
  AND reverted2.operation_id = reverted1.operation_id
283
519
  ), ${getRevertSql({ table })};`),
284
- );
520
+ );
285
521
 
286
- // @ts-ignore
287
- counts.push(result.rows[0]!.count);
288
- }
289
- } else {
290
- for (const table of tables) {
291
- const primaryKeyColumns = getPrimaryKeyColumns(table);
292
- const schema = getTableConfig(table).schema ?? "public";
522
+ // @ts-ignore
523
+ counts.push(result.rows[0]!.count);
524
+ }
525
+
526
+ return counts;
527
+ },
528
+ undefined,
529
+ context,
530
+ );
531
+ };
532
+
533
+ export const revertIsolated = async (
534
+ qb: QB,
535
+ {
536
+ checkpoint,
537
+ tables,
538
+ }: {
539
+ checkpoint: string;
540
+ tables: Table[];
541
+ },
542
+ context?: { logger?: Logger },
543
+ ) => {
544
+ if (tables.length === 0) return [];
545
+
546
+ return qb.transaction(
547
+ { label: "revert" },
548
+ async (tx) => {
549
+ const counts: number[] = [];
550
+
551
+ for (const table of tables) {
552
+ const primaryKeyColumns = getPrimaryKeyColumns(table);
553
+ const schema = getTableConfig(table).schema ?? "public";
293
554
 
294
- const result = await tx.wrap((tx) =>
295
- tx.execute(`
555
+ const result = await tx.wrap((tx) =>
556
+ tx.execute(`
296
557
  WITH reverted1 AS (
297
- DELETE FROM "${schema}"."${getTableName(getReorgTable(table))}"
298
- WHERE checkpoint > '${checkpoint}' RETURNING *
558
+ DELETE FROM "${schema}"."${getReorgTableName(table)}"
559
+ WHERE checkpoint > '${checkpoint}' AND SUBSTRING(checkpoint, 11, 16)::numeric = ${String(decodeCheckpoint(checkpoint).chainId)} RETURNING *
299
560
  ), reverted2 AS (
300
561
  SELECT ${primaryKeyColumns.map(({ sql }) => `"${sql}"`).join(", ")}, MIN(operation_id) AS operation_id FROM reverted1
301
562
  GROUP BY ${primaryKeyColumns.map(({ sql }) => `"${sql}"`).join(", ")}
@@ -307,11 +568,10 @@ WITH reverted1 AS (
307
568
  ON ${primaryKeyColumns.map(({ sql }) => `reverted2."${sql}" = reverted1."${sql}"`).join("AND ")}
308
569
  AND reverted2.operation_id = reverted1.operation_id
309
570
  ), ${getRevertSql({ table })};`),
310
- );
571
+ );
311
572
 
312
- // @ts-ignore
313
- counts.push(result.rows[0]!.count);
314
- }
573
+ // @ts-ignore
574
+ counts.push(result.rows[0]!.count);
315
575
  }
316
576
 
317
577
  return counts;
@@ -321,23 +581,22 @@ WITH reverted1 AS (
321
581
  );
322
582
  };
323
583
 
324
- export const finalize = async (
584
+ export const finalizeOmnichain = async (
325
585
  qb: QB,
326
586
  {
327
587
  checkpoint,
328
588
  tables,
329
- preBuild,
330
589
  namespaceBuild,
331
590
  }: {
332
591
  checkpoint: string;
333
592
  tables: Table[];
334
- preBuild: Pick<PreBuild, "ordering">;
335
593
  namespaceBuild: NamespaceBuild;
336
594
  },
337
595
  context?: { logger?: Logger },
338
- ): Promise<void> => {
596
+ ) => {
339
597
  const PONDER_CHECKPOINT = getPonderCheckpointTable(namespaceBuild.schema);
340
598
 
599
+ // TODO(kyle) is this breaking an invariant?
341
600
  if (tables.length === 0) {
342
601
  await qb.wrap(
343
602
  (db) =>
@@ -349,27 +608,76 @@ export const finalize = async (
349
608
  return;
350
609
  }
351
610
 
352
- // NOTE: It is invariant that PONDER_CHECKPOINT has a value for each chain.
353
-
354
611
  return qb.transaction(
355
612
  { label: "finalize" },
356
613
  async (tx) => {
357
- if (preBuild.ordering === "multichain") {
614
+ await tx.wrap((tx) =>
615
+ tx.update(PONDER_CHECKPOINT).set({
616
+ finalizedCheckpoint: checkpoint,
617
+ safeCheckpoint: checkpoint,
618
+ }),
619
+ );
620
+
621
+ for (const table of tables) {
358
622
  await tx.wrap((tx) =>
359
623
  tx
360
- .update(PONDER_CHECKPOINT)
361
- .set({ finalizedCheckpoint: checkpoint })
362
- .where(
363
- eq(
364
- PONDER_CHECKPOINT.chainId,
365
- Number(decodeCheckpoint(checkpoint).chainId),
366
- ),
367
- ),
624
+ .delete(getReorgTable(table))
625
+ .where(lte(getReorgTable(table).checkpoint, checkpoint)),
368
626
  );
627
+ }
628
+ },
629
+ undefined,
630
+ context,
631
+ );
632
+ };
369
633
 
370
- const minOperationId = await tx
371
- .wrap((tx) =>
372
- tx.execute(`
634
+ export const finalizeMultichain = async (
635
+ qb: QB,
636
+ {
637
+ checkpoint,
638
+ tables,
639
+ namespaceBuild,
640
+ }: {
641
+ checkpoint: string;
642
+ tables: Table[];
643
+ namespaceBuild: NamespaceBuild;
644
+ },
645
+ context?: { logger?: Logger },
646
+ ) => {
647
+ const PONDER_CHECKPOINT = getPonderCheckpointTable(namespaceBuild.schema);
648
+
649
+ // TODO(kyle) is this breaking an invariant?
650
+ if (tables.length === 0) {
651
+ await qb.wrap(
652
+ (db) =>
653
+ db
654
+ .update(PONDER_CHECKPOINT)
655
+ .set({ finalizedCheckpoint: checkpoint, safeCheckpoint: checkpoint }),
656
+ context,
657
+ );
658
+ return;
659
+ }
660
+
661
+ // NOTE: It is invariant that PONDER_CHECKPOINT has a value for each chain.
662
+
663
+ return qb.transaction(
664
+ { label: "finalize" },
665
+ async (tx) => {
666
+ await tx.wrap((tx) =>
667
+ tx
668
+ .update(PONDER_CHECKPOINT)
669
+ .set({ finalizedCheckpoint: checkpoint })
670
+ .where(
671
+ eq(
672
+ PONDER_CHECKPOINT.chainId,
673
+ Number(decodeCheckpoint(checkpoint).chainId),
674
+ ),
675
+ ),
676
+ );
677
+
678
+ const minOperationId = await tx
679
+ .wrap((tx) =>
680
+ tx.execute(`
373
681
  SELECT MIN(operation_id) AS operation_id FROM (
374
682
  ${tables
375
683
  .map(
@@ -382,14 +690,14 @@ WHERE checkpoint > (
382
690
  )`,
383
691
  )
384
692
  .join(" UNION ALL ")}) AS all_mins;`),
385
- )
386
- .then((result) => {
387
- // @ts-ignore
388
- return result.rows[0]?.operation_id as string | null;
389
- });
693
+ )
694
+ .then((result) => {
695
+ // @ts-ignore
696
+ return result.rows[0]?.operation_id as string | null;
697
+ });
390
698
 
391
- const result = await tx.wrap((tx) =>
392
- tx.execute(`
699
+ const result = await tx.wrap((tx) =>
700
+ tx.execute(`
393
701
  WITH ${tables
394
702
  .map(
395
703
  (table, index) => `
@@ -405,39 +713,18 @@ WHERE checkpoint > (
405
713
  .map((_, index) => `SELECT checkpoint FROM deleted_${index}`)
406
714
  .join(" UNION ALL ")}
407
715
  )
408
- SELECT MAX(checkpoint) as safe_checkpoint, SUBSTRING(checkpoint, 11, 16)::numeric as chain_id, COUNT(*) AS deleted_count
716
+ SELECT MAX(checkpoint) as safe_checkpoint, SUBSTRING(checkpoint, 11, 16)::numeric as chain_id
409
717
  FROM all_deleted
410
718
  GROUP BY SUBSTRING(checkpoint, 11, 16)::numeric;`),
411
- );
719
+ );
412
720
 
413
- for (const { chain_id, safe_checkpoint } of result.rows) {
414
- await tx.wrap((tx) =>
415
- tx
416
- .update(PONDER_CHECKPOINT)
417
- .set({ safeCheckpoint: safe_checkpoint as string })
418
- .where(eq(PONDER_CHECKPOINT.chainId, chain_id as number)),
419
- );
420
- }
421
- } else {
721
+ for (const { chain_id, safe_checkpoint } of result.rows) {
422
722
  await tx.wrap((tx) =>
423
- tx.update(PONDER_CHECKPOINT).set({
424
- finalizedCheckpoint: checkpoint,
425
- safeCheckpoint: checkpoint,
426
- }),
723
+ tx
724
+ .update(PONDER_CHECKPOINT)
725
+ .set({ safeCheckpoint: safe_checkpoint as string })
726
+ .where(eq(PONDER_CHECKPOINT.chainId, chain_id as number)),
427
727
  );
428
-
429
- for (const table of tables) {
430
- await tx
431
- .wrap((tx) =>
432
- tx.execute(`
433
- WITH deleted AS (
434
- DELETE FROM "${getTableConfig(table).schema ?? "public"}"."${getTableName(getReorgTable(table))}"
435
- WHERE checkpoint <= '${checkpoint}'
436
- RETURNING *
437
- ) SELECT COUNT(*) AS deleted_count FROM deleted;`),
438
- )
439
- .then((result) => Number(result.rows[0]!.deleted_count));
440
- }
441
728
  }
442
729
  },
443
730
  undefined,
@@ -445,19 +732,81 @@ WITH deleted AS (
445
732
  );
446
733
  };
447
734
 
735
+ export const finalizeIsolated = async (
736
+ qb: QB,
737
+ {
738
+ checkpoint,
739
+ tables,
740
+ namespaceBuild,
741
+ }: {
742
+ checkpoint: string;
743
+ tables: Table[];
744
+ namespaceBuild: NamespaceBuild;
745
+ },
746
+ context?: { logger?: Logger },
747
+ ) => {
748
+ const PONDER_CHECKPOINT = getPonderCheckpointTable(namespaceBuild.schema);
749
+ const chainId = Number(decodeCheckpoint(checkpoint).chainId);
750
+
751
+ if (tables.length === 0) {
752
+ await qb.wrap(
753
+ (db) =>
754
+ db
755
+ .update(PONDER_CHECKPOINT)
756
+ .set({ finalizedCheckpoint: checkpoint, safeCheckpoint: checkpoint })
757
+ .where(eq(PONDER_CHECKPOINT.chainId, chainId)),
758
+ context,
759
+ );
760
+ return;
761
+ }
762
+ return qb.transaction({ label: "finalize" }, async (tx) => {
763
+ await tx.wrap((tx) =>
764
+ tx
765
+ .update(PONDER_CHECKPOINT)
766
+ .set({ finalizedCheckpoint: checkpoint, safeCheckpoint: checkpoint })
767
+ .where(eq(PONDER_CHECKPOINT.chainId, chainId)),
768
+ );
769
+
770
+ for (const table of tables) {
771
+ await tx.wrap((tx) =>
772
+ tx
773
+ .delete(getReorgTable(table))
774
+ .where(
775
+ and(
776
+ lte(getReorgTable(table).checkpoint, checkpoint),
777
+ eq(sql`chain_id`, chainId),
778
+ ),
779
+ ),
780
+ );
781
+ }
782
+ });
783
+ };
784
+
448
785
  export const commitBlock = async (
449
786
  qb: QB,
450
- { checkpoint, table }: { checkpoint: string; table: Table },
787
+ {
788
+ checkpoint,
789
+ table,
790
+ preBuild,
791
+ }: { checkpoint: string; table: Table; preBuild: Pick<PreBuild, "ordering"> },
451
792
  context?: { logger?: Logger },
452
793
  ) => {
453
794
  const reorgTable = getReorgTable(table);
795
+ let whereClause: SQL;
796
+ if (preBuild.ordering === "experimental_isolated") {
797
+ // Note: Query must include `chain_id` because it's possible for multiple chains to be indexing in parallel.
798
+ const chainId = Number(decodeCheckpoint(checkpoint).chainId);
799
+ whereClause = and(
800
+ eq(reorgTable.checkpoint, MAX_CHECKPOINT_STRING),
801
+ eq(sql`chain_id`, chainId),
802
+ )!;
803
+ } else {
804
+ whereClause = eq(reorgTable.checkpoint, MAX_CHECKPOINT_STRING);
805
+ }
806
+
454
807
  await qb.wrap(
455
808
  { label: "commit_block" },
456
- (db) =>
457
- db
458
- .update(reorgTable)
459
- .set({ checkpoint })
460
- .where(eq(reorgTable.checkpoint, MAX_CHECKPOINT_STRING)),
809
+ (db) => db.update(reorgTable).set({ checkpoint }).where(whereClause),
461
810
  context,
462
811
  );
463
812
  };