@fedify/postgres 2.0.0-dev.279 → 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.
- package/dist/mq.cjs +78 -32
- package/dist/mq.js +78 -32
- 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;
|
|
@@ -113,51 +114,93 @@ var PostgresMessageQueue = class {
|
|
|
113
114
|
const { signal } = options;
|
|
114
115
|
const poll = async () => {
|
|
115
116
|
while (!signal?.aborted) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
SELECT id
|
|
117
|
+
let processed = false;
|
|
118
|
+
for (const row of await this.#sql`
|
|
119
|
+
WITH candidate AS (
|
|
120
|
+
SELECT id, ordering_key
|
|
120
121
|
FROM ${this.#sql(this.#tableName)}
|
|
121
122
|
WHERE created + delay < CURRENT_TIMESTAMP
|
|
122
|
-
AND
|
|
123
|
-
OR pg_try_advisory_lock(
|
|
124
|
-
hashtext(${this.#tableName}),
|
|
125
|
-
hashtext(ordering_key)
|
|
126
|
-
))
|
|
123
|
+
AND ordering_key IS NULL
|
|
127
124
|
ORDER BY created
|
|
128
125
|
LIMIT 1
|
|
126
|
+
FOR UPDATE SKIP LOCKED
|
|
129
127
|
)
|
|
128
|
+
DELETE FROM ${this.#sql(this.#tableName)}
|
|
129
|
+
WHERE id IN (SELECT id FROM candidate)
|
|
130
130
|
RETURNING message, ordering_key;
|
|
131
|
-
|
|
132
|
-
const cancel = query.cancel.bind(query);
|
|
133
|
-
signal?.addEventListener("abort", cancel);
|
|
134
|
-
let i = 0;
|
|
135
|
-
for (const row of await query) {
|
|
131
|
+
`) {
|
|
136
132
|
if (signal?.aborted) return;
|
|
137
|
-
|
|
133
|
+
await handler(row.message);
|
|
134
|
+
processed = true;
|
|
135
|
+
}
|
|
136
|
+
if (processed) continue;
|
|
137
|
+
const attemptedOrderingKeys = /* @__PURE__ */ new Set();
|
|
138
|
+
while (!signal?.aborted) {
|
|
139
|
+
const candidateResult = await this.#sql`
|
|
140
|
+
SELECT id, ordering_key
|
|
141
|
+
FROM ${this.#sql(this.#tableName)}
|
|
142
|
+
WHERE created + delay < CURRENT_TIMESTAMP
|
|
143
|
+
AND ordering_key IS NOT NULL
|
|
144
|
+
${attemptedOrderingKeys.size > 0 ? this.#sql`AND ordering_key NOT IN ${this.#sql([...attemptedOrderingKeys])}` : this.#sql``}
|
|
145
|
+
ORDER BY created
|
|
146
|
+
LIMIT 1
|
|
147
|
+
`;
|
|
148
|
+
if (candidateResult.length === 0) break;
|
|
149
|
+
const candidate = candidateResult[0];
|
|
150
|
+
const candidateId = candidate.id;
|
|
151
|
+
const orderingKey = candidate.ordering_key;
|
|
152
|
+
attemptedOrderingKeys.add(orderingKey);
|
|
153
|
+
const reserved = await this.#sql.reserve();
|
|
138
154
|
try {
|
|
139
|
-
await
|
|
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
|
+
`;
|
|
180
|
+
}
|
|
181
|
+
if (processed) break;
|
|
182
|
+
}
|
|
140
183
|
} finally {
|
|
141
|
-
|
|
142
|
-
SELECT pg_advisory_unlock(
|
|
143
|
-
hashtext(${this.#tableName}),
|
|
144
|
-
hashtext(${orderingKey})
|
|
145
|
-
);
|
|
146
|
-
`;
|
|
184
|
+
reserved.release();
|
|
147
185
|
}
|
|
148
|
-
i++;
|
|
149
186
|
}
|
|
150
|
-
|
|
151
|
-
|
|
187
|
+
if (processed) continue;
|
|
188
|
+
break;
|
|
152
189
|
}
|
|
153
190
|
};
|
|
191
|
+
let pollLock = Promise.resolve();
|
|
192
|
+
const serializedPoll = () => {
|
|
193
|
+
const next = pollLock.then(poll);
|
|
194
|
+
pollLock = next.catch(() => {});
|
|
195
|
+
return next;
|
|
196
|
+
};
|
|
154
197
|
const timeouts = /* @__PURE__ */ new Set();
|
|
155
198
|
const listen = await this.#sql.listen(this.#channelName, async (delay) => {
|
|
156
199
|
const duration = Temporal.Duration.from(delay);
|
|
157
200
|
const durationMs = duration.total("millisecond");
|
|
158
|
-
if (durationMs < 1) await
|
|
159
|
-
else timeouts.add(setTimeout(
|
|
160
|
-
},
|
|
201
|
+
if (durationMs < 1) await serializedPoll();
|
|
202
|
+
else timeouts.add(setTimeout(serializedPoll, durationMs));
|
|
203
|
+
}, serializedPoll);
|
|
161
204
|
signal?.addEventListener("abort", () => {
|
|
162
205
|
listen.unlisten();
|
|
163
206
|
for (const timeout of timeouts) clearTimeout(timeout);
|
|
@@ -173,7 +216,7 @@ var PostgresMessageQueue = class {
|
|
|
173
216
|
timeouts.add(timeout);
|
|
174
217
|
});
|
|
175
218
|
if (timeout != null) timeouts.delete(timeout);
|
|
176
|
-
await
|
|
219
|
+
await serializedPoll();
|
|
177
220
|
}
|
|
178
221
|
await new Promise((resolve) => {
|
|
179
222
|
signal?.addEventListener("abort", () => resolve());
|
|
@@ -183,8 +226,11 @@ var PostgresMessageQueue = class {
|
|
|
183
226
|
/**
|
|
184
227
|
* Initializes the message queue table if it does not already exist.
|
|
185
228
|
*/
|
|
186
|
-
|
|
187
|
-
if (this.#initialized) return;
|
|
229
|
+
initialize() {
|
|
230
|
+
if (this.#initialized) return Promise.resolve();
|
|
231
|
+
return this.#initPromise ??= this.#doInitialize();
|
|
232
|
+
}
|
|
233
|
+
async #doInitialize() {
|
|
188
234
|
logger.debug("Initializing the message queue table {tableName}...", { tableName: this.#tableName });
|
|
189
235
|
try {
|
|
190
236
|
await this.#sql`
|
|
@@ -201,7 +247,7 @@ var PostgresMessageQueue = class {
|
|
|
201
247
|
ADD COLUMN IF NOT EXISTS ordering_key text;
|
|
202
248
|
`;
|
|
203
249
|
} catch (error) {
|
|
204
|
-
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"))) {
|
|
205
251
|
logger.error("Failed to initialize the message queue table: {error}", { error });
|
|
206
252
|
throw error;
|
|
207
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;
|
|
@@ -112,51 +113,93 @@ var PostgresMessageQueue = class {
|
|
|
112
113
|
const { signal } = options;
|
|
113
114
|
const poll = async () => {
|
|
114
115
|
while (!signal?.aborted) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
SELECT id
|
|
116
|
+
let processed = false;
|
|
117
|
+
for (const row of await this.#sql`
|
|
118
|
+
WITH candidate AS (
|
|
119
|
+
SELECT id, ordering_key
|
|
119
120
|
FROM ${this.#sql(this.#tableName)}
|
|
120
121
|
WHERE created + delay < CURRENT_TIMESTAMP
|
|
121
|
-
AND
|
|
122
|
-
OR pg_try_advisory_lock(
|
|
123
|
-
hashtext(${this.#tableName}),
|
|
124
|
-
hashtext(ordering_key)
|
|
125
|
-
))
|
|
122
|
+
AND ordering_key IS NULL
|
|
126
123
|
ORDER BY created
|
|
127
124
|
LIMIT 1
|
|
125
|
+
FOR UPDATE SKIP LOCKED
|
|
128
126
|
)
|
|
127
|
+
DELETE FROM ${this.#sql(this.#tableName)}
|
|
128
|
+
WHERE id IN (SELECT id FROM candidate)
|
|
129
129
|
RETURNING message, ordering_key;
|
|
130
|
-
|
|
131
|
-
const cancel = query.cancel.bind(query);
|
|
132
|
-
signal?.addEventListener("abort", cancel);
|
|
133
|
-
let i = 0;
|
|
134
|
-
for (const row of await query) {
|
|
130
|
+
`) {
|
|
135
131
|
if (signal?.aborted) return;
|
|
136
|
-
|
|
132
|
+
await handler(row.message);
|
|
133
|
+
processed = true;
|
|
134
|
+
}
|
|
135
|
+
if (processed) continue;
|
|
136
|
+
const attemptedOrderingKeys = /* @__PURE__ */ new Set();
|
|
137
|
+
while (!signal?.aborted) {
|
|
138
|
+
const candidateResult = await this.#sql`
|
|
139
|
+
SELECT id, ordering_key
|
|
140
|
+
FROM ${this.#sql(this.#tableName)}
|
|
141
|
+
WHERE created + delay < CURRENT_TIMESTAMP
|
|
142
|
+
AND ordering_key IS NOT NULL
|
|
143
|
+
${attemptedOrderingKeys.size > 0 ? this.#sql`AND ordering_key NOT IN ${this.#sql([...attemptedOrderingKeys])}` : this.#sql``}
|
|
144
|
+
ORDER BY created
|
|
145
|
+
LIMIT 1
|
|
146
|
+
`;
|
|
147
|
+
if (candidateResult.length === 0) break;
|
|
148
|
+
const candidate = candidateResult[0];
|
|
149
|
+
const candidateId = candidate.id;
|
|
150
|
+
const orderingKey = candidate.ordering_key;
|
|
151
|
+
attemptedOrderingKeys.add(orderingKey);
|
|
152
|
+
const reserved = await this.#sql.reserve();
|
|
137
153
|
try {
|
|
138
|
-
await
|
|
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
|
+
`;
|
|
179
|
+
}
|
|
180
|
+
if (processed) break;
|
|
181
|
+
}
|
|
139
182
|
} finally {
|
|
140
|
-
|
|
141
|
-
SELECT pg_advisory_unlock(
|
|
142
|
-
hashtext(${this.#tableName}),
|
|
143
|
-
hashtext(${orderingKey})
|
|
144
|
-
);
|
|
145
|
-
`;
|
|
183
|
+
reserved.release();
|
|
146
184
|
}
|
|
147
|
-
i++;
|
|
148
185
|
}
|
|
149
|
-
|
|
150
|
-
|
|
186
|
+
if (processed) continue;
|
|
187
|
+
break;
|
|
151
188
|
}
|
|
152
189
|
};
|
|
190
|
+
let pollLock = Promise.resolve();
|
|
191
|
+
const serializedPoll = () => {
|
|
192
|
+
const next = pollLock.then(poll);
|
|
193
|
+
pollLock = next.catch(() => {});
|
|
194
|
+
return next;
|
|
195
|
+
};
|
|
153
196
|
const timeouts = /* @__PURE__ */ new Set();
|
|
154
197
|
const listen = await this.#sql.listen(this.#channelName, async (delay) => {
|
|
155
198
|
const duration = Temporal.Duration.from(delay);
|
|
156
199
|
const durationMs = duration.total("millisecond");
|
|
157
|
-
if (durationMs < 1) await
|
|
158
|
-
else timeouts.add(setTimeout(
|
|
159
|
-
},
|
|
200
|
+
if (durationMs < 1) await serializedPoll();
|
|
201
|
+
else timeouts.add(setTimeout(serializedPoll, durationMs));
|
|
202
|
+
}, serializedPoll);
|
|
160
203
|
signal?.addEventListener("abort", () => {
|
|
161
204
|
listen.unlisten();
|
|
162
205
|
for (const timeout of timeouts) clearTimeout(timeout);
|
|
@@ -172,7 +215,7 @@ var PostgresMessageQueue = class {
|
|
|
172
215
|
timeouts.add(timeout);
|
|
173
216
|
});
|
|
174
217
|
if (timeout != null) timeouts.delete(timeout);
|
|
175
|
-
await
|
|
218
|
+
await serializedPoll();
|
|
176
219
|
}
|
|
177
220
|
await new Promise((resolve) => {
|
|
178
221
|
signal?.addEventListener("abort", () => resolve());
|
|
@@ -182,8 +225,11 @@ var PostgresMessageQueue = class {
|
|
|
182
225
|
/**
|
|
183
226
|
* Initializes the message queue table if it does not already exist.
|
|
184
227
|
*/
|
|
185
|
-
|
|
186
|
-
if (this.#initialized) return;
|
|
228
|
+
initialize() {
|
|
229
|
+
if (this.#initialized) return Promise.resolve();
|
|
230
|
+
return this.#initPromise ??= this.#doInitialize();
|
|
231
|
+
}
|
|
232
|
+
async #doInitialize() {
|
|
187
233
|
logger.debug("Initializing the message queue table {tableName}...", { tableName: this.#tableName });
|
|
188
234
|
try {
|
|
189
235
|
await this.#sql`
|
|
@@ -200,7 +246,7 @@ var PostgresMessageQueue = class {
|
|
|
200
246
|
ADD COLUMN IF NOT EXISTS ordering_key text;
|
|
201
247
|
`;
|
|
202
248
|
} catch (error) {
|
|
203
|
-
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"))) {
|
|
204
250
|
logger.error("Failed to initialize the message queue table: {error}", { error });
|
|
205
251
|
throw error;
|
|
206
252
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fedify/postgres",
|
|
3
|
-
"version": "2.0.0-dev.
|
|
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.
|
|
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/
|
|
84
|
-
"@fedify/
|
|
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=
|
|
93
|
+
"test:bun": "bun test --timeout=60000"
|
|
94
94
|
}
|
|
95
95
|
}
|