lonnymq 0.0.6 → 0.0.8
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/README.md +138 -63
- package/dist/index.cjs +51 -41
- package/dist/index.d.ts +3 -1
- package/dist/index.js +51 -41
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# LonnyMQ
|
|
2
2
|
|
|
3
|
-
A high
|
|
3
|
+
A high-performance, multi-tenant Postgres message queue implementation for Node.js/TypeScript.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
- High throughput
|
|
8
|
+
- Multi-tenant concurrency and capacity constraints
|
|
9
|
+
- Durable message processing
|
|
10
|
+
- Flexible message processing retries/deferrals
|
|
11
|
+
- Message de-duplication
|
|
12
|
+
- Queue actions as part of *existing* database transactions
|
|
13
|
+
- Database client agnostic
|
|
14
|
+
- Instant reactivity via `LISTEN/NOTIFY`
|
|
15
|
+
- Zero dependencies
|
|
16
16
|
|
|
17
|
-
N.B.
|
|
17
|
+
N.B. Unlike other queue implementations, LonnyMQ provides direct access to queue methods rather than providing batteries-included Worker/Processor daemons.
|
|
18
18
|
|
|
19
19
|
## Quick Look
|
|
20
20
|
|
|
@@ -27,59 +27,84 @@ databaseClient satisfies DatabaseClient
|
|
|
27
27
|
|
|
28
28
|
const queue = new Queue({ schema: "lonny" })
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
// Run migrations first
|
|
31
|
+
for (const migration of queue.migrations()) {
|
|
32
|
+
for (const sql of migration.sql) {
|
|
33
|
+
await databaseClient.query(sql, [])
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Create messages
|
|
38
|
+
for (let ix = 0; ix < 500; ix += 1) {
|
|
39
|
+
await queue
|
|
32
40
|
.channel("myChannel")
|
|
33
41
|
.message
|
|
34
42
|
.create({
|
|
35
|
-
content: "Hello",
|
|
43
|
+
content: Buffer.from("Hello"),
|
|
36
44
|
databaseClient,
|
|
37
45
|
})
|
|
38
46
|
}
|
|
39
47
|
|
|
40
|
-
|
|
48
|
+
// Process messages
|
|
49
|
+
while (true) {
|
|
41
50
|
const dequeueResult = await queue.dequeue({ databaseClient })
|
|
42
|
-
if(dequeueResult.resultType === "MESSAGE_NOT_AVAILABLE") {
|
|
43
|
-
|
|
51
|
+
if (dequeueResult.resultType === "MESSAGE_NOT_AVAILABLE") {
|
|
52
|
+
const sleepMs = Math.min(1000, dequeueResult.retryMs ?? 5000)
|
|
53
|
+
await new Promise(resolve => setTimeout(resolve, sleepMs))
|
|
44
54
|
continue
|
|
45
55
|
}
|
|
46
56
|
|
|
47
|
-
console.log(dequeueResult.message.content)
|
|
57
|
+
console.log(dequeueResult.message.content.toString())
|
|
48
58
|
await dequeueResult.message.delete({ databaseClient })
|
|
49
59
|
}
|
|
50
60
|
```
|
|
51
61
|
|
|
52
62
|
## Setup & Installation
|
|
53
63
|
|
|
54
|
-
LonnyMQ can be installed from npm
|
|
64
|
+
LonnyMQ can be installed from npm:
|
|
55
65
|
|
|
56
66
|
```bash
|
|
57
67
|
npm install lonnymq
|
|
58
68
|
```
|
|
59
69
|
|
|
60
|
-
Once the package is installed,
|
|
70
|
+
Once the package is installed, you need to install the requisite database machinery. LonnyMQ is agnostic to database client and migration process, providing users with an ordered list of "Migrations" - each containing a unique name and SQL fragments to be executed.
|
|
61
71
|
|
|
62
72
|
```typescript
|
|
63
73
|
const queue = new Queue({ schema: "lonny" })
|
|
64
74
|
const migrations = queue.migrations()
|
|
75
|
+
|
|
76
|
+
// Execute migrations (in a transaction for safety)
|
|
77
|
+
await databaseClient.query("BEGIN")
|
|
78
|
+
try {
|
|
79
|
+
for (const migration of migrations) {
|
|
80
|
+
for (const sql of migration.sql) {
|
|
81
|
+
await databaseClient.query(sql, [])
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
await databaseClient.query("COMMIT")
|
|
85
|
+
} catch (error) {
|
|
86
|
+
await databaseClient.query("ROLLBACK")
|
|
87
|
+
throw error
|
|
88
|
+
}
|
|
65
89
|
```
|
|
66
90
|
|
|
67
|
-
|
|
91
|
+
**Note:** Migration SQL is not idempotent and should be executed within a transaction that can be rolled back if needed.
|
|
68
92
|
|
|
69
93
|
## Channels
|
|
70
94
|
|
|
71
|
-
Channels
|
|
95
|
+
Channels provide LonnyMQ's multi-tenancy support. They can be considered lightweight sub-queues that are read from in a round-robin fashion. There is no performance penalty associated with using large numbers of channels, so they can be assigned on a highly granular basis (e.g., per-user) to ensure work is scheduled fairly.
|
|
72
96
|
|
|
73
|
-
Channels can be configured with concurrency and
|
|
97
|
+
Channels can be configured with concurrency, capacity, and rate limits by setting their "channel policy":
|
|
74
98
|
|
|
75
99
|
```typescript
|
|
76
100
|
await queue
|
|
77
101
|
.channel("my-channel")
|
|
78
102
|
.policy
|
|
79
103
|
.set({
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
104
|
+
databaseClient,
|
|
105
|
+
maxConcurrency: 1,
|
|
106
|
+
maxSize: 100,
|
|
107
|
+
releaseIntervalMs: 1000
|
|
83
108
|
})
|
|
84
109
|
|
|
85
110
|
// Remove all constraints:
|
|
@@ -91,7 +116,7 @@ await queue
|
|
|
91
116
|
|
|
92
117
|
## Message creation
|
|
93
118
|
|
|
94
|
-
|
|
119
|
+
You can add a message to the queue (and assign it to a particular channel) using the `create` function:
|
|
95
120
|
|
|
96
121
|
```typescript
|
|
97
122
|
await queue
|
|
@@ -99,82 +124,132 @@ await queue
|
|
|
99
124
|
.message
|
|
100
125
|
.create({
|
|
101
126
|
databaseClient,
|
|
102
|
-
content: "Hello, world"
|
|
127
|
+
content: Buffer.from("Hello, world"),
|
|
128
|
+
name: "optional-dedup-key",
|
|
129
|
+
lockMs: 30000, // 30 seconds
|
|
130
|
+
delayMs: 5000 // 5 second delay
|
|
103
131
|
})
|
|
104
132
|
```
|
|
105
133
|
|
|
106
|
-
|
|
134
|
+
The `name` argument can be provided for de-duplication purposes: if a message that has *never* been dequeued exists with the same name within the same channel, no new message will be created.
|
|
135
|
+
|
|
136
|
+
## Message Processing
|
|
107
137
|
|
|
108
|
-
|
|
138
|
+
Messages can be fetched for processing by calling `dequeue` on the `Queue` - this locks the message. Once processing has completed, messages must be "finalized" via **deletion** or **deferral** (for further processing in the future).
|
|
109
139
|
|
|
110
|
-
|
|
140
|
+
```typescript
|
|
141
|
+
const dequeueResult = await queue.dequeue({ databaseClient })
|
|
142
|
+
|
|
143
|
+
if (dequeueResult.resultType === "MESSAGE_DEQUEUED") {
|
|
144
|
+
const { message } = dequeueResult
|
|
145
|
+
console.log(`Processing message: ${message.id}`)
|
|
146
|
+
console.log(`Content: ${message.content.toString()}`)
|
|
147
|
+
console.log(`Attempts: ${message.numAttempts}`)
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
// Process the message...
|
|
151
|
+
await processMessage(message.content)
|
|
152
|
+
|
|
153
|
+
// Delete on success
|
|
154
|
+
await message.delete({ databaseClient })
|
|
155
|
+
} catch (error) {
|
|
156
|
+
// Defer for retry with updated state
|
|
157
|
+
await message.defer({
|
|
158
|
+
databaseClient,
|
|
159
|
+
delays: 30000, // Retry in 30 seconds
|
|
160
|
+
state: Buffer.from(JSON.stringify({ error: error.message }))
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
111
165
|
|
|
112
|
-
When deferring a message,
|
|
166
|
+
When deferring a message, you can optionally specify `delayMs` and `state` arguments. The `delayMs` parameter tells the queue how long to wait before allowing the message to be re-processed, and `state` allows you to "save your work" and implement durable and/or repeating/scheduled tasks.
|
|
113
167
|
|
|
114
|
-
### Graceful
|
|
168
|
+
### Graceful Shutdowns and Message Sweeping
|
|
115
169
|
|
|
116
|
-
If your program ends unexpectedly, messages that are
|
|
170
|
+
If your program ends unexpectedly, messages that are currently being processed may become "orphaned" in a locked state - causing channel blockages and reducing throughput. To mitigate this problem, it's imperative that you gracefully shutdown by catching unhandled exceptions and signals (i.e., `SIGINT`/`SIGTERM`) and finalizing all outstanding messages prior to exiting.
|
|
117
171
|
|
|
118
|
-
That said, despite our best efforts,
|
|
172
|
+
That said, despite our best efforts, if we run out of memory, suffer a loss of power, or receive a `SIGKILL`, we will be unable to finalize messages that are currently locked. To mitigate this, we set a `lockMs` during message creation (by default this is 1 hour) which limits the maximum amount of time a message can be locked before being available again for dequeue. This facility ensures that no matter the nature of the shutdown, the queue will always recover automatically.
|
|
119
173
|
|
|
120
|
-
## Improvements on
|
|
174
|
+
## Improvements on Polling
|
|
121
175
|
|
|
122
|
-
The simplest approach for processing messages is to call `dequeue` in a loop,
|
|
176
|
+
The simplest approach for processing messages is to call `dequeue` in a loop, backing off with a sleep when no messages are available. The downside with this approach is that we lose reactivity as we increase the polling timeout interval.
|
|
123
177
|
|
|
124
|
-
To improve reactivity,
|
|
178
|
+
To improve reactivity, you can use the `retryMs` returned when failing to dequeue a message. This will either be `null` or tell you how long until the next message is available for processing (a message might be deferred for a period of time). Thus, you can tune your sleep to use the minimum of your `retryMs` and a default poll timeout.
|
|
125
179
|
|
|
126
|
-
Unfortunately, this doesn't help
|
|
180
|
+
Unfortunately, this doesn't help in situations where a message is created/deferred while a worker is sleeping. However, if you deploy LonnyMQ with the `useWake` parameter enabled, message creations and deferrals will trigger a payload to the `queue.wakeChannel()` Postgres channel with the number of milliseconds until said message becomes available for processing encoded as a string payload.
|
|
127
181
|
|
|
128
182
|
```typescript
|
|
129
183
|
const queue = new Queue({ schema: "lonny" })
|
|
130
184
|
const migrations = queue.migrations({ useWake: true })
|
|
131
185
|
|
|
186
|
+
// Run migrations with wake enabled...
|
|
187
|
+
|
|
132
188
|
// LISTEN/NOTIFY only works with a single connection - not on a connection pool.
|
|
133
|
-
const client = await
|
|
134
|
-
await client.query(`LISTEN "${
|
|
189
|
+
const client = await databaseClient.connect()
|
|
190
|
+
await client.query(`LISTEN "${queue.wakeChannel()}"`)
|
|
135
191
|
client.on("notification", (msg) => {
|
|
136
192
|
if (msg.channel === queue.wakeChannel()) {
|
|
137
193
|
const deferMs = parseInt(msg.payload as string, 10)
|
|
138
194
|
console.log(`Should wake in ${deferMs} ms`)
|
|
195
|
+
// Wake up your worker loop here
|
|
139
196
|
}
|
|
140
197
|
})
|
|
141
198
|
```
|
|
142
199
|
|
|
143
200
|
## Deadlocks
|
|
144
201
|
|
|
145
|
-
If all queue actions are isolated to their own transaction, there is
|
|
202
|
+
If all queue actions are isolated to their own transaction, there is zero risk of deadlocks occurring. That being said, it is *possible* to safely bulk-perform the following actions within a single transaction if we ensure they are performed in a consistent lexicographical ordering with respect to channel name and message name (if provided):
|
|
146
203
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
204
|
+
- Message create
|
|
205
|
+
- Channel policy set
|
|
206
|
+
- Channel policy clear
|
|
150
207
|
|
|
151
208
|
Beyond the actions specified above, it is manifestly **unsafe** to bulk-perform any of the remaining actions within a single transaction. Each of these actions should be isolated within their **own** transaction:
|
|
152
209
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
210
|
+
- Message dequeue
|
|
211
|
+
- Message defer
|
|
212
|
+
- Message delete
|
|
213
|
+
|
|
214
|
+
## Batching Operations
|
|
156
215
|
|
|
157
|
-
|
|
216
|
+
When you need to perform multiple safe operations (message creation and channel policy changes) within a single transaction, LonnyMQ provides a batching mechanism that automatically handles proper ordering to prevent deadlocks.
|
|
217
|
+
|
|
218
|
+
The batch interface mirrors the main queue interface - you call `queue.batch()` to create a batch, then use the same `.channel(name).message.create()` and `.channel(name).policy.set/clear()` methods you're already familiar with. The key difference is that batch operations are queued up and don't execute immediately.
|
|
219
|
+
|
|
220
|
+
The batch system ensures that all operations are executed in a consistent lexicographical order based on channel name and message name, eliminating the possibility of deadlocks when multiple workers are performing bulk operations simultaneously.
|
|
158
221
|
|
|
159
222
|
```typescript
|
|
160
223
|
const batch = queue.batch()
|
|
161
|
-
```
|
|
162
224
|
|
|
163
|
-
|
|
225
|
+
batch.channel("user-123").message.create({
|
|
226
|
+
content: Buffer.from("Welcome email")
|
|
227
|
+
})
|
|
164
228
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
229
|
+
batch.channel("user-123").policy.set({
|
|
230
|
+
maxConcurrency: 5,
|
|
231
|
+
maxSize: 1000,
|
|
232
|
+
releaseIntervalMs: 100
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
batch.channel("notifications").message.create({
|
|
236
|
+
content: Buffer.from("Daily digest"),
|
|
237
|
+
name: "daily-digest-2025-08-29"
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
batch.channel("analytics").policy.clear()
|
|
174
241
|
|
|
175
|
-
```typescript
|
|
176
242
|
await batch.execute({ databaseClient })
|
|
243
|
+
```
|
|
177
244
|
|
|
178
|
-
|
|
179
|
-
|
|
245
|
+
## Database Clients
|
|
246
|
+
|
|
247
|
+
LonnyMQ is designed to be database client agnostic, requiring only a minimal interface that most PostgreSQL clients already implement. Your database client must provide a single `query` method with this signature:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
interface DatabaseClient {
|
|
251
|
+
query(sql: string, params: Array<unknown>): Promise<{
|
|
252
|
+
rows: Array<Record<string, unknown>>
|
|
253
|
+
}>
|
|
254
|
+
}
|
|
180
255
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=Object.prototype.hasOwnProperty;var
|
|
1
|
+
var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=Object.prototype.hasOwnProperty;var y=new WeakMap,J=(e)=>{var n=y.get(e),s;if(n)return n;if(n=d({},"__esModule",{value:!0}),e&&typeof e==="object"||typeof e==="function")V(e).map((_)=>!K.call(n,_)&&d(n,_,{get:()=>e[_],enumerable:!(s=X(e,_))||s.enumerable}));return y.set(e,n),n};var j=(e,n)=>{for(var s in n)d(e,s,{get:n[s],enumerable:!0,configurable:!0,set:(_)=>n[s]=()=>_})};var re={};j(re,{Queue:()=>x,MessageDequeueCommand:()=>i,MessageDeleteCommand:()=>l,MessageDeferCommand:()=>h,MessageCreateCommand:()=>o,ChannelPolicySetCommand:()=>m,ChannelPolicyClearCommand:()=>r});module.exports=J(re);var E=(e)=>({nodeType:"VALUE",value:e}),t=(e)=>({nodeType:"REF",value:e}),Z=(e)=>({nodeType:"RAW",value:e}),ee=(e)=>{return`'${e.replace(/'/g,"''")}'`},ne=(e)=>{if(e===null)return"NULL";else if(typeof e==="string")return ee(e);else if(typeof e==="number")return e.toString();else if(typeof e==="boolean")return e?"TRUE":"FALSE";else if(e instanceof Date)return`'${e.toISOString()}'`;else if(typeof e==="bigint")return e.toString();else throw new Error(`Unsupported value type: ${typeof e}`)},te=(e)=>{return`"${e.replace(/"/g,'""')}"`},se=(e)=>{if(e.nodeType==="VALUE")return ne(e.value);else if(e.nodeType==="REF")return te(e.value);else if(e.nodeType==="RAW")return e.value;else throw new Error("Unsupported SQL node type")};var a=(e,...n)=>{let s=[];for(let _=0;_<e.length;_+=1)if(s.push(e[_]),_<n.length)s.push(se(n[_]));return Z(s.join(""))};class r{schema;channelName;createdAt;constructor(e){this.schema=e.schema,this.channelName=e.channelName,this.createdAt=new Date}sortKeyGet(){return JSON.stringify([this.channelName,null,this.createdAt.toISOString()])}async execute(e){await e.query(a`
|
|
2
2
|
SELECT 1 FROM ${t(this.schema)}."channel_policy_clear"(
|
|
3
3
|
$1
|
|
4
4
|
)
|
|
5
|
-
`.value,[this.channelName])}}class m{schema;channelName;maxSize;maxConcurrency;releaseIntervalMs;createdAt;constructor(e){this.schema=e.schema,this.channelName=e.channelName;let n=e.maxConcurrency??null;this.maxConcurrency=n!==null?Math.max(0,n):null;let
|
|
5
|
+
`.value,[this.channelName])}}class m{schema;channelName;maxSize;maxConcurrency;releaseIntervalMs;createdAt;constructor(e){this.schema=e.schema,this.channelName=e.channelName;let n=e.maxConcurrency??null;this.maxConcurrency=n!==null?Math.max(0,n):null;let s=e.maxSize??null;this.maxSize=s!==null?Math.max(0,s):null;let _=e.releaseIntervalMs??null;this.releaseIntervalMs=_!==null?Math.max(0,_):null,this.createdAt=new Date}sortKeyGet(){return JSON.stringify([this.channelName,null,this.createdAt.toISOString()])}async execute(e){await e.query(a`
|
|
6
6
|
SELECT 1 FROM ${t(this.schema)}."channel_policy_set"(
|
|
7
7
|
$1,
|
|
8
8
|
$2::INTEGER,
|
|
9
9
|
$3::INTEGER,
|
|
10
10
|
$4::INTEGER
|
|
11
11
|
)
|
|
12
|
-
`.value,[this.channelName,this.maxSize,this.maxConcurrency,this.releaseIntervalMs])}}var R=(e)=>{return e*1000},
|
|
12
|
+
`.value,[this.channelName,this.maxSize,this.maxConcurrency,this.releaseIntervalMs])}}var R=(e)=>{return e*1000},ae=(e)=>{return R(e*60)},f=(e)=>{return ae(e*60)};var C=require("node:crypto");class p{value;constructor(e){this.value=e}toString(e){return C.createHash("sha256").update(e).update(this.value).digest("base64").replace(/=/g,"")}}var u=new p("WAKE"),q=!1,N=R(0),$=f(1);var g=require("path"),__filename="/home/runner/work/lonnymq/lonnymq/src/core/path.ts",_e=g.dirname(g.dirname(__filename)),ce=new RegExp(`^${_e}/`),c=(e)=>{return e.replace(ce,"")};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/04-function-message-create.ts";var F={name:c(__filename),sql:(e)=>{return[a`
|
|
13
13
|
CREATE FUNCTION ${t(e.schema)}."message_create" (
|
|
14
14
|
p_id UUID,
|
|
15
15
|
p_channel_name TEXT,
|
|
@@ -65,7 +65,8 @@ var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=
|
|
|
65
65
|
"max_size",
|
|
66
66
|
"max_concurrency",
|
|
67
67
|
"message_next_id",
|
|
68
|
-
"message_next_dequeue_after"
|
|
68
|
+
"message_next_dequeue_after",
|
|
69
|
+
"message_next_seq_no"
|
|
69
70
|
INTO v_channel_state;
|
|
70
71
|
|
|
71
72
|
IF v_channel_state."current_size" >= v_channel_policy."max_size" THEN
|
|
@@ -95,6 +96,7 @@ var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=
|
|
|
95
96
|
"name" = EXCLUDED."name"
|
|
96
97
|
RETURNING
|
|
97
98
|
"id",
|
|
99
|
+
"seq_no",
|
|
98
100
|
"dequeue_after"
|
|
99
101
|
INTO v_message;
|
|
100
102
|
|
|
@@ -106,12 +108,14 @@ var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=
|
|
|
106
108
|
|
|
107
109
|
IF
|
|
108
110
|
v_channel_state."message_next_id" IS NULL OR
|
|
109
|
-
v_channel_state."message_next_dequeue_after" > v_message."dequeue_after"
|
|
111
|
+
v_channel_state."message_next_dequeue_after" > v_message."dequeue_after" OR
|
|
112
|
+
(v_channel_state."message_next_dequeue_after" = v_message."dequeue_after" AND v_channel_state."message_next_seq_no" > v_message."seq_no")
|
|
110
113
|
THEN
|
|
111
114
|
UPDATE ${t(e.schema)}."channel_state" SET
|
|
112
115
|
"current_size" = v_channel_state."current_size" + 1,
|
|
113
116
|
"message_next_id" = v_message."id",
|
|
114
|
-
"message_next_dequeue_after" = GREATEST(v_now, v_message."dequeue_after")
|
|
117
|
+
"message_next_dequeue_after" = GREATEST(v_now, v_message."dequeue_after"),
|
|
118
|
+
"message_next_seq_no" = v_message."seq_no"
|
|
115
119
|
WHERE "id" = v_channel_state."id";
|
|
116
120
|
ELSE
|
|
117
121
|
UPDATE ${t(e.schema)}."channel_state" SET
|
|
@@ -126,7 +130,7 @@ var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=
|
|
|
126
130
|
RETURN;
|
|
127
131
|
END;
|
|
128
132
|
$$ LANGUAGE plpgsql;
|
|
129
|
-
`]}};var M=require("node:crypto");class o{schema;channelName;name;content;lockMs;id;delayMs;createdAt;constructor(e){let n=e.name??null,
|
|
133
|
+
`]}};var M=require("node:crypto");class o{schema;channelName;name;content;lockMs;id;delayMs;createdAt;constructor(e){let n=e.name??null,s=e.lockMs===void 0?$:Math.max(0,e.lockMs),_=e.delayMs===void 0?N:e.delayMs;this.id=M.randomUUID(),this.schema=e.schema,this.channelName=e.channelName,this.content=e.content,this.name=n,this.lockMs=s,this.delayMs=_,this.createdAt=new Date}async execute(e){let n=await e.query(a`
|
|
130
134
|
SELECT * FROM ${t(this.schema)}."message_create"(
|
|
131
135
|
$1,
|
|
132
136
|
$2,
|
|
@@ -135,7 +139,7 @@ var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=
|
|
|
135
139
|
$5::INTEGER,
|
|
136
140
|
$6::INTEGER
|
|
137
141
|
)
|
|
138
|
-
`.value,[this.id,this.channelName,this.name,this.content,this.lockMs,this.delayMs]).then((
|
|
142
|
+
`.value,[this.id,this.channelName,this.name,this.content,this.lockMs,this.delayMs]).then((s)=>s.rows[0]);if(n.result_code===1)return{resultType:"MESSAGE_DROPPED"};else if(n.result_code===2)return{resultType:"MESSAGE_DEDUPLICATED"};else if(n.result_code===0)return{resultType:"MESSAGE_CREATED"};else throw new Error("Unexpected result")}}var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/05-function-message-dequeue.ts";var G={name:c(__filename),sql:(e)=>{return[a`
|
|
139
143
|
CREATE FUNCTION ${t(e.schema)}."message_dequeue" ()
|
|
140
144
|
RETURNS TABLE (
|
|
141
145
|
result_code INTEGER,
|
|
@@ -181,14 +185,12 @@ var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=
|
|
|
181
185
|
|
|
182
186
|
RETURN QUERY SELECT
|
|
183
187
|
${E(1)},
|
|
184
|
-
|
|
185
|
-
|
|
188
|
+
v_message_locked.content,
|
|
189
|
+
v_message_locked.state,
|
|
186
190
|
JSON_BUILD_OBJECT(
|
|
187
191
|
'id', v_message_locked.id,
|
|
188
192
|
'channel_name', v_message_locked.channel_name,
|
|
189
|
-
'
|
|
190
|
-
'name', v_message_locked.name
|
|
191
|
-
'content', v_message_locked.content,
|
|
193
|
+
'name', v_message_locked.name,
|
|
192
194
|
'num_attempts', v_message_locked.num_attempts
|
|
193
195
|
);
|
|
194
196
|
RETURN;
|
|
@@ -248,11 +250,12 @@ var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=
|
|
|
248
250
|
|
|
249
251
|
SELECT
|
|
250
252
|
"message"."id",
|
|
251
|
-
"message"."dequeue_after"
|
|
253
|
+
"message"."dequeue_after",
|
|
254
|
+
"message"."seq_no"
|
|
252
255
|
FROM ${t(e.schema)}."message"
|
|
253
256
|
WHERE NOT "is_locked"
|
|
254
257
|
AND "channel_name" = v_message_dequeue."channel_name"
|
|
255
|
-
ORDER BY "dequeue_after" ASC, "
|
|
258
|
+
ORDER BY "dequeue_after" ASC, "seq_no" ASC
|
|
256
259
|
LIMIT 1
|
|
257
260
|
INTO v_message_next_dequeue;
|
|
258
261
|
|
|
@@ -265,7 +268,8 @@ var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=
|
|
|
265
268
|
UPDATE ${t(e.schema)}."channel_state" SET
|
|
266
269
|
"current_concurrency" = v_channel_state."current_concurrency" + 1,
|
|
267
270
|
"message_next_id" = v_message_next_dequeue."id",
|
|
268
|
-
"message_next_dequeue_after" = v_message_next_dequeue_after
|
|
271
|
+
"message_next_dequeue_after" = v_message_next_dequeue_after,
|
|
272
|
+
"message_next_seq_no" = v_message_next_dequeue."seq_no"
|
|
269
273
|
WHERE "id" = v_channel_state."id";
|
|
270
274
|
ELSE
|
|
271
275
|
UPDATE ${t(e.schema)}."channel_state" SET
|
|
@@ -288,14 +292,14 @@ var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=
|
|
|
288
292
|
RETURN;
|
|
289
293
|
END;
|
|
290
294
|
$$ LANGUAGE plpgsql;
|
|
291
|
-
`]}};class i{schema;constructor(e){this.schema=e.schema}async execute(e){let n=await e.query(
|
|
295
|
+
`]}};class i{schema;constructor(e){this.schema=e.schema}async execute(e){let n=await e.query(a`
|
|
292
296
|
SELECT
|
|
293
297
|
result_code,
|
|
294
298
|
metadata,
|
|
295
299
|
content,
|
|
296
300
|
state
|
|
297
301
|
FROM ${t(this.schema)}."message_dequeue"()
|
|
298
|
-
`.value,[]).then((
|
|
302
|
+
`.value,[]).then((s)=>s.rows[0]);if(n.result_code===0)return{resultType:"MESSAGE_NOT_AVAILABLE",retryMs:n.metadata.retry_ms};else if(n.result_code===1)return{resultType:"MESSAGE_DEQUEUED",message:{id:n.metadata.id,channelName:n.metadata.channel_name,name:n.metadata.name,content:n.content,state:n.state,numAttempts:n.metadata.num_attempts}};else throw new Error("Unexpected dequeue result")}}var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/06-function-message-delete.ts";var B={name:c(__filename),sql:(e)=>{return[a`
|
|
299
303
|
CREATE FUNCTION ${t(e.schema)}."message_delete" (
|
|
300
304
|
p_id UUID
|
|
301
305
|
)
|
|
@@ -360,11 +364,11 @@ var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=
|
|
|
360
364
|
RETURN;
|
|
361
365
|
END;
|
|
362
366
|
$$ LANGUAGE plpgsql;
|
|
363
|
-
`]}};class l{schema;id;constructor(e){this.schema=e.schema,this.id=e.id}async execute(e){let n=await e.query(
|
|
367
|
+
`]}};class l{schema;id;constructor(e){this.schema=e.schema,this.id=e.id}async execute(e){let n=await e.query(a`
|
|
364
368
|
SELECT * FROM ${t(this.schema)}."message_delete"(
|
|
365
369
|
$1
|
|
366
370
|
)
|
|
367
|
-
`.value,[this.id]).then((
|
|
371
|
+
`.value,[this.id]).then((s)=>s.rows[0]);if(n.result_code===0)return{resultType:"MESSAGE_NOT_FOUND"};else if(n.result_code===1)return{resultType:"STATE_INVALID"};else if(n.result_code===2)return{resultType:"MESSAGE_DELETED"};else throw new Error("Unexpected result")}}var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/07-function-message-defer.ts";var P={name:c(__filename),sql:(e)=>{return[a`
|
|
368
372
|
CREATE FUNCTION ${t(e.schema)}."message_defer" (
|
|
369
373
|
p_id UUID,
|
|
370
374
|
p_delay_ms INTEGER,
|
|
@@ -381,7 +385,8 @@ var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=
|
|
|
381
385
|
SELECT
|
|
382
386
|
"message"."id",
|
|
383
387
|
"message"."channel_name",
|
|
384
|
-
"message"."is_locked"
|
|
388
|
+
"message"."is_locked",
|
|
389
|
+
"message"."seq_no"
|
|
385
390
|
FROM ${t(e.schema)}."message"
|
|
386
391
|
WHERE "id" = p_id
|
|
387
392
|
FOR UPDATE
|
|
@@ -400,7 +405,8 @@ var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=
|
|
|
400
405
|
SELECT
|
|
401
406
|
"channel_state"."current_concurrency",
|
|
402
407
|
"channel_state"."message_next_id",
|
|
403
|
-
"channel_state"."message_next_dequeue_after"
|
|
408
|
+
"channel_state"."message_next_dequeue_after",
|
|
409
|
+
"channel_state"."message_next_seq_no"
|
|
404
410
|
FROM ${t(e.schema)}."channel_state"
|
|
405
411
|
WHERE "name" = v_message."channel_name"
|
|
406
412
|
FOR UPDATE
|
|
@@ -410,12 +416,14 @@ var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=
|
|
|
410
416
|
|
|
411
417
|
IF
|
|
412
418
|
v_channel_state."message_next_id" IS NULL OR
|
|
413
|
-
v_channel_state."message_next_dequeue_after" > v_dequeue_after
|
|
419
|
+
v_channel_state."message_next_dequeue_after" > v_dequeue_after OR
|
|
420
|
+
(v_channel_state."message_next_dequeue_after" = v_dequeue_after AND v_channel_state."message_next_seq_no" > v_message."seq_no")
|
|
414
421
|
THEN
|
|
415
422
|
UPDATE ${t(e.schema)}."channel_state" SET
|
|
416
423
|
"current_concurrency" = v_channel_state."current_concurrency" - 1,
|
|
417
424
|
"message_next_id" = v_message."id",
|
|
418
|
-
"message_next_dequeue_after" = v_dequeue_after
|
|
425
|
+
"message_next_dequeue_after" = v_dequeue_after,
|
|
426
|
+
"message_next_seq_no" = v_message."seq_no"
|
|
419
427
|
WHERE "name" = v_message."channel_name";
|
|
420
428
|
ELSE
|
|
421
429
|
UPDATE ${t(e.schema)}."channel_state" SET
|
|
@@ -436,15 +444,15 @@ var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=
|
|
|
436
444
|
RETURN;
|
|
437
445
|
END;
|
|
438
446
|
$$ LANGUAGE plpgsql;
|
|
439
|
-
`]}};class h{schema;id;delayMs;state;constructor(e){let n=e.delayMs===void 0?N:e.delayMs;this.schema=e.schema,this.id=e.id,this.delayMs=n,this.state=e.state??null}async execute(e){let n=await e.query(
|
|
447
|
+
`]}};class h{schema;id;delayMs;state;constructor(e){let n=e.delayMs===void 0?N:e.delayMs;this.schema=e.schema,this.id=e.id,this.delayMs=n,this.state=e.state??null}async execute(e){let n=await e.query(a`
|
|
440
448
|
SELECT * FROM ${t(this.schema)}."message_defer"(
|
|
441
449
|
$1,
|
|
442
450
|
$2,
|
|
443
451
|
$3
|
|
444
452
|
)
|
|
445
|
-
`.value,[this.id,this.delayMs,this.state]).then((
|
|
446
|
-
`),
|
|
447
|
-
`).trim()};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/00-table-channel-policy.ts",k={name:c(__filename),sql:(e)=>{return[
|
|
453
|
+
`.value,[this.id,this.delayMs,this.state]).then((s)=>s.rows[0]);if(n.result_code===0)return{resultType:"MESSAGE_NOT_FOUND"};else if(n.result_code===1)return{resultType:"STATE_INVALID"};else if(n.result_code===2)return{resultType:"MESSAGE_DEFERRED"};else throw new Error("Unexpected result")}}var H=(e)=>{let n=e.split(`
|
|
454
|
+
`),s=Number.MAX_SAFE_INTEGER;for(let _ of n){if(_.trim().length===0)continue;let T=_.search(/\S/);s=Math.min(s,T)}return n.map((_)=>_.slice(s)).join(`
|
|
455
|
+
`).trim()};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/00-table-channel-policy.ts",k={name:c(__filename),sql:(e)=>{return[a`
|
|
448
456
|
CREATE TABLE ${t(e.schema)}."channel_policy" (
|
|
449
457
|
"id" UUID NOT NULL DEFAULT GEN_RANDOM_UUID(),
|
|
450
458
|
"name" TEXT NOT NULL,
|
|
@@ -453,10 +461,10 @@ var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=
|
|
|
453
461
|
"release_interval_ms" INTEGER,
|
|
454
462
|
PRIMARY KEY ("id")
|
|
455
463
|
);
|
|
456
|
-
`,
|
|
464
|
+
`,a`
|
|
457
465
|
CREATE UNIQUE INDEX "channel_policy_name_ux"
|
|
458
466
|
ON ${t(e.schema)}."channel_policy" ("name");
|
|
459
|
-
`]}};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/01-table-channel-state.ts",b={name:c(__filename),sql:(e)=>{return[
|
|
467
|
+
`]}};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/01-table-channel-state.ts",b={name:c(__filename),sql:(e)=>{return[a`
|
|
460
468
|
CREATE TABLE ${t(e.schema)}."channel_state" (
|
|
461
469
|
"id" UUID NOT NULL DEFAULT GEN_RANDOM_UUID(),
|
|
462
470
|
"name" TEXT NOT NULL,
|
|
@@ -467,21 +475,23 @@ var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=
|
|
|
467
475
|
"current_concurrency" INTEGER NOT NULL,
|
|
468
476
|
"message_next_id" UUID,
|
|
469
477
|
"message_next_dequeue_after" TIMESTAMP,
|
|
478
|
+
"message_next_seq_no" BIGINT,
|
|
470
479
|
PRIMARY KEY ("id")
|
|
471
480
|
);
|
|
472
|
-
`,
|
|
481
|
+
`,a`
|
|
473
482
|
CREATE UNIQUE INDEX "channel_state_name_ux"
|
|
474
483
|
ON ${t(e.schema)}."channel_state" ("name");
|
|
475
|
-
`,
|
|
484
|
+
`,a`
|
|
476
485
|
CREATE INDEX "channel_state_dequeue_ix"
|
|
477
486
|
ON ${t(e.schema)}."channel_state" (
|
|
478
487
|
"message_next_dequeue_after" ASC
|
|
479
488
|
) WHERE "message_next_id" IS NOT NULL
|
|
480
489
|
AND ("max_concurrency" IS NULL OR "current_concurrency" < "max_concurrency");
|
|
481
|
-
`]}};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/02-table-message.ts",z={name:c(__filename),sql:(e)=>{return[
|
|
490
|
+
`]}};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/02-table-message.ts",z={name:c(__filename),sql:(e)=>{return[a`
|
|
482
491
|
CREATE TABLE ${t(e.schema)}."message" (
|
|
483
492
|
"id" UUID NOT NULL,
|
|
484
493
|
"channel_name" TEXT NOT NULL,
|
|
494
|
+
"seq_no" BIGSERIAL NOT NULL,
|
|
485
495
|
"name" TEXT,
|
|
486
496
|
"content" BYTEA NOT NULL,
|
|
487
497
|
"state" BYTEA,
|
|
@@ -491,25 +501,25 @@ var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=
|
|
|
491
501
|
"dequeue_after" TIMESTAMP NOT NULL,
|
|
492
502
|
PRIMARY KEY ("id")
|
|
493
503
|
);
|
|
494
|
-
`,
|
|
504
|
+
`,a`
|
|
495
505
|
CREATE UNIQUE INDEX "message_name_ux"
|
|
496
506
|
ON ${t(e.schema)}."message" (
|
|
497
507
|
"channel_name",
|
|
498
508
|
"name"
|
|
499
509
|
) WHERE "num_attempts" = 0
|
|
500
|
-
`,
|
|
510
|
+
`,a`
|
|
501
511
|
CREATE INDEX "message_dequeue_ix"
|
|
502
512
|
ON ${t(e.schema)}."message" (
|
|
503
513
|
"channel_name",
|
|
504
514
|
"dequeue_after" ASC,
|
|
505
|
-
"
|
|
515
|
+
"seq_no" ASC
|
|
506
516
|
) WHERE NOT "is_locked";
|
|
507
|
-
`,
|
|
517
|
+
`,a`
|
|
508
518
|
CREATE INDEX "message_locked_dequeue_ix"
|
|
509
519
|
ON ${t(e.schema)}."message" (
|
|
510
520
|
"dequeue_after" ASC
|
|
511
521
|
) WHERE "is_locked";
|
|
512
|
-
`]}};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/03-function-wake.ts",w={name:c(__filename),sql:(e)=>{let n=u.toString(e.schema);return[
|
|
522
|
+
`]}};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/03-function-wake.ts",w={name:c(__filename),sql:(e)=>{let n=u.toString(e.schema);return[a`
|
|
513
523
|
CREATE FUNCTION ${t(e.schema)}."wake" (
|
|
514
524
|
p_delay_ms INTEGER
|
|
515
525
|
) RETURNS VOID AS $$
|
|
@@ -519,7 +529,7 @@ var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=
|
|
|
519
529
|
END IF;
|
|
520
530
|
END;
|
|
521
531
|
$$ LANGUAGE plpgsql;
|
|
522
|
-
`]}};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/08-function-channel-policy-set.ts",W={name:c(__filename),sql:(e)=>{return[
|
|
532
|
+
`]}};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/08-function-channel-policy-set.ts",W={name:c(__filename),sql:(e)=>{return[a`
|
|
523
533
|
CREATE FUNCTION ${t(e.schema)}."channel_policy_set" (
|
|
524
534
|
p_name TEXT,
|
|
525
535
|
p_max_size INTEGER,
|
|
@@ -551,7 +561,7 @@ var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=
|
|
|
551
561
|
PERFORM ${t(e.schema)}."wake"(0);
|
|
552
562
|
END;
|
|
553
563
|
$$ LANGUAGE plpgsql;
|
|
554
|
-
`]}};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/09-function-channel-policy-clear.ts",Y={name:c(__filename),sql:(e)=>{return[
|
|
564
|
+
`]}};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/09-function-channel-policy-clear.ts",Y={name:c(__filename),sql:(e)=>{return[a`
|
|
555
565
|
CREATE FUNCTION ${t(e.schema)}."channel_policy_clear" (
|
|
556
566
|
p_name TEXT
|
|
557
567
|
) RETURNS VOID AS $$
|
|
@@ -583,4 +593,4 @@ var{defineProperty:d,getOwnPropertyNames:V,getOwnPropertyDescriptor:X}=Object,K=
|
|
|
583
593
|
PERFORM ${t(e.schema)}."wake"(0);
|
|
584
594
|
END;
|
|
585
595
|
$$ LANGUAGE plpgsql;
|
|
586
|
-
`]}};class S{schema;channelName;registerFn;constructor(e){this.schema=e.schema,this.channelName=e.channelName,this.registerFn=e.registerFn}create(e){let n=new o({schema:this.schema,channelName:this.channelName,name:e.name,content:e.content,lockMs:e.lockMs,delayMs:e.delayMs}),
|
|
596
|
+
`]}};class S{schema;channelName;registerFn;constructor(e){this.schema=e.schema,this.channelName=e.channelName,this.registerFn=e.registerFn}create(e){let n=new o({schema:this.schema,channelName:this.channelName,name:e.name,content:e.content,lockMs:e.lockMs,delayMs:e.delayMs}),s=new Promise((_)=>{this.registerFn({sortKey:JSON.stringify([n.channelName,n.name,n.createdAt.toISOString()]),execute:(T)=>n.execute(T).then((Q)=>_(Q))})});return{messageId:n.id,promise:s}}}class A{schema;channelName;registerFn;constructor(e){this.schema=e.schema,this.channelName=e.channelName,this.registerFn=e.registerFn}set(e){let n=new m({schema:this.schema,channelName:this.channelName,maxConcurrency:e.maxConcurrency,maxSize:e.maxSize,releaseIntervalMs:e.releaseIntervalMs});this.registerFn({sortKey:JSON.stringify([n.channelName,null,n.createdAt.toISOString()]),execute:async(s)=>{await n.execute(s)}})}clear(){let e=new r({schema:this.schema,channelName:this.channelName});this.registerFn({sortKey:JSON.stringify([e.channelName,null,e.createdAt.toISOString()]),execute:async(n)=>{await e.execute(n)}})}}class I{policy;message;constructor(e){this.message=new S(e),this.policy=new A(e)}}var Ee=(e,n)=>{return e.sortKey.localeCompare(n.sortKey)};class L{commands;schema;constructor(e){this.commands=[],this.schema=e.schema}channel(e){return new I({schema:this.schema,channelName:e,registerFn:(n)=>{this.commands.push(n)}})}async execute(e){for(let n of this.commands.sort(Ee))await n.execute(e.databaseClient)}}class v{schema;channelName;constructor(e){this.schema=e.schema,this.channelName=e.channelName}async create(e){return new o({schema:this.schema,channelName:this.channelName,name:e.name,content:e.content,lockMs:e.lockMs,delayMs:e.delayMs}).execute(e.databaseClient)}}class U{schema;channelName;constructor(e){this.schema=e.schema,this.channelName=e.channelName}set(e){return new m({schema:this.schema,channelName:this.channelName,maxConcurrency:e.maxConcurrency,maxSize:e.maxSize,releaseIntervalMs:e.releaseIntervalMs}).execute(e.databaseClient)}clear(e){return new r({schema:this.schema,channelName:this.channelName}).execute(e.databaseClient)}}class O{policy;message;constructor(e){this.message=new v({schema:e.schema,channelName:e.channelName}),this.policy=new U({schema:e.schema,channelName:e.channelName})}}class D{schema;id;channelName;name;content;state;numAttempts;constructor(e){this.schema=e.schema,this.id=e.id,this.channelName=e.channelName,this.name=e.name,this.content=e.content,this.state=e.state,this.numAttempts=e.numAttempts}async defer(e){return new h({schema:this.schema,id:this.id,delayMs:e.delayMs,state:e.state}).execute(e.databaseClient)}async delete(e){return new l({schema:this.schema,id:this.id}).execute(e.databaseClient)}}class x{schema;constructor(e){this.schema=e.schema}async dequeue(e){let s=await new i({schema:this.schema}).execute(e.databaseClient);if(s.resultType==="MESSAGE_DEQUEUED")return{resultType:"MESSAGE_DEQUEUED",message:new D({schema:this.schema,id:s.message.id,channelName:s.message.channelName,name:s.message.name,content:s.message.content,state:s.message.state,numAttempts:s.message.numAttempts})};else return s}channel(e){return new O({schema:this.schema,channelName:e})}batch(){return new L({schema:this.schema})}migrations(e){return[k,b,z,w,F,G,B,P,W,Y].map((n)=>({name:n.name,sql:n.sql({schema:this.schema,useWake:e.useWake??q}).map((s)=>H(s.value))})).sort((n,s)=>n.name.localeCompare(s.name))}wakeChannel(){return u.toString(this.schema)}}
|
package/dist/index.d.ts
CHANGED
|
@@ -268,7 +268,9 @@ export type MessageDequeueResult = {
|
|
|
268
268
|
};
|
|
269
269
|
export declare class Queue {
|
|
270
270
|
private readonly schema;
|
|
271
|
-
constructor(
|
|
271
|
+
constructor(params: {
|
|
272
|
+
schema: string;
|
|
273
|
+
});
|
|
272
274
|
dequeue(params: {
|
|
273
275
|
databaseClient: DatabaseClient;
|
|
274
276
|
}): Promise<MessageDequeueResult>;
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=>({nodeType:"RAW",value:e}),W=(e)=>{return`'${e.replace(/'/g,"''")}'`},Y=(e)=>{if(e===null)return"NULL";else if(typeof e==="string")return W(e);else if(typeof e==="number")return e.toString();else if(typeof e==="boolean")return e?"TRUE":"FALSE";else if(e instanceof Date)return`'${e.toISOString()}'`;else if(typeof e==="bigint")return e.toString();else throw new Error(`Unsupported value type: ${typeof e}`)},Q=(e)=>{return`"${e.replace(/"/g,'""')}"`},V=(e)=>{if(e.nodeType==="VALUE")return Y(e.value);else if(e.nodeType==="REF")return Q(e.value);else if(e.nodeType==="RAW")return e.value;else throw new Error("Unsupported SQL node type")};var
|
|
1
|
+
var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=>({nodeType:"RAW",value:e}),W=(e)=>{return`'${e.replace(/'/g,"''")}'`},Y=(e)=>{if(e===null)return"NULL";else if(typeof e==="string")return W(e);else if(typeof e==="number")return e.toString();else if(typeof e==="boolean")return e?"TRUE":"FALSE";else if(e instanceof Date)return`'${e.toISOString()}'`;else if(typeof e==="bigint")return e.toString();else throw new Error(`Unsupported value type: ${typeof e}`)},Q=(e)=>{return`"${e.replace(/"/g,'""')}"`},V=(e)=>{if(e.nodeType==="VALUE")return Y(e.value);else if(e.nodeType==="REF")return Q(e.value);else if(e.nodeType==="RAW")return e.value;else throw new Error("Unsupported SQL node type")};var s=(e,...t)=>{let a=[];for(let E=0;E<e.length;E+=1)if(a.push(e[E]),E<t.length)a.push(V(t[E]));return w(a.join(""))};class r{schema;channelName;createdAt;constructor(e){this.schema=e.schema,this.channelName=e.channelName,this.createdAt=new Date}sortKeyGet(){return JSON.stringify([this.channelName,null,this.createdAt.toISOString()])}async execute(e){await e.query(s`
|
|
2
2
|
SELECT 1 FROM ${n(this.schema)}."channel_policy_clear"(
|
|
3
3
|
$1
|
|
4
4
|
)
|
|
5
|
-
`.value,[this.channelName])}}class m{schema;channelName;maxSize;maxConcurrency;releaseIntervalMs;createdAt;constructor(e){this.schema=e.schema,this.channelName=e.channelName;let t=e.maxConcurrency??null;this.maxConcurrency=t!==null?Math.max(0,t):null;let
|
|
5
|
+
`.value,[this.channelName])}}class m{schema;channelName;maxSize;maxConcurrency;releaseIntervalMs;createdAt;constructor(e){this.schema=e.schema,this.channelName=e.channelName;let t=e.maxConcurrency??null;this.maxConcurrency=t!==null?Math.max(0,t):null;let a=e.maxSize??null;this.maxSize=a!==null?Math.max(0,a):null;let E=e.releaseIntervalMs??null;this.releaseIntervalMs=E!==null?Math.max(0,E):null,this.createdAt=new Date}sortKeyGet(){return JSON.stringify([this.channelName,null,this.createdAt.toISOString()])}async execute(e){await e.query(s`
|
|
6
6
|
SELECT 1 FROM ${n(this.schema)}."channel_policy_set"(
|
|
7
7
|
$1,
|
|
8
8
|
$2::INTEGER,
|
|
9
9
|
$3::INTEGER,
|
|
10
10
|
$4::INTEGER
|
|
11
11
|
)
|
|
12
|
-
`.value,[this.channelName,this.maxSize,this.maxConcurrency,this.releaseIntervalMs])}}var d=(e)=>{return e*1000},X=(e)=>{return d(e*60)},O=(e)=>{return X(e*60)};import{createHash as K}from"node:crypto";class
|
|
12
|
+
`.value,[this.channelName,this.maxSize,this.maxConcurrency,this.releaseIntervalMs])}}var d=(e)=>{return e*1000},X=(e)=>{return d(e*60)},O=(e)=>{return X(e*60)};import{createHash as K}from"node:crypto";class D{value;constructor(e){this.value=e}toString(e){return K("sha256").update(e).update(this.value).digest("base64").replace(/=/g,"")}}var i=new D("WAKE"),x=!1,l=d(0),y=O(1);import{dirname as f}from"path";var __filename="/home/runner/work/lonnymq/lonnymq/src/core/path.ts",J=f(f(__filename)),j=new RegExp(`^${J}/`),_=(e)=>{return e.replace(j,"")};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/04-function-message-create.ts";var C={name:_(__filename),sql:(e)=>{return[s`
|
|
13
13
|
CREATE FUNCTION ${n(e.schema)}."message_create" (
|
|
14
14
|
p_id UUID,
|
|
15
15
|
p_channel_name TEXT,
|
|
@@ -65,7 +65,8 @@ var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=
|
|
|
65
65
|
"max_size",
|
|
66
66
|
"max_concurrency",
|
|
67
67
|
"message_next_id",
|
|
68
|
-
"message_next_dequeue_after"
|
|
68
|
+
"message_next_dequeue_after",
|
|
69
|
+
"message_next_seq_no"
|
|
69
70
|
INTO v_channel_state;
|
|
70
71
|
|
|
71
72
|
IF v_channel_state."current_size" >= v_channel_policy."max_size" THEN
|
|
@@ -95,6 +96,7 @@ var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=
|
|
|
95
96
|
"name" = EXCLUDED."name"
|
|
96
97
|
RETURNING
|
|
97
98
|
"id",
|
|
99
|
+
"seq_no",
|
|
98
100
|
"dequeue_after"
|
|
99
101
|
INTO v_message;
|
|
100
102
|
|
|
@@ -106,12 +108,14 @@ var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=
|
|
|
106
108
|
|
|
107
109
|
IF
|
|
108
110
|
v_channel_state."message_next_id" IS NULL OR
|
|
109
|
-
v_channel_state."message_next_dequeue_after" > v_message."dequeue_after"
|
|
111
|
+
v_channel_state."message_next_dequeue_after" > v_message."dequeue_after" OR
|
|
112
|
+
(v_channel_state."message_next_dequeue_after" = v_message."dequeue_after" AND v_channel_state."message_next_seq_no" > v_message."seq_no")
|
|
110
113
|
THEN
|
|
111
114
|
UPDATE ${n(e.schema)}."channel_state" SET
|
|
112
115
|
"current_size" = v_channel_state."current_size" + 1,
|
|
113
116
|
"message_next_id" = v_message."id",
|
|
114
|
-
"message_next_dequeue_after" = GREATEST(v_now, v_message."dequeue_after")
|
|
117
|
+
"message_next_dequeue_after" = GREATEST(v_now, v_message."dequeue_after"),
|
|
118
|
+
"message_next_seq_no" = v_message."seq_no"
|
|
115
119
|
WHERE "id" = v_channel_state."id";
|
|
116
120
|
ELSE
|
|
117
121
|
UPDATE ${n(e.schema)}."channel_state" SET
|
|
@@ -126,7 +130,7 @@ var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=
|
|
|
126
130
|
RETURN;
|
|
127
131
|
END;
|
|
128
132
|
$$ LANGUAGE plpgsql;
|
|
129
|
-
`]}};import{randomUUID as Z}from"node:crypto";class o{schema;channelName;name;content;lockMs;id;delayMs;createdAt;constructor(e){let t=e.name??null,
|
|
133
|
+
`]}};import{randomUUID as Z}from"node:crypto";class o{schema;channelName;name;content;lockMs;id;delayMs;createdAt;constructor(e){let t=e.name??null,a=e.lockMs===void 0?y:Math.max(0,e.lockMs),E=e.delayMs===void 0?l:e.delayMs;this.id=Z(),this.schema=e.schema,this.channelName=e.channelName,this.content=e.content,this.name=t,this.lockMs=a,this.delayMs=E,this.createdAt=new Date}async execute(e){let t=await e.query(s`
|
|
130
134
|
SELECT * FROM ${n(this.schema)}."message_create"(
|
|
131
135
|
$1,
|
|
132
136
|
$2,
|
|
@@ -135,7 +139,7 @@ var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=
|
|
|
135
139
|
$5::INTEGER,
|
|
136
140
|
$6::INTEGER
|
|
137
141
|
)
|
|
138
|
-
`.value,[this.id,this.channelName,this.name,this.content,this.lockMs,this.delayMs]).then((
|
|
142
|
+
`.value,[this.id,this.channelName,this.name,this.content,this.lockMs,this.delayMs]).then((a)=>a.rows[0]);if(t.result_code===1)return{resultType:"MESSAGE_DROPPED"};else if(t.result_code===2)return{resultType:"MESSAGE_DEDUPLICATED"};else if(t.result_code===0)return{resultType:"MESSAGE_CREATED"};else throw new Error("Unexpected result")}}var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/05-function-message-dequeue.ts";var p={name:_(__filename),sql:(e)=>{return[s`
|
|
139
143
|
CREATE FUNCTION ${n(e.schema)}."message_dequeue" ()
|
|
140
144
|
RETURNS TABLE (
|
|
141
145
|
result_code INTEGER,
|
|
@@ -181,14 +185,12 @@ var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=
|
|
|
181
185
|
|
|
182
186
|
RETURN QUERY SELECT
|
|
183
187
|
${c(1)},
|
|
184
|
-
|
|
185
|
-
|
|
188
|
+
v_message_locked.content,
|
|
189
|
+
v_message_locked.state,
|
|
186
190
|
JSON_BUILD_OBJECT(
|
|
187
191
|
'id', v_message_locked.id,
|
|
188
192
|
'channel_name', v_message_locked.channel_name,
|
|
189
|
-
'
|
|
190
|
-
'name', v_message_locked.name
|
|
191
|
-
'content', v_message_locked.content,
|
|
193
|
+
'name', v_message_locked.name,
|
|
192
194
|
'num_attempts', v_message_locked.num_attempts
|
|
193
195
|
);
|
|
194
196
|
RETURN;
|
|
@@ -248,11 +250,12 @@ var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=
|
|
|
248
250
|
|
|
249
251
|
SELECT
|
|
250
252
|
"message"."id",
|
|
251
|
-
"message"."dequeue_after"
|
|
253
|
+
"message"."dequeue_after",
|
|
254
|
+
"message"."seq_no"
|
|
252
255
|
FROM ${n(e.schema)}."message"
|
|
253
256
|
WHERE NOT "is_locked"
|
|
254
257
|
AND "channel_name" = v_message_dequeue."channel_name"
|
|
255
|
-
ORDER BY "dequeue_after" ASC, "
|
|
258
|
+
ORDER BY "dequeue_after" ASC, "seq_no" ASC
|
|
256
259
|
LIMIT 1
|
|
257
260
|
INTO v_message_next_dequeue;
|
|
258
261
|
|
|
@@ -265,7 +268,8 @@ var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=
|
|
|
265
268
|
UPDATE ${n(e.schema)}."channel_state" SET
|
|
266
269
|
"current_concurrency" = v_channel_state."current_concurrency" + 1,
|
|
267
270
|
"message_next_id" = v_message_next_dequeue."id",
|
|
268
|
-
"message_next_dequeue_after" = v_message_next_dequeue_after
|
|
271
|
+
"message_next_dequeue_after" = v_message_next_dequeue_after,
|
|
272
|
+
"message_next_seq_no" = v_message_next_dequeue."seq_no"
|
|
269
273
|
WHERE "id" = v_channel_state."id";
|
|
270
274
|
ELSE
|
|
271
275
|
UPDATE ${n(e.schema)}."channel_state" SET
|
|
@@ -288,14 +292,14 @@ var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=
|
|
|
288
292
|
RETURN;
|
|
289
293
|
END;
|
|
290
294
|
$$ LANGUAGE plpgsql;
|
|
291
|
-
`]}};class h{schema;constructor(e){this.schema=e.schema}async execute(e){let t=await e.query(
|
|
295
|
+
`]}};class h{schema;constructor(e){this.schema=e.schema}async execute(e){let t=await e.query(s`
|
|
292
296
|
SELECT
|
|
293
297
|
result_code,
|
|
294
298
|
metadata,
|
|
295
299
|
content,
|
|
296
300
|
state
|
|
297
301
|
FROM ${n(this.schema)}."message_dequeue"()
|
|
298
|
-
`.value,[]).then((
|
|
302
|
+
`.value,[]).then((a)=>a.rows[0]);if(t.result_code===0)return{resultType:"MESSAGE_NOT_AVAILABLE",retryMs:t.metadata.retry_ms};else if(t.result_code===1)return{resultType:"MESSAGE_DEQUEUED",message:{id:t.metadata.id,channelName:t.metadata.channel_name,name:t.metadata.name,content:t.content,state:t.state,numAttempts:t.metadata.num_attempts}};else throw new Error("Unexpected dequeue result")}}var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/06-function-message-delete.ts";var q={name:_(__filename),sql:(e)=>{return[s`
|
|
299
303
|
CREATE FUNCTION ${n(e.schema)}."message_delete" (
|
|
300
304
|
p_id UUID
|
|
301
305
|
)
|
|
@@ -360,11 +364,11 @@ var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=
|
|
|
360
364
|
RETURN;
|
|
361
365
|
END;
|
|
362
366
|
$$ LANGUAGE plpgsql;
|
|
363
|
-
`]}};class u{schema;id;constructor(e){this.schema=e.schema,this.id=e.id}async execute(e){let t=await e.query(
|
|
367
|
+
`]}};class u{schema;id;constructor(e){this.schema=e.schema,this.id=e.id}async execute(e){let t=await e.query(s`
|
|
364
368
|
SELECT * FROM ${n(this.schema)}."message_delete"(
|
|
365
369
|
$1
|
|
366
370
|
)
|
|
367
|
-
`.value,[this.id]).then((
|
|
371
|
+
`.value,[this.id]).then((a)=>a.rows[0]);if(t.result_code===0)return{resultType:"MESSAGE_NOT_FOUND"};else if(t.result_code===1)return{resultType:"STATE_INVALID"};else if(t.result_code===2)return{resultType:"MESSAGE_DELETED"};else throw new Error("Unexpected result")}}var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/07-function-message-defer.ts";var $={name:_(__filename),sql:(e)=>{return[s`
|
|
368
372
|
CREATE FUNCTION ${n(e.schema)}."message_defer" (
|
|
369
373
|
p_id UUID,
|
|
370
374
|
p_delay_ms INTEGER,
|
|
@@ -381,7 +385,8 @@ var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=
|
|
|
381
385
|
SELECT
|
|
382
386
|
"message"."id",
|
|
383
387
|
"message"."channel_name",
|
|
384
|
-
"message"."is_locked"
|
|
388
|
+
"message"."is_locked",
|
|
389
|
+
"message"."seq_no"
|
|
385
390
|
FROM ${n(e.schema)}."message"
|
|
386
391
|
WHERE "id" = p_id
|
|
387
392
|
FOR UPDATE
|
|
@@ -400,7 +405,8 @@ var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=
|
|
|
400
405
|
SELECT
|
|
401
406
|
"channel_state"."current_concurrency",
|
|
402
407
|
"channel_state"."message_next_id",
|
|
403
|
-
"channel_state"."message_next_dequeue_after"
|
|
408
|
+
"channel_state"."message_next_dequeue_after",
|
|
409
|
+
"channel_state"."message_next_seq_no"
|
|
404
410
|
FROM ${n(e.schema)}."channel_state"
|
|
405
411
|
WHERE "name" = v_message."channel_name"
|
|
406
412
|
FOR UPDATE
|
|
@@ -410,12 +416,14 @@ var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=
|
|
|
410
416
|
|
|
411
417
|
IF
|
|
412
418
|
v_channel_state."message_next_id" IS NULL OR
|
|
413
|
-
v_channel_state."message_next_dequeue_after" > v_dequeue_after
|
|
419
|
+
v_channel_state."message_next_dequeue_after" > v_dequeue_after OR
|
|
420
|
+
(v_channel_state."message_next_dequeue_after" = v_dequeue_after AND v_channel_state."message_next_seq_no" > v_message."seq_no")
|
|
414
421
|
THEN
|
|
415
422
|
UPDATE ${n(e.schema)}."channel_state" SET
|
|
416
423
|
"current_concurrency" = v_channel_state."current_concurrency" - 1,
|
|
417
424
|
"message_next_id" = v_message."id",
|
|
418
|
-
"message_next_dequeue_after" = v_dequeue_after
|
|
425
|
+
"message_next_dequeue_after" = v_dequeue_after,
|
|
426
|
+
"message_next_seq_no" = v_message."seq_no"
|
|
419
427
|
WHERE "name" = v_message."channel_name";
|
|
420
428
|
ELSE
|
|
421
429
|
UPDATE ${n(e.schema)}."channel_state" SET
|
|
@@ -436,15 +444,15 @@ var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=
|
|
|
436
444
|
RETURN;
|
|
437
445
|
END;
|
|
438
446
|
$$ LANGUAGE plpgsql;
|
|
439
|
-
`]}};class N{schema;id;delayMs;state;constructor(e){let t=e.delayMs===void 0?l:e.delayMs;this.schema=e.schema,this.id=e.id,this.delayMs=t,this.state=e.state??null}async execute(e){let t=await e.query(
|
|
447
|
+
`]}};class N{schema;id;delayMs;state;constructor(e){let t=e.delayMs===void 0?l:e.delayMs;this.schema=e.schema,this.id=e.id,this.delayMs=t,this.state=e.state??null}async execute(e){let t=await e.query(s`
|
|
440
448
|
SELECT * FROM ${n(this.schema)}."message_defer"(
|
|
441
449
|
$1,
|
|
442
450
|
$2,
|
|
443
451
|
$3
|
|
444
452
|
)
|
|
445
|
-
`.value,[this.id,this.delayMs,this.state]).then((
|
|
446
|
-
`),
|
|
447
|
-
`).trim()};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/00-table-channel-policy.ts",M={name:_(__filename),sql:(e)=>{return[
|
|
453
|
+
`.value,[this.id,this.delayMs,this.state]).then((a)=>a.rows[0]);if(t.result_code===0)return{resultType:"MESSAGE_NOT_FOUND"};else if(t.result_code===1)return{resultType:"STATE_INVALID"};else if(t.result_code===2)return{resultType:"MESSAGE_DEFERRED"};else throw new Error("Unexpected result")}}var F=(e)=>{let t=e.split(`
|
|
454
|
+
`),a=Number.MAX_SAFE_INTEGER;for(let E of t){if(E.trim().length===0)continue;let T=E.search(/\S/);a=Math.min(a,T)}return t.map((E)=>E.slice(a)).join(`
|
|
455
|
+
`).trim()};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/00-table-channel-policy.ts",M={name:_(__filename),sql:(e)=>{return[s`
|
|
448
456
|
CREATE TABLE ${n(e.schema)}."channel_policy" (
|
|
449
457
|
"id" UUID NOT NULL DEFAULT GEN_RANDOM_UUID(),
|
|
450
458
|
"name" TEXT NOT NULL,
|
|
@@ -453,10 +461,10 @@ var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=
|
|
|
453
461
|
"release_interval_ms" INTEGER,
|
|
454
462
|
PRIMARY KEY ("id")
|
|
455
463
|
);
|
|
456
|
-
`,
|
|
464
|
+
`,s`
|
|
457
465
|
CREATE UNIQUE INDEX "channel_policy_name_ux"
|
|
458
466
|
ON ${n(e.schema)}."channel_policy" ("name");
|
|
459
|
-
`]}};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/01-table-channel-state.ts",G={name:_(__filename),sql:(e)=>{return[
|
|
467
|
+
`]}};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/01-table-channel-state.ts",G={name:_(__filename),sql:(e)=>{return[s`
|
|
460
468
|
CREATE TABLE ${n(e.schema)}."channel_state" (
|
|
461
469
|
"id" UUID NOT NULL DEFAULT GEN_RANDOM_UUID(),
|
|
462
470
|
"name" TEXT NOT NULL,
|
|
@@ -467,21 +475,23 @@ var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=
|
|
|
467
475
|
"current_concurrency" INTEGER NOT NULL,
|
|
468
476
|
"message_next_id" UUID,
|
|
469
477
|
"message_next_dequeue_after" TIMESTAMP,
|
|
478
|
+
"message_next_seq_no" BIGINT,
|
|
470
479
|
PRIMARY KEY ("id")
|
|
471
480
|
);
|
|
472
|
-
`,
|
|
481
|
+
`,s`
|
|
473
482
|
CREATE UNIQUE INDEX "channel_state_name_ux"
|
|
474
483
|
ON ${n(e.schema)}."channel_state" ("name");
|
|
475
|
-
`,
|
|
484
|
+
`,s`
|
|
476
485
|
CREATE INDEX "channel_state_dequeue_ix"
|
|
477
486
|
ON ${n(e.schema)}."channel_state" (
|
|
478
487
|
"message_next_dequeue_after" ASC
|
|
479
488
|
) WHERE "message_next_id" IS NOT NULL
|
|
480
489
|
AND ("max_concurrency" IS NULL OR "current_concurrency" < "max_concurrency");
|
|
481
|
-
`]}};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/02-table-message.ts",B={name:_(__filename),sql:(e)=>{return[
|
|
490
|
+
`]}};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/02-table-message.ts",B={name:_(__filename),sql:(e)=>{return[s`
|
|
482
491
|
CREATE TABLE ${n(e.schema)}."message" (
|
|
483
492
|
"id" UUID NOT NULL,
|
|
484
493
|
"channel_name" TEXT NOT NULL,
|
|
494
|
+
"seq_no" BIGSERIAL NOT NULL,
|
|
485
495
|
"name" TEXT,
|
|
486
496
|
"content" BYTEA NOT NULL,
|
|
487
497
|
"state" BYTEA,
|
|
@@ -491,25 +501,25 @@ var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=
|
|
|
491
501
|
"dequeue_after" TIMESTAMP NOT NULL,
|
|
492
502
|
PRIMARY KEY ("id")
|
|
493
503
|
);
|
|
494
|
-
`,
|
|
504
|
+
`,s`
|
|
495
505
|
CREATE UNIQUE INDEX "message_name_ux"
|
|
496
506
|
ON ${n(e.schema)}."message" (
|
|
497
507
|
"channel_name",
|
|
498
508
|
"name"
|
|
499
509
|
) WHERE "num_attempts" = 0
|
|
500
|
-
`,
|
|
510
|
+
`,s`
|
|
501
511
|
CREATE INDEX "message_dequeue_ix"
|
|
502
512
|
ON ${n(e.schema)}."message" (
|
|
503
513
|
"channel_name",
|
|
504
514
|
"dequeue_after" ASC,
|
|
505
|
-
"
|
|
515
|
+
"seq_no" ASC
|
|
506
516
|
) WHERE NOT "is_locked";
|
|
507
|
-
`,
|
|
517
|
+
`,s`
|
|
508
518
|
CREATE INDEX "message_locked_dequeue_ix"
|
|
509
519
|
ON ${n(e.schema)}."message" (
|
|
510
520
|
"dequeue_after" ASC
|
|
511
521
|
) WHERE "is_locked";
|
|
512
|
-
`]}};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/03-function-wake.ts",P={name:_(__filename),sql:(e)=>{let t=i.toString(e.schema);return[
|
|
522
|
+
`]}};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/03-function-wake.ts",P={name:_(__filename),sql:(e)=>{let t=i.toString(e.schema);return[s`
|
|
513
523
|
CREATE FUNCTION ${n(e.schema)}."wake" (
|
|
514
524
|
p_delay_ms INTEGER
|
|
515
525
|
) RETURNS VOID AS $$
|
|
@@ -519,7 +529,7 @@ var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=
|
|
|
519
529
|
END IF;
|
|
520
530
|
END;
|
|
521
531
|
$$ LANGUAGE plpgsql;
|
|
522
|
-
`]}};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/08-function-channel-policy-set.ts",H={name:_(__filename),sql:(e)=>{return[
|
|
532
|
+
`]}};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/08-function-channel-policy-set.ts",H={name:_(__filename),sql:(e)=>{return[s`
|
|
523
533
|
CREATE FUNCTION ${n(e.schema)}."channel_policy_set" (
|
|
524
534
|
p_name TEXT,
|
|
525
535
|
p_max_size INTEGER,
|
|
@@ -551,7 +561,7 @@ var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=
|
|
|
551
561
|
PERFORM ${n(e.schema)}."wake"(0);
|
|
552
562
|
END;
|
|
553
563
|
$$ LANGUAGE plpgsql;
|
|
554
|
-
`]}};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/09-function-channel-policy-clear.ts",k={name:_(__filename),sql:(e)=>{return[
|
|
564
|
+
`]}};var __filename="/home/runner/work/lonnymq/lonnymq/src/migration/09-function-channel-policy-clear.ts",k={name:_(__filename),sql:(e)=>{return[s`
|
|
555
565
|
CREATE FUNCTION ${n(e.schema)}."channel_policy_clear" (
|
|
556
566
|
p_name TEXT
|
|
557
567
|
) RETURNS VOID AS $$
|
|
@@ -583,4 +593,4 @@ var c=(e)=>({nodeType:"VALUE",value:e}),n=(e)=>({nodeType:"REF",value:e}),w=(e)=
|
|
|
583
593
|
PERFORM ${n(e.schema)}."wake"(0);
|
|
584
594
|
END;
|
|
585
595
|
$$ LANGUAGE plpgsql;
|
|
586
|
-
`]}};class R{schema;channelName;registerFn;constructor(e){this.schema=e.schema,this.channelName=e.channelName,this.registerFn=e.registerFn}create(e){let t=new o({schema:this.schema,channelName:this.channelName,name:e.name,content:e.content,lockMs:e.lockMs,delayMs:e.delayMs}),
|
|
596
|
+
`]}};class R{schema;channelName;registerFn;constructor(e){this.schema=e.schema,this.channelName=e.channelName,this.registerFn=e.registerFn}create(e){let t=new o({schema:this.schema,channelName:this.channelName,name:e.name,content:e.content,lockMs:e.lockMs,delayMs:e.delayMs}),a=new Promise((E)=>{this.registerFn({sortKey:JSON.stringify([t.channelName,t.name,t.createdAt.toISOString()]),execute:(T)=>t.execute(T).then((z)=>E(z))})});return{messageId:t.id,promise:a}}}class g{schema;channelName;registerFn;constructor(e){this.schema=e.schema,this.channelName=e.channelName,this.registerFn=e.registerFn}set(e){let t=new m({schema:this.schema,channelName:this.channelName,maxConcurrency:e.maxConcurrency,maxSize:e.maxSize,releaseIntervalMs:e.releaseIntervalMs});this.registerFn({sortKey:JSON.stringify([t.channelName,null,t.createdAt.toISOString()]),execute:async(a)=>{await t.execute(a)}})}clear(){let e=new r({schema:this.schema,channelName:this.channelName});this.registerFn({sortKey:JSON.stringify([e.channelName,null,e.createdAt.toISOString()]),execute:async(t)=>{await e.execute(t)}})}}class S{policy;message;constructor(e){this.message=new R(e),this.policy=new g(e)}}var ee=(e,t)=>{return e.sortKey.localeCompare(t.sortKey)};class A{commands;schema;constructor(e){this.commands=[],this.schema=e.schema}channel(e){return new S({schema:this.schema,channelName:e,registerFn:(t)=>{this.commands.push(t)}})}async execute(e){for(let t of this.commands.sort(ee))await t.execute(e.databaseClient)}}class I{schema;channelName;constructor(e){this.schema=e.schema,this.channelName=e.channelName}async create(e){return new o({schema:this.schema,channelName:this.channelName,name:e.name,content:e.content,lockMs:e.lockMs,delayMs:e.delayMs}).execute(e.databaseClient)}}class L{schema;channelName;constructor(e){this.schema=e.schema,this.channelName=e.channelName}set(e){return new m({schema:this.schema,channelName:this.channelName,maxConcurrency:e.maxConcurrency,maxSize:e.maxSize,releaseIntervalMs:e.releaseIntervalMs}).execute(e.databaseClient)}clear(e){return new r({schema:this.schema,channelName:this.channelName}).execute(e.databaseClient)}}class v{policy;message;constructor(e){this.message=new I({schema:e.schema,channelName:e.channelName}),this.policy=new L({schema:e.schema,channelName:e.channelName})}}class U{schema;id;channelName;name;content;state;numAttempts;constructor(e){this.schema=e.schema,this.id=e.id,this.channelName=e.channelName,this.name=e.name,this.content=e.content,this.state=e.state,this.numAttempts=e.numAttempts}async defer(e){return new N({schema:this.schema,id:this.id,delayMs:e.delayMs,state:e.state}).execute(e.databaseClient)}async delete(e){return new u({schema:this.schema,id:this.id}).execute(e.databaseClient)}}class b{schema;constructor(e){this.schema=e.schema}async dequeue(e){let a=await new h({schema:this.schema}).execute(e.databaseClient);if(a.resultType==="MESSAGE_DEQUEUED")return{resultType:"MESSAGE_DEQUEUED",message:new U({schema:this.schema,id:a.message.id,channelName:a.message.channelName,name:a.message.name,content:a.message.content,state:a.message.state,numAttempts:a.message.numAttempts})};else return a}channel(e){return new v({schema:this.schema,channelName:e})}batch(){return new A({schema:this.schema})}migrations(e){return[M,G,B,P,C,p,q,$,H,k].map((t)=>({name:t.name,sql:t.sql({schema:this.schema,useWake:e.useWake??x}).map((a)=>F(a.value))})).sort((t,a)=>t.name.localeCompare(a.name))}wakeChannel(){return i.toString(this.schema)}}export{b as Queue,h as MessageDequeueCommand,u as MessageDeleteCommand,N as MessageDeferCommand,o as MessageCreateCommand,m as ChannelPolicySetCommand,r as ChannelPolicyClearCommand};
|