@fedify/postgres 2.0.0-pr.490.2 → 2.0.0-pr.559.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright 2024–2025 Hong Minhee
3
+ Copyright 2024–2026 Hong Minhee
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
package/README.md CHANGED
@@ -25,10 +25,10 @@ const federation = createFederation({
25
25
  });
26
26
  ~~~~
27
27
 
28
- [JSR]: https://jsr.io/@fedify/postgres
29
28
  [JSR badge]: https://jsr.io/badges/@fedify/postgres
30
- [npm]: https://www.npmjs.com/package/@fedify/postgres
29
+ [JSR]: https://jsr.io/@fedify/postgres
31
30
  [npm badge]: https://img.shields.io/npm/v/@fedify/postgres?logo=npm
31
+ [npm]: https://www.npmjs.com/package/@fedify/postgres
32
32
  [Fedify]: https://fedify.dev/
33
33
  [`KvStore`]: https://jsr.io/@fedify/fedify/doc/federation/~/KvStore
34
34
  [`MessageQueue`]: https://jsr.io/@fedify/fedify/doc/federation/~/MessageQueue
package/dist/kv.cjs CHANGED
@@ -81,6 +81,35 @@ var PostgresKvStore = class {
81
81
  await this.#expire();
82
82
  }
83
83
  /**
84
+ * {@inheritDoc KvStore.list}
85
+ * @since 1.10.0
86
+ */
87
+ async *list(prefix) {
88
+ await this.initialize();
89
+ let results;
90
+ if (prefix == null || prefix.length === 0) results = await this.#sql`
91
+ SELECT key, value
92
+ FROM ${this.#sql(this.#tableName)}
93
+ WHERE ttl IS NULL OR created + ttl > CURRENT_TIMESTAMP
94
+ ORDER BY key
95
+ `;
96
+ else {
97
+ const prefixLength = prefix.length;
98
+ results = await this.#sql`
99
+ SELECT key, value
100
+ FROM ${this.#sql(this.#tableName)}
101
+ WHERE array_length(key, 1) >= ${prefixLength}
102
+ AND key[1:${prefixLength}] = ${prefix}::text[]
103
+ AND (ttl IS NULL OR created + ttl > CURRENT_TIMESTAMP)
104
+ ORDER BY key
105
+ `;
106
+ }
107
+ for (const row of results) yield {
108
+ key: row.key,
109
+ value: row.value
110
+ };
111
+ }
112
+ /**
84
113
  * Creates the table used by the key–value store if it does not already exist.
85
114
  * Does nothing if the table already exists.
86
115
  */
package/dist/kv.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { KvKey, KvStore, KvStoreSetOptions } from "@fedify/fedify";
1
+ import { KvKey, KvStore, KvStoreListEntry, KvStoreSetOptions } from "@fedify/fedify";
2
2
  import { Sql } from "postgres";
3
3
 
4
4
  //#region src/kv.d.ts
@@ -11,12 +11,12 @@ interface PostgresKvStoreOptions {
11
11
  * `"fedify_kv_v2"` by default.
12
12
  * @default `"fedify_kv_v2"`
13
13
  */
14
- tableName?: string;
14
+ readonly tableName?: string;
15
15
  /**
16
16
  * Whether the table has been initialized. `false` by default.
17
17
  * @default `false`
18
18
  */
19
- initialized?: boolean;
19
+ readonly initialized?: boolean;
20
20
  }
21
21
  /**
22
22
  * A key–value store that uses PostgreSQL as the underlying storage.
@@ -44,6 +44,11 @@ declare class PostgresKvStore implements KvStore {
44
44
  get<T = unknown>(key: KvKey): Promise<T | undefined>;
45
45
  set(key: KvKey, value: unknown, options?: KvStoreSetOptions | undefined): Promise<void>;
46
46
  delete(key: KvKey): Promise<void>;
47
+ /**
48
+ * {@inheritDoc KvStore.list}
49
+ * @since 1.10.0
50
+ */
51
+ list(prefix?: KvKey): AsyncIterable<KvStoreListEntry>;
47
52
  /**
48
53
  * Creates the table used by the key–value store if it does not already exist.
49
54
  * Does nothing if the table already exists.
package/dist/kv.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { Sql } from "postgres";
3
- import { KvKey, KvStore, KvStoreSetOptions } from "@fedify/fedify";
3
+ import { KvKey, KvStore, KvStoreListEntry, KvStoreSetOptions } from "@fedify/fedify";
4
4
 
5
5
  //#region src/kv.d.ts
6
6
  /**
@@ -12,12 +12,12 @@ interface PostgresKvStoreOptions {
12
12
  * `"fedify_kv_v2"` by default.
13
13
  * @default `"fedify_kv_v2"`
14
14
  */
15
- tableName?: string;
15
+ readonly tableName?: string;
16
16
  /**
17
17
  * Whether the table has been initialized. `false` by default.
18
18
  * @default `false`
19
19
  */
20
- initialized?: boolean;
20
+ readonly initialized?: boolean;
21
21
  }
22
22
  /**
23
23
  * A key–value store that uses PostgreSQL as the underlying storage.
@@ -45,6 +45,11 @@ declare class PostgresKvStore implements KvStore {
45
45
  get<T = unknown>(key: KvKey): Promise<T | undefined>;
46
46
  set(key: KvKey, value: unknown, options?: KvStoreSetOptions | undefined): Promise<void>;
47
47
  delete(key: KvKey): Promise<void>;
48
+ /**
49
+ * {@inheritDoc KvStore.list}
50
+ * @since 1.10.0
51
+ */
52
+ list(prefix?: KvKey): AsyncIterable<KvStoreListEntry>;
48
53
  /**
49
54
  * Creates the table used by the key–value store if it does not already exist.
50
55
  * Does nothing if the table already exists.
package/dist/kv.js CHANGED
@@ -80,6 +80,35 @@ var PostgresKvStore = class {
80
80
  await this.#expire();
81
81
  }
82
82
  /**
83
+ * {@inheritDoc KvStore.list}
84
+ * @since 1.10.0
85
+ */
86
+ async *list(prefix) {
87
+ await this.initialize();
88
+ let results;
89
+ if (prefix == null || prefix.length === 0) results = await this.#sql`
90
+ SELECT key, value
91
+ FROM ${this.#sql(this.#tableName)}
92
+ WHERE ttl IS NULL OR created + ttl > CURRENT_TIMESTAMP
93
+ ORDER BY key
94
+ `;
95
+ else {
96
+ const prefixLength = prefix.length;
97
+ results = await this.#sql`
98
+ SELECT key, value
99
+ FROM ${this.#sql(this.#tableName)}
100
+ WHERE array_length(key, 1) >= ${prefixLength}
101
+ AND key[1:${prefixLength}] = ${prefix}::text[]
102
+ AND (ttl IS NULL OR created + ttl > CURRENT_TIMESTAMP)
103
+ ORDER BY key
104
+ `;
105
+ }
106
+ for (const row of results) yield {
107
+ key: row.key,
108
+ value: row.value
109
+ };
110
+ }
111
+ /**
83
112
  * Creates the table used by the key–value store if it does not already exist.
84
113
  * Does nothing if the table already exists.
85
114
  */
package/dist/mq.cjs CHANGED
@@ -46,46 +46,66 @@ var PostgresMessageQueue = class {
46
46
  async enqueue(message, options) {
47
47
  await this.initialize();
48
48
  const delay = options?.delay ?? Temporal.Duration.from({ seconds: 0 });
49
+ const orderingKey = options?.orderingKey ?? null;
49
50
  if (options?.delay) logger.debug("Enqueuing a message with a delay of {delay}...", {
50
51
  delay,
51
- message
52
+ message,
53
+ orderingKey
54
+ });
55
+ else logger.debug("Enqueuing a message...", {
56
+ message,
57
+ orderingKey
52
58
  });
53
- else logger.debug("Enqueuing a message...", { message });
54
59
  await this.#sql`
55
- INSERT INTO ${this.#sql(this.#tableName)} (message, delay)
60
+ INSERT INTO ${this.#sql(this.#tableName)} (message, delay, ordering_key)
56
61
  VALUES (
57
62
  ${this.#json(message)},
58
- ${delay.toString()}
63
+ ${delay.toString()},
64
+ ${orderingKey}
59
65
  );
60
66
  `;
61
- logger.debug("Enqueued a message.", { message });
67
+ logger.debug("Enqueued a message.", {
68
+ message,
69
+ orderingKey
70
+ });
62
71
  await this.#sql.notify(this.#channelName, delay.toString());
63
72
  logger.debug("Notified the message queue channel {channelName}.", {
64
73
  channelName: this.#channelName,
65
- message
74
+ message,
75
+ orderingKey
66
76
  });
67
77
  }
68
78
  async enqueueMany(messages, options) {
69
79
  if (messages.length === 0) return;
70
80
  await this.initialize();
71
81
  const delay = options?.delay ?? Temporal.Duration.from({ seconds: 0 });
82
+ const orderingKey = options?.orderingKey ?? null;
72
83
  if (options?.delay) logger.debug("Enqueuing messages with a delay of {delay}...", {
73
84
  delay,
74
- messages
85
+ messages,
86
+ orderingKey
87
+ });
88
+ else logger.debug("Enqueuing messages...", {
89
+ messages,
90
+ orderingKey
75
91
  });
76
- else logger.debug("Enqueuing messages...", { messages });
77
92
  for (const message of messages) await this.#sql`
78
- INSERT INTO ${this.#sql(this.#tableName)} (message, delay)
93
+ INSERT INTO ${this.#sql(this.#tableName)} (message, delay, ordering_key)
79
94
  VALUES (
80
95
  ${this.#json(message)},
81
- ${delay.toString()}
96
+ ${delay.toString()},
97
+ ${orderingKey}
82
98
  );
83
99
  `;
84
- logger.debug("Enqueued messages.", { messages });
100
+ logger.debug("Enqueued messages.", {
101
+ messages,
102
+ orderingKey
103
+ });
85
104
  await this.#sql.notify(this.#channelName, delay.toString());
86
105
  logger.debug("Notified the message queue channel {channelName}.", {
87
106
  channelName: this.#channelName,
88
- messages
107
+ messages,
108
+ orderingKey
89
109
  });
90
110
  }
91
111
  async listen(handler, options = {}) {
@@ -93,27 +113,77 @@ var PostgresMessageQueue = class {
93
113
  const { signal } = options;
94
114
  const poll = async () => {
95
115
  while (!signal?.aborted) {
116
+ let processed = false;
96
117
  const query = this.#sql`
97
- DELETE FROM ${this.#sql(this.#tableName)}
98
- WHERE id = (
99
- SELECT id
118
+ WITH candidate AS (
119
+ SELECT id, ordering_key
100
120
  FROM ${this.#sql(this.#tableName)}
101
121
  WHERE created + delay < CURRENT_TIMESTAMP
122
+ AND ordering_key IS NULL
102
123
  ORDER BY created
103
124
  LIMIT 1
125
+ FOR UPDATE SKIP LOCKED
104
126
  )
105
- RETURNING message;
127
+ DELETE FROM ${this.#sql(this.#tableName)}
128
+ WHERE id IN (SELECT id FROM candidate)
129
+ RETURNING message, ordering_key;
106
130
  `.execute();
107
131
  const cancel = query.cancel.bind(query);
108
132
  signal?.addEventListener("abort", cancel);
109
- let i = 0;
110
- for (const message of await query) {
133
+ for (const row of await query) {
111
134
  if (signal?.aborted) return;
112
- await handler(message.message);
113
- i++;
135
+ await handler(row.message);
136
+ processed = true;
114
137
  }
115
138
  signal?.removeEventListener("abort", cancel);
116
- if (i < 1) break;
139
+ if (processed) continue;
140
+ const attemptedOrderingKeys = /* @__PURE__ */ new Set();
141
+ while (!signal?.aborted) {
142
+ const candidateResult = await this.#sql`
143
+ SELECT id, ordering_key
144
+ FROM ${this.#sql(this.#tableName)}
145
+ WHERE created + delay < CURRENT_TIMESTAMP
146
+ AND ordering_key IS NOT NULL
147
+ ${attemptedOrderingKeys.size > 0 ? this.#sql`AND ordering_key NOT IN ${this.#sql([...attemptedOrderingKeys])}` : this.#sql``}
148
+ ORDER BY created
149
+ LIMIT 1
150
+ `;
151
+ if (candidateResult.length === 0) break;
152
+ const candidate = candidateResult[0];
153
+ const candidateId = candidate.id;
154
+ const orderingKey = candidate.ordering_key;
155
+ 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;
173
+ }
174
+ } finally {
175
+ await this.#sql`
176
+ SELECT pg_advisory_unlock(
177
+ hashtext(${this.#tableName}),
178
+ hashtext(${orderingKey})
179
+ )
180
+ `;
181
+ }
182
+ if (processed) break;
183
+ }
184
+ }
185
+ if (processed) continue;
186
+ break;
117
187
  }
118
188
  };
119
189
  const timeouts = /* @__PURE__ */ new Set();
@@ -157,8 +227,13 @@ var PostgresMessageQueue = class {
157
227
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
158
228
  message jsonb NOT NULL,
159
229
  delay interval DEFAULT '0 seconds',
160
- created timestamp with time zone DEFAULT CURRENT_TIMESTAMP
230
+ created timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
231
+ ordering_key text
161
232
  );
233
+ `;
234
+ await this.#sql`
235
+ ALTER TABLE ${this.#sql(this.#tableName)}
236
+ ADD COLUMN IF NOT EXISTS ordering_key text;
162
237
  `;
163
238
  } catch (error) {
164
239
  if (!(error instanceof postgres.default.PostgresError && error.constraint_name === "pg_type_typname_nsp_index")) {
package/dist/mq.d.cts CHANGED
@@ -11,23 +11,23 @@ interface PostgresMessageQueueOptions {
11
11
  * `"fedify_message_v2"` by default.
12
12
  * @default `"fedify_message_v2"`
13
13
  */
14
- tableName?: string;
14
+ readonly tableName?: string;
15
15
  /**
16
16
  * The channel name to use for the message queue.
17
17
  * `"fedify_channel"` by default.
18
18
  * @default `"fedify_channel"`
19
19
  */
20
- channelName?: string;
20
+ readonly channelName?: string;
21
21
  /**
22
22
  * Whether the table has been initialized. `false` by default.
23
23
  * @default `false`
24
24
  */
25
- initialized?: boolean;
25
+ readonly initialized?: boolean;
26
26
  /**
27
27
  * The poll interval for the message queue. 5 seconds by default.
28
28
  * @default `{ seconds: 5 }`
29
29
  */
30
- pollInterval?: Temporal.Duration | Temporal.DurationLike;
30
+ readonly pollInterval?: Temporal.Duration | Temporal.DurationLike;
31
31
  }
32
32
  /**
33
33
  * A message queue that uses PostgreSQL as the underlying storage.
@@ -50,7 +50,7 @@ declare class PostgresMessageQueue implements MessageQueue {
50
50
  #private;
51
51
  constructor(sql: Sql<{}>, options?: PostgresMessageQueueOptions);
52
52
  enqueue(message: any, options?: MessageQueueEnqueueOptions): Promise<void>;
53
- enqueueMany(messages: any[], options?: MessageQueueEnqueueOptions): Promise<void>;
53
+ enqueueMany(messages: readonly any[], options?: MessageQueueEnqueueOptions): Promise<void>;
54
54
  listen(handler: (message: any) => void | Promise<void>, options?: MessageQueueListenOptions): Promise<void>;
55
55
  /**
56
56
  * Initializes the message queue table if it does not already exist.
package/dist/mq.d.ts CHANGED
@@ -12,23 +12,23 @@ interface PostgresMessageQueueOptions {
12
12
  * `"fedify_message_v2"` by default.
13
13
  * @default `"fedify_message_v2"`
14
14
  */
15
- tableName?: string;
15
+ readonly tableName?: string;
16
16
  /**
17
17
  * The channel name to use for the message queue.
18
18
  * `"fedify_channel"` by default.
19
19
  * @default `"fedify_channel"`
20
20
  */
21
- channelName?: string;
21
+ readonly channelName?: string;
22
22
  /**
23
23
  * Whether the table has been initialized. `false` by default.
24
24
  * @default `false`
25
25
  */
26
- initialized?: boolean;
26
+ readonly initialized?: boolean;
27
27
  /**
28
28
  * The poll interval for the message queue. 5 seconds by default.
29
29
  * @default `{ seconds: 5 }`
30
30
  */
31
- pollInterval?: Temporal.Duration | Temporal.DurationLike;
31
+ readonly pollInterval?: Temporal.Duration | Temporal.DurationLike;
32
32
  }
33
33
  /**
34
34
  * A message queue that uses PostgreSQL as the underlying storage.
@@ -51,7 +51,7 @@ declare class PostgresMessageQueue implements MessageQueue {
51
51
  #private;
52
52
  constructor(sql: Sql<{}>, options?: PostgresMessageQueueOptions);
53
53
  enqueue(message: any, options?: MessageQueueEnqueueOptions): Promise<void>;
54
- enqueueMany(messages: any[], options?: MessageQueueEnqueueOptions): Promise<void>;
54
+ enqueueMany(messages: readonly any[], options?: MessageQueueEnqueueOptions): Promise<void>;
55
55
  listen(handler: (message: any) => void | Promise<void>, options?: MessageQueueListenOptions): Promise<void>;
56
56
  /**
57
57
  * Initializes the message queue table if it does not already exist.
package/dist/mq.js CHANGED
@@ -45,46 +45,66 @@ var PostgresMessageQueue = class {
45
45
  async enqueue(message, options) {
46
46
  await this.initialize();
47
47
  const delay = options?.delay ?? Temporal.Duration.from({ seconds: 0 });
48
+ const orderingKey = options?.orderingKey ?? null;
48
49
  if (options?.delay) logger.debug("Enqueuing a message with a delay of {delay}...", {
49
50
  delay,
50
- message
51
+ message,
52
+ orderingKey
53
+ });
54
+ else logger.debug("Enqueuing a message...", {
55
+ message,
56
+ orderingKey
51
57
  });
52
- else logger.debug("Enqueuing a message...", { message });
53
58
  await this.#sql`
54
- INSERT INTO ${this.#sql(this.#tableName)} (message, delay)
59
+ INSERT INTO ${this.#sql(this.#tableName)} (message, delay, ordering_key)
55
60
  VALUES (
56
61
  ${this.#json(message)},
57
- ${delay.toString()}
62
+ ${delay.toString()},
63
+ ${orderingKey}
58
64
  );
59
65
  `;
60
- logger.debug("Enqueued a message.", { message });
66
+ logger.debug("Enqueued a message.", {
67
+ message,
68
+ orderingKey
69
+ });
61
70
  await this.#sql.notify(this.#channelName, delay.toString());
62
71
  logger.debug("Notified the message queue channel {channelName}.", {
63
72
  channelName: this.#channelName,
64
- message
73
+ message,
74
+ orderingKey
65
75
  });
66
76
  }
67
77
  async enqueueMany(messages, options) {
68
78
  if (messages.length === 0) return;
69
79
  await this.initialize();
70
80
  const delay = options?.delay ?? Temporal.Duration.from({ seconds: 0 });
81
+ const orderingKey = options?.orderingKey ?? null;
71
82
  if (options?.delay) logger.debug("Enqueuing messages with a delay of {delay}...", {
72
83
  delay,
73
- messages
84
+ messages,
85
+ orderingKey
86
+ });
87
+ else logger.debug("Enqueuing messages...", {
88
+ messages,
89
+ orderingKey
74
90
  });
75
- else logger.debug("Enqueuing messages...", { messages });
76
91
  for (const message of messages) await this.#sql`
77
- INSERT INTO ${this.#sql(this.#tableName)} (message, delay)
92
+ INSERT INTO ${this.#sql(this.#tableName)} (message, delay, ordering_key)
78
93
  VALUES (
79
94
  ${this.#json(message)},
80
- ${delay.toString()}
95
+ ${delay.toString()},
96
+ ${orderingKey}
81
97
  );
82
98
  `;
83
- logger.debug("Enqueued messages.", { messages });
99
+ logger.debug("Enqueued messages.", {
100
+ messages,
101
+ orderingKey
102
+ });
84
103
  await this.#sql.notify(this.#channelName, delay.toString());
85
104
  logger.debug("Notified the message queue channel {channelName}.", {
86
105
  channelName: this.#channelName,
87
- messages
106
+ messages,
107
+ orderingKey
88
108
  });
89
109
  }
90
110
  async listen(handler, options = {}) {
@@ -92,27 +112,77 @@ var PostgresMessageQueue = class {
92
112
  const { signal } = options;
93
113
  const poll = async () => {
94
114
  while (!signal?.aborted) {
115
+ let processed = false;
95
116
  const query = this.#sql`
96
- DELETE FROM ${this.#sql(this.#tableName)}
97
- WHERE id = (
98
- SELECT id
117
+ WITH candidate AS (
118
+ SELECT id, ordering_key
99
119
  FROM ${this.#sql(this.#tableName)}
100
120
  WHERE created + delay < CURRENT_TIMESTAMP
121
+ AND ordering_key IS NULL
101
122
  ORDER BY created
102
123
  LIMIT 1
124
+ FOR UPDATE SKIP LOCKED
103
125
  )
104
- RETURNING message;
126
+ DELETE FROM ${this.#sql(this.#tableName)}
127
+ WHERE id IN (SELECT id FROM candidate)
128
+ RETURNING message, ordering_key;
105
129
  `.execute();
106
130
  const cancel = query.cancel.bind(query);
107
131
  signal?.addEventListener("abort", cancel);
108
- let i = 0;
109
- for (const message of await query) {
132
+ for (const row of await query) {
110
133
  if (signal?.aborted) return;
111
- await handler(message.message);
112
- i++;
134
+ await handler(row.message);
135
+ processed = true;
113
136
  }
114
137
  signal?.removeEventListener("abort", cancel);
115
- if (i < 1) break;
138
+ if (processed) continue;
139
+ const attemptedOrderingKeys = /* @__PURE__ */ new Set();
140
+ while (!signal?.aborted) {
141
+ const candidateResult = await this.#sql`
142
+ SELECT id, ordering_key
143
+ FROM ${this.#sql(this.#tableName)}
144
+ WHERE created + delay < CURRENT_TIMESTAMP
145
+ AND ordering_key IS NOT NULL
146
+ ${attemptedOrderingKeys.size > 0 ? this.#sql`AND ordering_key NOT IN ${this.#sql([...attemptedOrderingKeys])}` : this.#sql``}
147
+ ORDER BY created
148
+ LIMIT 1
149
+ `;
150
+ if (candidateResult.length === 0) break;
151
+ const candidate = candidateResult[0];
152
+ const candidateId = candidate.id;
153
+ const orderingKey = candidate.ordering_key;
154
+ 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;
172
+ }
173
+ } finally {
174
+ await this.#sql`
175
+ SELECT pg_advisory_unlock(
176
+ hashtext(${this.#tableName}),
177
+ hashtext(${orderingKey})
178
+ )
179
+ `;
180
+ }
181
+ if (processed) break;
182
+ }
183
+ }
184
+ if (processed) continue;
185
+ break;
116
186
  }
117
187
  };
118
188
  const timeouts = /* @__PURE__ */ new Set();
@@ -156,8 +226,13 @@ var PostgresMessageQueue = class {
156
226
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
157
227
  message jsonb NOT NULL,
158
228
  delay interval DEFAULT '0 seconds',
159
- created timestamp with time zone DEFAULT CURRENT_TIMESTAMP
229
+ created timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
230
+ ordering_key text
160
231
  );
232
+ `;
233
+ await this.#sql`
234
+ ALTER TABLE ${this.#sql(this.#tableName)}
235
+ ADD COLUMN IF NOT EXISTS ordering_key text;
161
236
  `;
162
237
  } catch (error) {
163
238
  if (!(error instanceof postgres.PostgresError && error.constraint_name === "pg_type_typname_nsp_index")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/postgres",
3
- "version": "2.0.0-pr.490.2+99a396d5",
3
+ "version": "2.0.0-pr.559.4+6357309b",
4
4
  "description": "PostgreSQL drivers for Fedify",
5
5
  "keywords": [
6
6
  "fedify",
@@ -70,21 +70,26 @@
70
70
  ],
71
71
  "dependencies": {
72
72
  "@js-temporal/polyfill": "^0.5.1",
73
- "@logtape/logtape": "^1.2.2"
73
+ "@logtape/logtape": "^2.0.0"
74
74
  },
75
75
  "peerDependencies": {
76
76
  "postgres": "^3.4.7",
77
- "@fedify/fedify": "^2.0.0-pr.490.2+99a396d5"
77
+ "@fedify/fedify": "^2.0.0-pr.559.4+6357309b"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@std/async": "npm:@jsr/std__async@^1.0.13",
81
81
  "tsdown": "^0.12.9",
82
- "typescript": "^5.9.3"
82
+ "typescript": "^5.9.3",
83
+ "@fedify/fixture": "^2.0.0",
84
+ "@fedify/testing": "^2.0.0-pr.559.4+6357309b"
83
85
  },
84
86
  "scripts": {
85
- "build": "tsdown",
86
- "prepublish": "tsdown",
87
- "test": "tsdown && node --experimental-transform-types --test",
88
- "test:bun": "tsdown && bun test --timeout=10000"
87
+ "build:self": "tsdown",
88
+ "build": "pnpm --filter @fedify/postgres... run build:self",
89
+ "prepublish": "pnpm build",
90
+ "pretest": "pnpm build",
91
+ "test": "node --experimental-transform-types --test",
92
+ "pretest:bun": "pnpm build",
93
+ "test:bun": "bun test --timeout=10000"
89
94
  }
90
95
  }