@fedify/postgres 2.0.0-dev.323 → 2.0.0-dev.335

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 (3) hide show
  1. package/dist/mq.cjs +49 -38
  2. package/dist/mq.js +49 -38
  3. package/package.json +5 -5
package/dist/mq.cjs CHANGED
@@ -35,6 +35,7 @@ var PostgresMessageQueue = class {
35
35
  #channelName;
36
36
  #pollIntervalMs;
37
37
  #initialized;
38
+ #initPromise;
38
39
  #driverSerializesJson = false;
39
40
  constructor(sql, options = {}) {
40
41
  this.#sql = sql;
@@ -114,7 +115,7 @@ var PostgresMessageQueue = class {
114
115
  const poll = async () => {
115
116
  while (!signal?.aborted) {
116
117
  let processed = false;
117
- const query = this.#sql`
118
+ for (const row of await this.#sql`
118
119
  WITH candidate AS (
119
120
  SELECT id, ordering_key
120
121
  FROM ${this.#sql(this.#tableName)}
@@ -127,15 +128,11 @@ var PostgresMessageQueue = class {
127
128
  DELETE FROM ${this.#sql(this.#tableName)}
128
129
  WHERE id IN (SELECT id FROM candidate)
129
130
  RETURNING message, ordering_key;
130
- `.execute();
131
- const cancel = query.cancel.bind(query);
132
- signal?.addEventListener("abort", cancel);
133
- for (const row of await query) {
131
+ `) {
134
132
  if (signal?.aborted) return;
135
133
  await handler(row.message);
136
134
  processed = true;
137
135
  }
138
- signal?.removeEventListener("abort", cancel);
139
136
  if (processed) continue;
140
137
  const attemptedOrderingKeys = /* @__PURE__ */ new Set();
141
138
  while (!signal?.aborted) {
@@ -153,46 +150,57 @@ var PostgresMessageQueue = class {
153
150
  const candidateId = candidate.id;
154
151
  const orderingKey = candidate.ordering_key;
155
152
  attemptedOrderingKeys.add(orderingKey);
156
- const lockResult = await this.#sql`
157
- SELECT pg_try_advisory_lock(
158
- hashtext(${this.#tableName}),
159
- hashtext(${orderingKey})
160
- ) AS acquired
161
- `;
162
- if (lockResult[0].acquired) {
163
- try {
164
- const deleteResult = await this.#sql`
165
- DELETE FROM ${this.#sql(this.#tableName)}
166
- WHERE id = ${candidateId}
167
- RETURNING message, ordering_key
168
- `;
169
- for (const row of deleteResult) {
170
- if (signal?.aborted) return;
171
- await handler(row.message);
172
- processed = true;
153
+ const reserved = await this.#sql.reserve();
154
+ try {
155
+ const lockResult = await reserved`
156
+ SELECT pg_try_advisory_lock(
157
+ hashtext(${this.#tableName}),
158
+ hashtext(${orderingKey})
159
+ ) AS acquired
160
+ `;
161
+ if (lockResult[0].acquired) {
162
+ try {
163
+ const deleteResult = await reserved`
164
+ DELETE FROM ${reserved(this.#tableName)}
165
+ WHERE id = ${candidateId}
166
+ RETURNING message, ordering_key
167
+ `;
168
+ for (const row of deleteResult) {
169
+ if (signal?.aborted) return;
170
+ await handler(row.message);
171
+ processed = true;
172
+ }
173
+ } finally {
174
+ await reserved`
175
+ SELECT pg_advisory_unlock(
176
+ hashtext(${this.#tableName}),
177
+ hashtext(${orderingKey})
178
+ )
179
+ `;
173
180
  }
174
- } finally {
175
- await this.#sql`
176
- SELECT pg_advisory_unlock(
177
- hashtext(${this.#tableName}),
178
- hashtext(${orderingKey})
179
- )
180
- `;
181
+ if (processed) break;
181
182
  }
182
- if (processed) break;
183
+ } finally {
184
+ reserved.release();
183
185
  }
184
186
  }
185
187
  if (processed) continue;
186
188
  break;
187
189
  }
188
190
  };
191
+ let pollLock = Promise.resolve();
192
+ const serializedPoll = () => {
193
+ const next = pollLock.then(poll);
194
+ pollLock = next.catch(() => {});
195
+ return next;
196
+ };
189
197
  const timeouts = /* @__PURE__ */ new Set();
190
198
  const listen = await this.#sql.listen(this.#channelName, async (delay) => {
191
199
  const duration = Temporal.Duration.from(delay);
192
200
  const durationMs = duration.total("millisecond");
193
- if (durationMs < 1) await poll();
194
- else timeouts.add(setTimeout(poll, durationMs));
195
- }, poll);
201
+ if (durationMs < 1) await serializedPoll();
202
+ else timeouts.add(setTimeout(serializedPoll, durationMs));
203
+ }, serializedPoll);
196
204
  signal?.addEventListener("abort", () => {
197
205
  listen.unlisten();
198
206
  for (const timeout of timeouts) clearTimeout(timeout);
@@ -208,7 +216,7 @@ var PostgresMessageQueue = class {
208
216
  timeouts.add(timeout);
209
217
  });
210
218
  if (timeout != null) timeouts.delete(timeout);
211
- await poll();
219
+ await serializedPoll();
212
220
  }
213
221
  await new Promise((resolve) => {
214
222
  signal?.addEventListener("abort", () => resolve());
@@ -218,8 +226,11 @@ var PostgresMessageQueue = class {
218
226
  /**
219
227
  * Initializes the message queue table if it does not already exist.
220
228
  */
221
- async initialize() {
222
- if (this.#initialized) return;
229
+ initialize() {
230
+ if (this.#initialized) return Promise.resolve();
231
+ return this.#initPromise ??= this.#doInitialize();
232
+ }
233
+ async #doInitialize() {
223
234
  logger.debug("Initializing the message queue table {tableName}...", { tableName: this.#tableName });
224
235
  try {
225
236
  await this.#sql`
@@ -236,7 +247,7 @@ var PostgresMessageQueue = class {
236
247
  ADD COLUMN IF NOT EXISTS ordering_key text;
237
248
  `;
238
249
  } catch (error) {
239
- if (!(error instanceof postgres.default.PostgresError && error.constraint_name === "pg_type_typname_nsp_index")) {
250
+ if (!(error instanceof postgres.default.PostgresError && (error.constraint_name === "pg_type_typname_nsp_index" || error.code === "42P07"))) {
240
251
  logger.error("Failed to initialize the message queue table: {error}", { error });
241
252
  throw error;
242
253
  }
package/dist/mq.js CHANGED
@@ -34,6 +34,7 @@ var PostgresMessageQueue = class {
34
34
  #channelName;
35
35
  #pollIntervalMs;
36
36
  #initialized;
37
+ #initPromise;
37
38
  #driverSerializesJson = false;
38
39
  constructor(sql, options = {}) {
39
40
  this.#sql = sql;
@@ -113,7 +114,7 @@ var PostgresMessageQueue = class {
113
114
  const poll = async () => {
114
115
  while (!signal?.aborted) {
115
116
  let processed = false;
116
- const query = this.#sql`
117
+ for (const row of await this.#sql`
117
118
  WITH candidate AS (
118
119
  SELECT id, ordering_key
119
120
  FROM ${this.#sql(this.#tableName)}
@@ -126,15 +127,11 @@ var PostgresMessageQueue = class {
126
127
  DELETE FROM ${this.#sql(this.#tableName)}
127
128
  WHERE id IN (SELECT id FROM candidate)
128
129
  RETURNING message, ordering_key;
129
- `.execute();
130
- const cancel = query.cancel.bind(query);
131
- signal?.addEventListener("abort", cancel);
132
- for (const row of await query) {
130
+ `) {
133
131
  if (signal?.aborted) return;
134
132
  await handler(row.message);
135
133
  processed = true;
136
134
  }
137
- signal?.removeEventListener("abort", cancel);
138
135
  if (processed) continue;
139
136
  const attemptedOrderingKeys = /* @__PURE__ */ new Set();
140
137
  while (!signal?.aborted) {
@@ -152,46 +149,57 @@ var PostgresMessageQueue = class {
152
149
  const candidateId = candidate.id;
153
150
  const orderingKey = candidate.ordering_key;
154
151
  attemptedOrderingKeys.add(orderingKey);
155
- const lockResult = await this.#sql`
156
- SELECT pg_try_advisory_lock(
157
- hashtext(${this.#tableName}),
158
- hashtext(${orderingKey})
159
- ) AS acquired
160
- `;
161
- if (lockResult[0].acquired) {
162
- try {
163
- const deleteResult = await this.#sql`
164
- DELETE FROM ${this.#sql(this.#tableName)}
165
- WHERE id = ${candidateId}
166
- RETURNING message, ordering_key
167
- `;
168
- for (const row of deleteResult) {
169
- if (signal?.aborted) return;
170
- await handler(row.message);
171
- processed = true;
152
+ const reserved = await this.#sql.reserve();
153
+ try {
154
+ const lockResult = await reserved`
155
+ SELECT pg_try_advisory_lock(
156
+ hashtext(${this.#tableName}),
157
+ hashtext(${orderingKey})
158
+ ) AS acquired
159
+ `;
160
+ if (lockResult[0].acquired) {
161
+ try {
162
+ const deleteResult = await reserved`
163
+ DELETE FROM ${reserved(this.#tableName)}
164
+ WHERE id = ${candidateId}
165
+ RETURNING message, ordering_key
166
+ `;
167
+ for (const row of deleteResult) {
168
+ if (signal?.aborted) return;
169
+ await handler(row.message);
170
+ processed = true;
171
+ }
172
+ } finally {
173
+ await reserved`
174
+ SELECT pg_advisory_unlock(
175
+ hashtext(${this.#tableName}),
176
+ hashtext(${orderingKey})
177
+ )
178
+ `;
172
179
  }
173
- } finally {
174
- await this.#sql`
175
- SELECT pg_advisory_unlock(
176
- hashtext(${this.#tableName}),
177
- hashtext(${orderingKey})
178
- )
179
- `;
180
+ if (processed) break;
180
181
  }
181
- if (processed) break;
182
+ } finally {
183
+ reserved.release();
182
184
  }
183
185
  }
184
186
  if (processed) continue;
185
187
  break;
186
188
  }
187
189
  };
190
+ let pollLock = Promise.resolve();
191
+ const serializedPoll = () => {
192
+ const next = pollLock.then(poll);
193
+ pollLock = next.catch(() => {});
194
+ return next;
195
+ };
188
196
  const timeouts = /* @__PURE__ */ new Set();
189
197
  const listen = await this.#sql.listen(this.#channelName, async (delay) => {
190
198
  const duration = Temporal.Duration.from(delay);
191
199
  const durationMs = duration.total("millisecond");
192
- if (durationMs < 1) await poll();
193
- else timeouts.add(setTimeout(poll, durationMs));
194
- }, poll);
200
+ if (durationMs < 1) await serializedPoll();
201
+ else timeouts.add(setTimeout(serializedPoll, durationMs));
202
+ }, serializedPoll);
195
203
  signal?.addEventListener("abort", () => {
196
204
  listen.unlisten();
197
205
  for (const timeout of timeouts) clearTimeout(timeout);
@@ -207,7 +215,7 @@ var PostgresMessageQueue = class {
207
215
  timeouts.add(timeout);
208
216
  });
209
217
  if (timeout != null) timeouts.delete(timeout);
210
- await poll();
218
+ await serializedPoll();
211
219
  }
212
220
  await new Promise((resolve) => {
213
221
  signal?.addEventListener("abort", () => resolve());
@@ -217,8 +225,11 @@ var PostgresMessageQueue = class {
217
225
  /**
218
226
  * Initializes the message queue table if it does not already exist.
219
227
  */
220
- async initialize() {
221
- if (this.#initialized) return;
228
+ initialize() {
229
+ if (this.#initialized) return Promise.resolve();
230
+ return this.#initPromise ??= this.#doInitialize();
231
+ }
232
+ async #doInitialize() {
222
233
  logger.debug("Initializing the message queue table {tableName}...", { tableName: this.#tableName });
223
234
  try {
224
235
  await this.#sql`
@@ -235,7 +246,7 @@ var PostgresMessageQueue = class {
235
246
  ADD COLUMN IF NOT EXISTS ordering_key text;
236
247
  `;
237
248
  } catch (error) {
238
- if (!(error instanceof postgres.PostgresError && error.constraint_name === "pg_type_typname_nsp_index")) {
249
+ if (!(error instanceof postgres.PostgresError && (error.constraint_name === "pg_type_typname_nsp_index" || error.code === "42P07"))) {
239
250
  logger.error("Failed to initialize the message queue table: {error}", { error });
240
251
  throw error;
241
252
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/postgres",
3
- "version": "2.0.0-dev.323+1d796545",
3
+ "version": "2.0.0-dev.335+6fe86704",
4
4
  "description": "PostgreSQL drivers for Fedify",
5
5
  "keywords": [
6
6
  "fedify",
@@ -74,14 +74,14 @@
74
74
  },
75
75
  "peerDependencies": {
76
76
  "postgres": "^3.4.7",
77
- "@fedify/fedify": "^2.0.0-dev.323+1d796545"
77
+ "@fedify/fedify": "^2.0.0-dev.335+6fe86704"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@std/async": "npm:@jsr/std__async@^1.0.13",
81
81
  "tsdown": "^0.12.9",
82
82
  "typescript": "^5.9.3",
83
- "@fedify/fixture": "^2.0.0",
84
- "@fedify/testing": "^2.0.0-dev.323+1d796545"
83
+ "@fedify/testing": "^2.0.0-dev.335+6fe86704",
84
+ "@fedify/fixture": "^2.0.0"
85
85
  },
86
86
  "scripts": {
87
87
  "build:self": "tsdown",
@@ -90,6 +90,6 @@
90
90
  "pretest": "pnpm build",
91
91
  "test": "node --experimental-transform-types --test",
92
92
  "pretest:bun": "pnpm build",
93
- "test:bun": "bun test --timeout=10000"
93
+ "test:bun": "bun test --timeout=60000"
94
94
  }
95
95
  }