lonnymq 1.0.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +314 -0
- package/dist/index.mjs +1117 -0
- package/package.json +21 -17
- package/README.md +0 -356
- package/dist/index.cjs +0 -666
- package/dist/index.d.ts +0 -281
- package/dist/index.js +0 -666
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1117 @@
|
|
|
1
|
+
const INTEGER_MAX = 2147483647;
|
|
2
|
+
//#endregion
|
|
3
|
+
//#region src/util/sql.ts
|
|
4
|
+
const value = (value) => ({
|
|
5
|
+
nodeType: "VALUE",
|
|
6
|
+
value
|
|
7
|
+
});
|
|
8
|
+
const ref = (value) => ({
|
|
9
|
+
nodeType: "REF",
|
|
10
|
+
value
|
|
11
|
+
});
|
|
12
|
+
const raw = (value) => ({
|
|
13
|
+
nodeType: "RAW",
|
|
14
|
+
value
|
|
15
|
+
});
|
|
16
|
+
const escapeString = (value) => {
|
|
17
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
18
|
+
};
|
|
19
|
+
const escapeValue = (value) => {
|
|
20
|
+
if (value === null) return "NULL";
|
|
21
|
+
else if (typeof value === "string") return escapeString(value);
|
|
22
|
+
else if (typeof value === "number") return value.toString();
|
|
23
|
+
else if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
|
|
24
|
+
else if (value instanceof Date) return `'${value.toISOString()}'`;
|
|
25
|
+
else if (typeof value === "bigint") return value.toString();
|
|
26
|
+
else throw new Error(`Unsupported value type: ${typeof value}`);
|
|
27
|
+
};
|
|
28
|
+
const escapeRef = (value) => {
|
|
29
|
+
return `"${value.replace(/"/g, "\"\"")}"`;
|
|
30
|
+
};
|
|
31
|
+
const escapeNode = (value) => {
|
|
32
|
+
if (value.nodeType === "VALUE") return escapeValue(value.value);
|
|
33
|
+
else if (value.nodeType === "REF") return escapeRef(value.value);
|
|
34
|
+
else if (value.nodeType === "RAW") return value.value;
|
|
35
|
+
else throw new Error("Unsupported SQL node type");
|
|
36
|
+
};
|
|
37
|
+
const fragment = (fragments, ...values) => {
|
|
38
|
+
const parts = [];
|
|
39
|
+
for (let ix = 0; ix < fragments.length; ix += 1) {
|
|
40
|
+
parts.push(fragments[ix]);
|
|
41
|
+
if (ix < values.length) parts.push(escapeNode(values[ix]));
|
|
42
|
+
}
|
|
43
|
+
return raw(parts.join(""));
|
|
44
|
+
};
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/util/text.ts
|
|
47
|
+
const dedent = (value) => {
|
|
48
|
+
const lines = value.split("\n");
|
|
49
|
+
let minIndent = Number.MAX_SAFE_INTEGER;
|
|
50
|
+
for (const line of lines) {
|
|
51
|
+
if (line.trim().length === 0) continue;
|
|
52
|
+
const indent = line.search(/\S/);
|
|
53
|
+
minIndent = Math.min(minIndent, indent);
|
|
54
|
+
}
|
|
55
|
+
return lines.map((line) => line.slice(minIndent)).join("\n").trim();
|
|
56
|
+
};
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/command/channel/release.ts
|
|
59
|
+
var Release = class {
|
|
60
|
+
schema;
|
|
61
|
+
channel;
|
|
62
|
+
createdAt;
|
|
63
|
+
constructor(params) {
|
|
64
|
+
this.schema = params.schema;
|
|
65
|
+
this.channel = params.channel;
|
|
66
|
+
this.createdAt = /* @__PURE__ */ new Date();
|
|
67
|
+
}
|
|
68
|
+
async execute(databaseClient) {
|
|
69
|
+
const result = await databaseClient.query(fragment`
|
|
70
|
+
SELECT * FROM ${ref(this.schema)}."channel_release"(
|
|
71
|
+
$1
|
|
72
|
+
)
|
|
73
|
+
`.value, [this.channel]).then((res) => res.rows[0]);
|
|
74
|
+
if (result.result_code === 9) return { resultType: "CHANNEL_NOT_FOUND" };
|
|
75
|
+
else if (result.result_code === 10) return { resultType: "CHANNEL_RELEASED" };
|
|
76
|
+
else throw new Error("Unexpected result");
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/command/channel/set.ts
|
|
81
|
+
var Set = class {
|
|
82
|
+
schema;
|
|
83
|
+
channel;
|
|
84
|
+
maxConcurrency;
|
|
85
|
+
maxSize;
|
|
86
|
+
releaseIntervalMs;
|
|
87
|
+
createdAt;
|
|
88
|
+
constructor(params) {
|
|
89
|
+
this.schema = params.schema;
|
|
90
|
+
this.channel = params.channel;
|
|
91
|
+
if (!Number.isInteger(params.maxConcurrency) || params.maxConcurrency < 1 || params.maxConcurrency > 2147483647) throw new Error(`maxConcurrency must be an integer between 1 and ${INTEGER_MAX}`);
|
|
92
|
+
if (!Number.isInteger(params.maxSize) || params.maxSize < 1 || params.maxSize > 2147483647) throw new Error(`maxSize must be an integer between 1 and ${INTEGER_MAX}`);
|
|
93
|
+
if (!Number.isInteger(params.releaseIntervalMs) || params.releaseIntervalMs < 0 || params.releaseIntervalMs > 2147483647) throw new Error(`releaseIntervalMs must be an integer between 0 and ${INTEGER_MAX}`);
|
|
94
|
+
this.maxConcurrency = params.maxConcurrency;
|
|
95
|
+
this.maxSize = params.maxSize;
|
|
96
|
+
this.releaseIntervalMs = params.releaseIntervalMs;
|
|
97
|
+
this.createdAt = /* @__PURE__ */ new Date();
|
|
98
|
+
}
|
|
99
|
+
async execute(databaseClient) {
|
|
100
|
+
await databaseClient.query(fragment`
|
|
101
|
+
SELECT 1 FROM ${ref(this.schema)}."channel_set"(
|
|
102
|
+
$1,
|
|
103
|
+
$2::INTEGER,
|
|
104
|
+
$3::INTEGER,
|
|
105
|
+
$4::INTEGER
|
|
106
|
+
)
|
|
107
|
+
`.value, [
|
|
108
|
+
this.channel,
|
|
109
|
+
this.maxConcurrency,
|
|
110
|
+
this.maxSize,
|
|
111
|
+
this.releaseIntervalMs
|
|
112
|
+
]);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/command/message/create.ts
|
|
117
|
+
var Create = class {
|
|
118
|
+
schema;
|
|
119
|
+
channel;
|
|
120
|
+
content;
|
|
121
|
+
dequeueAt;
|
|
122
|
+
constructor(params) {
|
|
123
|
+
this.schema = params.schema;
|
|
124
|
+
this.channel = params.channel;
|
|
125
|
+
this.content = params.content;
|
|
126
|
+
this.dequeueAt = params.dequeueAt;
|
|
127
|
+
}
|
|
128
|
+
async execute(databaseClient) {
|
|
129
|
+
const result = await databaseClient.query(fragment`
|
|
130
|
+
SELECT
|
|
131
|
+
result_code,
|
|
132
|
+
metadata
|
|
133
|
+
FROM ${ref(this.schema)}."message_create"(
|
|
134
|
+
$1,
|
|
135
|
+
$2,
|
|
136
|
+
$3::BIGINT
|
|
137
|
+
)
|
|
138
|
+
`.value, [
|
|
139
|
+
this.channel,
|
|
140
|
+
this.content,
|
|
141
|
+
this.dequeueAt
|
|
142
|
+
]).then((res) => res.rows[0]);
|
|
143
|
+
if (result.result_code === 0) return {
|
|
144
|
+
resultType: "MESSAGE_CREATED",
|
|
145
|
+
id: BigInt(result.metadata.id)
|
|
146
|
+
};
|
|
147
|
+
else if (result.result_code === 1) return { resultType: "MESSAGE_DROPPED" };
|
|
148
|
+
else if (result.result_code === 9) return { resultType: "CHANNEL_NOT_FOUND" };
|
|
149
|
+
else throw new Error("Unexpected result code");
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region src/command/message/dequeue.ts
|
|
154
|
+
var Dequeue = class {
|
|
155
|
+
schema;
|
|
156
|
+
lockMs;
|
|
157
|
+
constructor(params) {
|
|
158
|
+
this.schema = params.schema;
|
|
159
|
+
this.lockMs = params.lockMs;
|
|
160
|
+
}
|
|
161
|
+
async execute(databaseClient) {
|
|
162
|
+
const result = await databaseClient.query(fragment`
|
|
163
|
+
SELECT
|
|
164
|
+
result_code,
|
|
165
|
+
metadata,
|
|
166
|
+
content,
|
|
167
|
+
state
|
|
168
|
+
FROM ${ref(this.schema)}."message_dequeue"($1::BIGINT)
|
|
169
|
+
`.value, [this.lockMs]).then((res) => res.rows[0]);
|
|
170
|
+
if (result.result_code === 2) return { resultType: "MESSAGE_NOT_AVAILABLE" };
|
|
171
|
+
else if (result.result_code === 3) return {
|
|
172
|
+
resultType: "MESSAGE_DEQUEUED",
|
|
173
|
+
id: BigInt(result.metadata.id),
|
|
174
|
+
isUnlocked: result.metadata.is_unlocked,
|
|
175
|
+
content: result.content,
|
|
176
|
+
state: result.state,
|
|
177
|
+
numAttempts: result.metadata.num_attempts
|
|
178
|
+
};
|
|
179
|
+
else throw new Error("Unexpected dequeue result");
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region src/command/message/complete.ts
|
|
184
|
+
var Complete = class {
|
|
185
|
+
schema;
|
|
186
|
+
id;
|
|
187
|
+
numAttempts;
|
|
188
|
+
constructor(params) {
|
|
189
|
+
this.schema = params.schema;
|
|
190
|
+
this.id = params.id;
|
|
191
|
+
this.numAttempts = params.numAttempts;
|
|
192
|
+
}
|
|
193
|
+
async execute(databaseClient) {
|
|
194
|
+
const result = await databaseClient.query(fragment`
|
|
195
|
+
SELECT * FROM ${ref(this.schema)}."message_complete"(
|
|
196
|
+
$1::BIGINT,
|
|
197
|
+
$2::BIGINT
|
|
198
|
+
)
|
|
199
|
+
`.value, [this.id.toString(), this.numAttempts]).then((res) => res.rows[0]);
|
|
200
|
+
if (result.result_code === 4) return { resultType: "MESSAGE_NOT_FOUND" };
|
|
201
|
+
else if (result.result_code === 5) return { resultType: "STATE_INVALID" };
|
|
202
|
+
else if (result.result_code === 6) return { resultType: "MESSAGE_COMPLETED" };
|
|
203
|
+
else throw new Error("Unexpected result");
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
//#endregion
|
|
207
|
+
//#region src/command/message/defer.ts
|
|
208
|
+
var Defer = class {
|
|
209
|
+
schema;
|
|
210
|
+
id;
|
|
211
|
+
numAttempts;
|
|
212
|
+
state;
|
|
213
|
+
dequeueAt;
|
|
214
|
+
constructor(params) {
|
|
215
|
+
this.schema = params.schema;
|
|
216
|
+
this.numAttempts = params.numAttempts;
|
|
217
|
+
this.id = params.id;
|
|
218
|
+
this.state = params.state;
|
|
219
|
+
this.dequeueAt = params.dequeueAt;
|
|
220
|
+
}
|
|
221
|
+
async execute(databaseClient) {
|
|
222
|
+
const result = await databaseClient.query(fragment`
|
|
223
|
+
SELECT * FROM ${ref(this.schema)}."message_defer"(
|
|
224
|
+
$1::BIGINT,
|
|
225
|
+
$2::BIGINT,
|
|
226
|
+
$3::BIGINT,
|
|
227
|
+
$4
|
|
228
|
+
)
|
|
229
|
+
`.value, [
|
|
230
|
+
this.id.toString(),
|
|
231
|
+
this.numAttempts,
|
|
232
|
+
this.dequeueAt,
|
|
233
|
+
this.state
|
|
234
|
+
]).then((res) => res.rows[0]);
|
|
235
|
+
if (result.result_code === 4) return { resultType: "MESSAGE_NOT_FOUND" };
|
|
236
|
+
else if (result.result_code === 5) return { resultType: "STATE_INVALID" };
|
|
237
|
+
else if (result.result_code === 7) return { resultType: "MESSAGE_DEFERRED" };
|
|
238
|
+
else throw new Error("Unexpected result");
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region src/command/message/heartbeat.ts
|
|
243
|
+
var Heartbeat = class {
|
|
244
|
+
schema;
|
|
245
|
+
id;
|
|
246
|
+
numAttempts;
|
|
247
|
+
lockMs;
|
|
248
|
+
constructor(params) {
|
|
249
|
+
this.schema = params.schema;
|
|
250
|
+
this.numAttempts = params.numAttempts;
|
|
251
|
+
this.id = params.id;
|
|
252
|
+
this.lockMs = params.lockMs;
|
|
253
|
+
}
|
|
254
|
+
async execute(databaseClient) {
|
|
255
|
+
const result = await databaseClient.query(fragment`
|
|
256
|
+
SELECT * FROM ${ref(this.schema)}."message_heartbeat"(
|
|
257
|
+
$1::BIGINT,
|
|
258
|
+
$2::BIGINT,
|
|
259
|
+
$3::BIGINT
|
|
260
|
+
)
|
|
261
|
+
`.value, [
|
|
262
|
+
this.id.toString(),
|
|
263
|
+
this.numAttempts,
|
|
264
|
+
this.lockMs
|
|
265
|
+
]).then((res) => res.rows[0]);
|
|
266
|
+
if (result.result_code === 4) return { resultType: "MESSAGE_NOT_FOUND" };
|
|
267
|
+
else if (result.result_code === 5) return { resultType: "MESSAGE_STATE_INVALID" };
|
|
268
|
+
else if (result.result_code === 8) return { resultType: "MESSAGE_HEARTBEATED" };
|
|
269
|
+
else throw new Error("Unexpected result");
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
//#endregion
|
|
273
|
+
//#region src/install/function/epoch.ts
|
|
274
|
+
const epoch = (params) => {
|
|
275
|
+
return [fragment`
|
|
276
|
+
CREATE FUNCTION ${ref(params.schema)}."epoch" ()
|
|
277
|
+
RETURNS BIGINT AS $$
|
|
278
|
+
DECLARE
|
|
279
|
+
v_now TIMESTAMPTZ;
|
|
280
|
+
BEGIN
|
|
281
|
+
v_now := NOW();
|
|
282
|
+
RETURN
|
|
283
|
+
EXTRACT(EPOCH FROM v_now)::BIGINT * 1_000 +
|
|
284
|
+
EXTRACT(MILLISECOND FROM v_now)::BIGINT;
|
|
285
|
+
END;
|
|
286
|
+
$$ LANGUAGE plpgsql;
|
|
287
|
+
`];
|
|
288
|
+
};
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region src/install/function/channel/release.ts
|
|
291
|
+
const release = (params) => {
|
|
292
|
+
return [fragment`
|
|
293
|
+
CREATE FUNCTION ${ref(params.schema)}."channel_release" (
|
|
294
|
+
p_text TEXT
|
|
295
|
+
) RETURNS TABLE (
|
|
296
|
+
result_code INTEGER
|
|
297
|
+
) AS $$
|
|
298
|
+
DECLARE
|
|
299
|
+
v_channel RECORD;
|
|
300
|
+
BEGIN
|
|
301
|
+
SELECT
|
|
302
|
+
"channel"."id",
|
|
303
|
+
"channel"."current_size"
|
|
304
|
+
FROM ${ref(params.schema)}."channel"
|
|
305
|
+
WHERE "name" = p_text
|
|
306
|
+
AND NOT "released"
|
|
307
|
+
FOR UPDATE
|
|
308
|
+
INTO v_channel;
|
|
309
|
+
|
|
310
|
+
IF v_channel."id" IS NULL THEN
|
|
311
|
+
RETURN QUERY SELECT
|
|
312
|
+
${value(9)};
|
|
313
|
+
RETURN;
|
|
314
|
+
END IF;
|
|
315
|
+
|
|
316
|
+
IF v_channel."current_size" = 0 THEN
|
|
317
|
+
DELETE FROM ${ref(params.schema)}."channel"
|
|
318
|
+
WHERE "id" = v_channel."id";
|
|
319
|
+
ELSE
|
|
320
|
+
UPDATE ${ref(params.schema)}."channel" SET
|
|
321
|
+
"released" = TRUE
|
|
322
|
+
WHERE "id" = v_channel."id";
|
|
323
|
+
END IF;
|
|
324
|
+
|
|
325
|
+
RETURN QUERY SELECT
|
|
326
|
+
${value(10)};
|
|
327
|
+
RETURN;
|
|
328
|
+
END;
|
|
329
|
+
$$ LANGUAGE plpgsql;
|
|
330
|
+
`];
|
|
331
|
+
};
|
|
332
|
+
//#endregion
|
|
333
|
+
//#region src/install/function/channel/set.ts
|
|
334
|
+
const set = (params) => {
|
|
335
|
+
return [fragment`
|
|
336
|
+
CREATE FUNCTION ${ref(params.schema)}."channel_set" (
|
|
337
|
+
p_text TEXT,
|
|
338
|
+
p_max_concurrency INTEGER,
|
|
339
|
+
p_max_size INTEGER,
|
|
340
|
+
p_release_interval_ms INTEGER
|
|
341
|
+
) RETURNS VOID AS $$
|
|
342
|
+
BEGIN
|
|
343
|
+
INSERT INTO ${ref(params.schema)}."channel" (
|
|
344
|
+
"name",
|
|
345
|
+
"max_concurrency",
|
|
346
|
+
"max_size",
|
|
347
|
+
"current_concurrency",
|
|
348
|
+
"current_size",
|
|
349
|
+
"release_interval_ms",
|
|
350
|
+
"released",
|
|
351
|
+
"dequeue_prev_at"
|
|
352
|
+
) VALUES (
|
|
353
|
+
p_text,
|
|
354
|
+
p_max_concurrency,
|
|
355
|
+
p_max_size,
|
|
356
|
+
0,
|
|
357
|
+
0,
|
|
358
|
+
p_release_interval_ms,
|
|
359
|
+
FALSE,
|
|
360
|
+
0
|
|
361
|
+
) ON CONFLICT ("name") WHERE NOT "released" DO UPDATE SET
|
|
362
|
+
"max_concurrency" = EXCLUDED."max_concurrency",
|
|
363
|
+
"max_size" = EXCLUDED."max_size",
|
|
364
|
+
"release_interval_ms" = EXCLUDED."release_interval_ms",
|
|
365
|
+
"released" = EXCLUDED."released",
|
|
366
|
+
"dequeue_next_at" = CASE
|
|
367
|
+
WHEN "channel"."message_id" IS NULL THEN NULL
|
|
368
|
+
ELSE GREATEST(
|
|
369
|
+
"channel"."message_dequeue_at",
|
|
370
|
+
"channel"."dequeue_prev_at" + EXCLUDED."release_interval_ms"
|
|
371
|
+
)
|
|
372
|
+
END;
|
|
373
|
+
END;
|
|
374
|
+
$$ LANGUAGE plpgsql;
|
|
375
|
+
`];
|
|
376
|
+
};
|
|
377
|
+
//#endregion
|
|
378
|
+
//#region src/install/function/message/create.ts
|
|
379
|
+
const create = (params) => {
|
|
380
|
+
return [fragment`
|
|
381
|
+
CREATE FUNCTION ${ref(params.schema)}."message_create" (
|
|
382
|
+
p_channel TEXT,
|
|
383
|
+
p_content BYTEA,
|
|
384
|
+
p_dequeue_at BIGINT
|
|
385
|
+
) RETURNS TABLE (
|
|
386
|
+
result_code INTEGER,
|
|
387
|
+
metadata JSON
|
|
388
|
+
) AS $$
|
|
389
|
+
DECLARE
|
|
390
|
+
v_now BIGINT;
|
|
391
|
+
v_dequeue_at BIGINT;
|
|
392
|
+
v_channel RECORD;
|
|
393
|
+
v_message RECORD;
|
|
394
|
+
BEGIN
|
|
395
|
+
v_now := ${ref(params.schema)}."epoch"();
|
|
396
|
+
v_dequeue_at := COALESCE(p_dequeue_at, v_now);
|
|
397
|
+
|
|
398
|
+
SELECT
|
|
399
|
+
"channel"."id",
|
|
400
|
+
"channel"."current_concurrency",
|
|
401
|
+
"channel"."current_size",
|
|
402
|
+
"channel"."max_concurrency",
|
|
403
|
+
"channel"."max_size",
|
|
404
|
+
"channel"."release_interval_ms",
|
|
405
|
+
"channel"."dequeue_prev_at",
|
|
406
|
+
"channel"."message_id",
|
|
407
|
+
"channel"."message_dequeue_at"
|
|
408
|
+
FROM ${ref(params.schema)}."channel"
|
|
409
|
+
WHERE "name" = p_channel
|
|
410
|
+
AND NOT "released"
|
|
411
|
+
FOR UPDATE
|
|
412
|
+
INTO v_channel;
|
|
413
|
+
|
|
414
|
+
IF v_channel."id" IS NULL THEN
|
|
415
|
+
RETURN QUERY SELECT
|
|
416
|
+
${value(9)},
|
|
417
|
+
NULL::JSON;
|
|
418
|
+
RETURN;
|
|
419
|
+
END IF;
|
|
420
|
+
|
|
421
|
+
IF v_channel."current_size" >= v_channel."max_size" THEN
|
|
422
|
+
RETURN QUERY SELECT
|
|
423
|
+
${value(1)},
|
|
424
|
+
NULL::JSON;
|
|
425
|
+
RETURN;
|
|
426
|
+
END IF;
|
|
427
|
+
|
|
428
|
+
INSERT INTO ${ref(params.schema)}."message" (
|
|
429
|
+
"channel_id",
|
|
430
|
+
"content",
|
|
431
|
+
"num_attempts",
|
|
432
|
+
"dequeue_at"
|
|
433
|
+
) VALUES (
|
|
434
|
+
v_channel."id",
|
|
435
|
+
p_content,
|
|
436
|
+
0,
|
|
437
|
+
v_dequeue_at
|
|
438
|
+
) RETURNING
|
|
439
|
+
"id"
|
|
440
|
+
INTO v_message;
|
|
441
|
+
|
|
442
|
+
IF
|
|
443
|
+
v_channel."message_id" IS NULL OR
|
|
444
|
+
v_dequeue_at < v_channel."message_dequeue_at" OR
|
|
445
|
+
v_dequeue_at = v_channel."message_dequeue_at" AND v_message."id" < v_channel."message_id"
|
|
446
|
+
THEN
|
|
447
|
+
UPDATE ${ref(params.schema)}."channel" SET
|
|
448
|
+
"current_size" = v_channel."current_size" + 1,
|
|
449
|
+
"message_id" = v_message."id",
|
|
450
|
+
"message_dequeue_at" = v_dequeue_at,
|
|
451
|
+
"dequeue_next_at" = GREATEST(
|
|
452
|
+
v_channel."dequeue_prev_at" + v_channel."release_interval_ms",
|
|
453
|
+
v_dequeue_at
|
|
454
|
+
)
|
|
455
|
+
WHERE "id" = v_channel."id";
|
|
456
|
+
ELSE
|
|
457
|
+
UPDATE ${ref(params.schema)}."channel" SET
|
|
458
|
+
"current_size" = v_channel."current_size" + 1
|
|
459
|
+
WHERE "id" = v_channel."id";
|
|
460
|
+
END IF;
|
|
461
|
+
|
|
462
|
+
IF ${value(params.eventChannel !== null)} THEN
|
|
463
|
+
PERFORM PG_NOTIFY(
|
|
464
|
+
${value(params.eventChannel)},
|
|
465
|
+
JSON_BUILD_OBJECT(
|
|
466
|
+
'type', ${value(0)},
|
|
467
|
+
'id', v_message."id"::TEXT,
|
|
468
|
+
'dequeue_at', v_dequeue_at
|
|
469
|
+
)::TEXT
|
|
470
|
+
);
|
|
471
|
+
END IF;
|
|
472
|
+
|
|
473
|
+
RETURN QUERY SELECT
|
|
474
|
+
${value(0)},
|
|
475
|
+
JSON_BUILD_OBJECT(
|
|
476
|
+
'id', v_message."id"::TEXT
|
|
477
|
+
);
|
|
478
|
+
END;
|
|
479
|
+
$$ LANGUAGE plpgsql;
|
|
480
|
+
`];
|
|
481
|
+
};
|
|
482
|
+
//#endregion
|
|
483
|
+
//#region src/install/function/message/defer.ts
|
|
484
|
+
const defer = (params) => {
|
|
485
|
+
return [fragment`
|
|
486
|
+
CREATE FUNCTION ${ref(params.schema)}."message_defer" (
|
|
487
|
+
p_id BIGINT,
|
|
488
|
+
p_num_attempts BIGINT,
|
|
489
|
+
p_dequeue_at BIGINT,
|
|
490
|
+
p_state BYTEA
|
|
491
|
+
)
|
|
492
|
+
RETURNS TABLE (
|
|
493
|
+
result_code INTEGER
|
|
494
|
+
) AS $$
|
|
495
|
+
DECLARE
|
|
496
|
+
v_now BIGINT;
|
|
497
|
+
v_channel RECORD;
|
|
498
|
+
v_message RECORD;
|
|
499
|
+
v_dequeue_at BIGINT;
|
|
500
|
+
BEGIN
|
|
501
|
+
v_now := ${ref(params.schema)}."epoch"();
|
|
502
|
+
|
|
503
|
+
SELECT
|
|
504
|
+
"message"."id",
|
|
505
|
+
"message"."channel_id",
|
|
506
|
+
"message"."num_attempts",
|
|
507
|
+
"message"."unlock_at"
|
|
508
|
+
FROM ${ref(params.schema)}."message"
|
|
509
|
+
WHERE "id" = p_id
|
|
510
|
+
FOR UPDATE
|
|
511
|
+
INTO v_message;
|
|
512
|
+
|
|
513
|
+
IF v_message."id" IS NULL THEN
|
|
514
|
+
RETURN QUERY SELECT
|
|
515
|
+
${value(4)};
|
|
516
|
+
RETURN;
|
|
517
|
+
ELSIF v_message."unlock_at" IS NULL OR v_message."num_attempts" <> p_num_attempts THEN
|
|
518
|
+
RETURN QUERY SELECT
|
|
519
|
+
${value(5)};
|
|
520
|
+
RETURN;
|
|
521
|
+
END IF;
|
|
522
|
+
|
|
523
|
+
SELECT
|
|
524
|
+
"channel"."current_concurrency",
|
|
525
|
+
"channel"."release_interval_ms",
|
|
526
|
+
"channel"."message_id",
|
|
527
|
+
"channel"."message_dequeue_at",
|
|
528
|
+
"channel"."dequeue_prev_at"
|
|
529
|
+
FROM ${ref(params.schema)}."channel"
|
|
530
|
+
WHERE "id" = v_message."channel_id"
|
|
531
|
+
FOR UPDATE
|
|
532
|
+
INTO v_channel;
|
|
533
|
+
|
|
534
|
+
v_dequeue_at := COALESCE(p_dequeue_at, v_now);
|
|
535
|
+
|
|
536
|
+
IF
|
|
537
|
+
v_channel."message_id" IS NULL OR
|
|
538
|
+
v_dequeue_at < v_channel."message_dequeue_at" OR
|
|
539
|
+
v_dequeue_at = v_channel."message_dequeue_at" AND v_message."id" < v_channel."message_id"
|
|
540
|
+
THEN
|
|
541
|
+
UPDATE ${ref(params.schema)}."channel" SET
|
|
542
|
+
"current_concurrency" = v_channel."current_concurrency" - 1,
|
|
543
|
+
"message_id" = v_message."id",
|
|
544
|
+
"message_dequeue_at" = v_dequeue_at,
|
|
545
|
+
"dequeue_next_at" = GREATEST(
|
|
546
|
+
v_channel."dequeue_prev_at" + v_channel."release_interval_ms",
|
|
547
|
+
v_dequeue_at
|
|
548
|
+
)
|
|
549
|
+
WHERE "id" = v_message."channel_id";
|
|
550
|
+
ELSE
|
|
551
|
+
UPDATE ${ref(params.schema)}."channel" SET
|
|
552
|
+
"current_concurrency" = v_channel."current_concurrency" - 1
|
|
553
|
+
WHERE "id" = v_message."channel_id";
|
|
554
|
+
END IF;
|
|
555
|
+
|
|
556
|
+
UPDATE ${ref(params.schema)}."message" SET
|
|
557
|
+
"state" = p_state,
|
|
558
|
+
"unlock_at" = NULL,
|
|
559
|
+
"dequeue_at" = v_dequeue_at
|
|
560
|
+
WHERE "id" = p_id;
|
|
561
|
+
|
|
562
|
+
IF ${value(params.eventChannel !== null)} THEN
|
|
563
|
+
PERFORM PG_NOTIFY(
|
|
564
|
+
${value(params.eventChannel)},
|
|
565
|
+
JSON_BUILD_OBJECT(
|
|
566
|
+
'type', ${value(7)},
|
|
567
|
+
'dequeue_at', v_dequeue_at,
|
|
568
|
+
'id', p_id::TEXT
|
|
569
|
+
)::TEXT
|
|
570
|
+
);
|
|
571
|
+
END IF;
|
|
572
|
+
|
|
573
|
+
RETURN QUERY SELECT
|
|
574
|
+
${value(7)};
|
|
575
|
+
RETURN;
|
|
576
|
+
END;
|
|
577
|
+
$$ LANGUAGE plpgsql;
|
|
578
|
+
`];
|
|
579
|
+
};
|
|
580
|
+
//#endregion
|
|
581
|
+
//#region src/install/function/message/dequeue.ts
|
|
582
|
+
const queryNextLockedMessage = (params) => fragment`
|
|
583
|
+
SELECT
|
|
584
|
+
"message"."id",
|
|
585
|
+
"message"."state",
|
|
586
|
+
"message"."content",
|
|
587
|
+
"message"."channel_id",
|
|
588
|
+
"message"."unlock_at",
|
|
589
|
+
"message"."num_attempts"
|
|
590
|
+
FROM ${ref(params.schema)}."message"
|
|
591
|
+
WHERE "unlock_at" IS NOT NULL
|
|
592
|
+
AND "unlock_at" <= ${params.now}
|
|
593
|
+
ORDER BY "unlock_at" ASC
|
|
594
|
+
`;
|
|
595
|
+
const queryNextChannel = (params) => fragment`
|
|
596
|
+
SELECT
|
|
597
|
+
"channel"."id",
|
|
598
|
+
"channel"."release_interval_ms",
|
|
599
|
+
"channel"."message_id",
|
|
600
|
+
"channel"."dequeue_next_at",
|
|
601
|
+
"channel"."dequeue_prev_at",
|
|
602
|
+
"channel"."current_concurrency"
|
|
603
|
+
FROM ${ref(params.schema)}."channel"
|
|
604
|
+
WHERE "message_id" IS NOT NULL
|
|
605
|
+
AND "current_concurrency" < "max_concurrency"
|
|
606
|
+
ORDER BY "dequeue_next_at" ASC
|
|
607
|
+
`;
|
|
608
|
+
const queryNextChannelMessage = (params) => fragment`
|
|
609
|
+
SELECT
|
|
610
|
+
"message"."id",
|
|
611
|
+
"message"."dequeue_at"
|
|
612
|
+
FROM ${ref(params.schema)}."message"
|
|
613
|
+
WHERE "unlock_at" IS NULL
|
|
614
|
+
AND "channel_id" = ${params.channel}
|
|
615
|
+
ORDER BY "dequeue_at" ASC, "id" ASC
|
|
616
|
+
`;
|
|
617
|
+
const dequeue = (params) => {
|
|
618
|
+
const nextLockedMessage = queryNextLockedMessage({
|
|
619
|
+
now: fragment`v_now`,
|
|
620
|
+
schema: params.schema
|
|
621
|
+
});
|
|
622
|
+
const nextChannelMessage = queryNextChannelMessage({
|
|
623
|
+
channel: fragment`v_channel."id"`,
|
|
624
|
+
schema: params.schema
|
|
625
|
+
});
|
|
626
|
+
const nextChannel = queryNextChannel({ schema: params.schema });
|
|
627
|
+
return [fragment`
|
|
628
|
+
CREATE FUNCTION ${ref(params.schema)}."message_dequeue" (
|
|
629
|
+
p_lock_ms BIGINT
|
|
630
|
+
)
|
|
631
|
+
RETURNS TABLE (
|
|
632
|
+
result_code INTEGER,
|
|
633
|
+
content BYTEA,
|
|
634
|
+
state BYTEA,
|
|
635
|
+
metadata JSON
|
|
636
|
+
) AS $$
|
|
637
|
+
DECLARE
|
|
638
|
+
v_now BIGINT;
|
|
639
|
+
v_channel RECORD;
|
|
640
|
+
v_message_locked RECORD;
|
|
641
|
+
v_message_dequeue RECORD;
|
|
642
|
+
v_message_next RECORD;
|
|
643
|
+
BEGIN
|
|
644
|
+
v_now := ${ref(params.schema)}."epoch"();
|
|
645
|
+
|
|
646
|
+
${nextLockedMessage}
|
|
647
|
+
FOR UPDATE
|
|
648
|
+
SKIP LOCKED
|
|
649
|
+
LIMIT 1
|
|
650
|
+
INTO v_message_locked;
|
|
651
|
+
|
|
652
|
+
IF v_message_locked."id" IS NOT NULL THEN
|
|
653
|
+
UPDATE ${ref(params.schema)}."message" SET
|
|
654
|
+
"num_attempts" = v_message_locked."num_attempts" + 1,
|
|
655
|
+
"unlock_at" = v_now + p_lock_ms
|
|
656
|
+
WHERE "id" = v_message_locked."id";
|
|
657
|
+
|
|
658
|
+
RETURN QUERY SELECT
|
|
659
|
+
${value(3)},
|
|
660
|
+
v_message_locked.content,
|
|
661
|
+
v_message_locked.state,
|
|
662
|
+
JSON_BUILD_OBJECT(
|
|
663
|
+
'id', v_message_locked."id",
|
|
664
|
+
'is_unlocked', TRUE,
|
|
665
|
+
'num_attempts', v_message_locked."num_attempts" + 1
|
|
666
|
+
);
|
|
667
|
+
RETURN;
|
|
668
|
+
END IF;
|
|
669
|
+
|
|
670
|
+
${nextChannel}
|
|
671
|
+
FOR UPDATE
|
|
672
|
+
SKIP LOCKED
|
|
673
|
+
LIMIT 1
|
|
674
|
+
INTO v_channel;
|
|
675
|
+
|
|
676
|
+
IF v_channel."id" IS NULL OR v_channel."dequeue_next_at" > v_now THEN
|
|
677
|
+
RETURN QUERY SELECT
|
|
678
|
+
${value(2)},
|
|
679
|
+
NULL::BYTEA,
|
|
680
|
+
NULL::BYTEA,
|
|
681
|
+
NULL::JSON;
|
|
682
|
+
RETURN;
|
|
683
|
+
END IF;
|
|
684
|
+
|
|
685
|
+
SELECT
|
|
686
|
+
"message"."id",
|
|
687
|
+
"message"."channel_id",
|
|
688
|
+
"message"."content",
|
|
689
|
+
"message"."num_attempts",
|
|
690
|
+
"message"."state"
|
|
691
|
+
FROM ${ref(params.schema)}."message"
|
|
692
|
+
WHERE "id" = v_channel."message_id"
|
|
693
|
+
INTO v_message_dequeue;
|
|
694
|
+
|
|
695
|
+
UPDATE ${ref(params.schema)}."message" SET
|
|
696
|
+
"num_attempts" = v_message_dequeue."num_attempts" + 1,
|
|
697
|
+
"unlock_at" = v_now + p_lock_ms
|
|
698
|
+
WHERE "id" = v_message_dequeue."id";
|
|
699
|
+
|
|
700
|
+
${nextChannelMessage}
|
|
701
|
+
LIMIT 1
|
|
702
|
+
INTO v_message_next;
|
|
703
|
+
|
|
704
|
+
IF v_message_next."id" IS NULL THEN
|
|
705
|
+
UPDATE ${ref(params.schema)}."channel" SET
|
|
706
|
+
"current_concurrency" = v_channel."current_concurrency" + 1,
|
|
707
|
+
"dequeue_prev_at" = v_now,
|
|
708
|
+
"message_id" = NULL
|
|
709
|
+
WHERE "id" = v_channel."id";
|
|
710
|
+
ELSE
|
|
711
|
+
UPDATE ${ref(params.schema)}."channel" SET
|
|
712
|
+
"current_concurrency" = v_channel."current_concurrency" + 1,
|
|
713
|
+
"message_id" = v_message_next."id",
|
|
714
|
+
"message_dequeue_at" = v_message_next."dequeue_at",
|
|
715
|
+
"dequeue_prev_at" = v_now,
|
|
716
|
+
"dequeue_next_at" = GREATEST(
|
|
717
|
+
v_message_next."dequeue_at",
|
|
718
|
+
v_now + v_channel."release_interval_ms"
|
|
719
|
+
)
|
|
720
|
+
WHERE "id" = v_channel."id";
|
|
721
|
+
END IF;
|
|
722
|
+
|
|
723
|
+
RETURN QUERY SELECT
|
|
724
|
+
${value(3)},
|
|
725
|
+
v_message_dequeue."content",
|
|
726
|
+
v_message_dequeue."state",
|
|
727
|
+
JSON_BUILD_OBJECT(
|
|
728
|
+
'id', v_message_dequeue."id"::TEXT,
|
|
729
|
+
'is_unlocked', FALSE,
|
|
730
|
+
'num_attempts', v_message_dequeue."num_attempts" + 1
|
|
731
|
+
);
|
|
732
|
+
RETURN;
|
|
733
|
+
END;
|
|
734
|
+
$$ LANGUAGE plpgsql;
|
|
735
|
+
`];
|
|
736
|
+
};
|
|
737
|
+
//#endregion
|
|
738
|
+
//#region src/install/function/message/heartbeat.ts
|
|
739
|
+
const heartbeat = (params) => {
|
|
740
|
+
return [fragment`
|
|
741
|
+
CREATE FUNCTION ${ref(params.schema)}."message_heartbeat" (
|
|
742
|
+
p_id BIGINT,
|
|
743
|
+
p_num_attempts BIGINT,
|
|
744
|
+
p_lock_ms BIGINT
|
|
745
|
+
)
|
|
746
|
+
RETURNS TABLE (
|
|
747
|
+
result_code INTEGER
|
|
748
|
+
) AS $$
|
|
749
|
+
DECLARE
|
|
750
|
+
v_now BIGINT;
|
|
751
|
+
v_message RECORD;
|
|
752
|
+
BEGIN
|
|
753
|
+
v_now := ${ref(params.schema)}."epoch"();
|
|
754
|
+
|
|
755
|
+
SELECT
|
|
756
|
+
"message"."id",
|
|
757
|
+
"message"."num_attempts",
|
|
758
|
+
"message"."unlock_at"
|
|
759
|
+
FROM ${ref(params.schema)}."message"
|
|
760
|
+
WHERE "id" = p_id
|
|
761
|
+
FOR UPDATE
|
|
762
|
+
INTO v_message;
|
|
763
|
+
|
|
764
|
+
IF v_message."id" IS NULL THEN
|
|
765
|
+
RETURN QUERY SELECT
|
|
766
|
+
${value(4)};
|
|
767
|
+
RETURN;
|
|
768
|
+
ELSIF v_message."unlock_at" IS NULL OR v_message."num_attempts" <> p_num_attempts THEN
|
|
769
|
+
RETURN QUERY SELECT
|
|
770
|
+
${value(5)};
|
|
771
|
+
RETURN;
|
|
772
|
+
END IF;
|
|
773
|
+
|
|
774
|
+
UPDATE ${ref(params.schema)}."message" SET
|
|
775
|
+
"unlock_at" = GREATEST(
|
|
776
|
+
v_now + p_lock_ms,
|
|
777
|
+
v_message."unlock_at"
|
|
778
|
+
)
|
|
779
|
+
WHERE "id" = p_id;
|
|
780
|
+
|
|
781
|
+
RETURN QUERY SELECT
|
|
782
|
+
${value(8)};
|
|
783
|
+
RETURN;
|
|
784
|
+
END;
|
|
785
|
+
$$ LANGUAGE plpgsql;
|
|
786
|
+
`];
|
|
787
|
+
};
|
|
788
|
+
//#endregion
|
|
789
|
+
//#region src/install/function/message/complete.ts
|
|
790
|
+
const complete = (params) => {
|
|
791
|
+
return [fragment`
|
|
792
|
+
CREATE FUNCTION ${ref(params.schema)}."message_complete" (
|
|
793
|
+
p_id BIGINT,
|
|
794
|
+
p_num_attempts BIGINT
|
|
795
|
+
)
|
|
796
|
+
RETURNS TABLE (
|
|
797
|
+
result_code INTEGER
|
|
798
|
+
) AS $$
|
|
799
|
+
DECLARE
|
|
800
|
+
v_channel RECORD;
|
|
801
|
+
v_message RECORD;
|
|
802
|
+
BEGIN
|
|
803
|
+
SELECT
|
|
804
|
+
"message"."id",
|
|
805
|
+
"message"."channel_id",
|
|
806
|
+
"message"."num_attempts",
|
|
807
|
+
"message"."unlock_at"
|
|
808
|
+
FROM ${ref(params.schema)}."message"
|
|
809
|
+
WHERE "id" = p_id
|
|
810
|
+
FOR UPDATE
|
|
811
|
+
INTO v_message;
|
|
812
|
+
|
|
813
|
+
IF v_message."id" IS NULL THEN
|
|
814
|
+
RETURN QUERY SELECT
|
|
815
|
+
${value(4)};
|
|
816
|
+
RETURN;
|
|
817
|
+
ELSIF v_message."unlock_at" IS NULL OR v_message."num_attempts" <> p_num_attempts THEN
|
|
818
|
+
RETURN QUERY SELECT
|
|
819
|
+
${value(5)};
|
|
820
|
+
RETURN;
|
|
821
|
+
END IF;
|
|
822
|
+
|
|
823
|
+
SELECT
|
|
824
|
+
"channel"."id",
|
|
825
|
+
"channel"."released",
|
|
826
|
+
"channel"."current_size",
|
|
827
|
+
"channel"."current_concurrency"
|
|
828
|
+
FROM ${ref(params.schema)}."channel"
|
|
829
|
+
WHERE "id" = v_message."channel_id"
|
|
830
|
+
FOR UPDATE
|
|
831
|
+
INTO v_channel;
|
|
832
|
+
|
|
833
|
+
IF v_channel."released" AND v_channel."current_size" = 1 THEN
|
|
834
|
+
DELETE FROM ${ref(params.schema)}."channel"
|
|
835
|
+
WHERE "id" = v_channel."id";
|
|
836
|
+
ELSE
|
|
837
|
+
UPDATE ${ref(params.schema)}."channel" SET
|
|
838
|
+
"current_concurrency" = v_channel."current_concurrency" - 1,
|
|
839
|
+
"current_size" = v_channel."current_size" - 1
|
|
840
|
+
WHERE "id" = v_channel."id";
|
|
841
|
+
END IF;
|
|
842
|
+
|
|
843
|
+
IF ${value(params.eventChannel !== null)} THEN
|
|
844
|
+
PERFORM PG_NOTIFY(
|
|
845
|
+
${value(params.eventChannel)},
|
|
846
|
+
JSON_BUILD_OBJECT(
|
|
847
|
+
'type', ${value(6)},
|
|
848
|
+
'id', p_id::TEXT
|
|
849
|
+
)::TEXT
|
|
850
|
+
);
|
|
851
|
+
END IF;
|
|
852
|
+
|
|
853
|
+
DELETE FROM ${ref(params.schema)}."message"
|
|
854
|
+
WHERE "id" = p_id;
|
|
855
|
+
|
|
856
|
+
RETURN QUERY SELECT
|
|
857
|
+
${value(6)};
|
|
858
|
+
RETURN;
|
|
859
|
+
END;
|
|
860
|
+
$$ LANGUAGE plpgsql;
|
|
861
|
+
`];
|
|
862
|
+
};
|
|
863
|
+
//#endregion
|
|
864
|
+
//#region src/install/table/channel.ts
|
|
865
|
+
const channel = (params) => {
|
|
866
|
+
const dequeueIndex = [params.schema, "channel_dequeue_ix"].join("_");
|
|
867
|
+
const nameIndex = [params.schema, "channel_name_ix"].join("_");
|
|
868
|
+
return [
|
|
869
|
+
fragment`
|
|
870
|
+
CREATE TABLE ${ref(params.schema)}."channel" (
|
|
871
|
+
"id" BIGSERIAL NOT NULL,
|
|
872
|
+
"name" TEXT NOT NULL,
|
|
873
|
+
"released" BOOLEAN NOT NULL,
|
|
874
|
+
"max_concurrency" INTEGER NOT NULL,
|
|
875
|
+
"max_size" INTEGER NOT NULL,
|
|
876
|
+
"release_interval_ms" INTEGER NOT NULL,
|
|
877
|
+
"current_size" INTEGER NOT NULL,
|
|
878
|
+
"current_concurrency" INTEGER NOT NULL,
|
|
879
|
+
"message_id" BIGINT,
|
|
880
|
+
"message_dequeue_at" BIGINT,
|
|
881
|
+
"dequeue_prev_at" BIGINT NOT NULL,
|
|
882
|
+
"dequeue_next_at" BIGINT NULL,
|
|
883
|
+
PRIMARY KEY ("id")
|
|
884
|
+
);
|
|
885
|
+
`,
|
|
886
|
+
fragment`
|
|
887
|
+
CREATE UNIQUE INDEX ${ref(nameIndex)}
|
|
888
|
+
ON ${ref(params.schema)}."channel" (
|
|
889
|
+
"name"
|
|
890
|
+
) WHERE NOT "released";
|
|
891
|
+
`,
|
|
892
|
+
fragment`
|
|
893
|
+
CREATE INDEX ${ref(dequeueIndex)}
|
|
894
|
+
ON ${ref(params.schema)}."channel" (
|
|
895
|
+
"dequeue_next_at" ASC
|
|
896
|
+
) WHERE "message_id" IS NOT NULL
|
|
897
|
+
AND "current_concurrency" < "max_concurrency";
|
|
898
|
+
`
|
|
899
|
+
];
|
|
900
|
+
};
|
|
901
|
+
//#endregion
|
|
902
|
+
//#region src/install/table/message.ts
|
|
903
|
+
const message = (params) => {
|
|
904
|
+
return [
|
|
905
|
+
fragment`
|
|
906
|
+
CREATE TABLE ${ref(params.schema)}."message" (
|
|
907
|
+
"id" BIGSERIAL NOT NULL,
|
|
908
|
+
"channel_id" BIGINT NOT NULL,
|
|
909
|
+
"content" BYTEA NOT NULL,
|
|
910
|
+
"state" BYTEA,
|
|
911
|
+
"num_attempts" BIGINT NOT NULL,
|
|
912
|
+
"dequeue_at" BIGINT NOT NULL,
|
|
913
|
+
"unlock_at" BIGINT,
|
|
914
|
+
PRIMARY KEY ("id")
|
|
915
|
+
);
|
|
916
|
+
`,
|
|
917
|
+
fragment`
|
|
918
|
+
CREATE INDEX "message_dequeue_ix"
|
|
919
|
+
ON ${ref(params.schema)}."message" (
|
|
920
|
+
"channel_id",
|
|
921
|
+
"dequeue_at" ASC,
|
|
922
|
+
"id" ASC
|
|
923
|
+
) WHERE "unlock_at" IS NULL;
|
|
924
|
+
`,
|
|
925
|
+
fragment`
|
|
926
|
+
CREATE INDEX "message_locked_dequeue_ix"
|
|
927
|
+
ON ${ref(params.schema)}."message" (
|
|
928
|
+
"unlock_at" ASC
|
|
929
|
+
) WHERE "unlock_at" IS NOT NULL;
|
|
930
|
+
`
|
|
931
|
+
];
|
|
932
|
+
};
|
|
933
|
+
//#endregion
|
|
934
|
+
//#region src/queue/module/channel/message.ts
|
|
935
|
+
var Message$1 = class {
|
|
936
|
+
schema;
|
|
937
|
+
channel;
|
|
938
|
+
adaptor;
|
|
939
|
+
constructor(params) {
|
|
940
|
+
this.schema = params.schema;
|
|
941
|
+
this.adaptor = params.adaptor;
|
|
942
|
+
this.channel = params.channel;
|
|
943
|
+
}
|
|
944
|
+
async create(params) {
|
|
945
|
+
const adaptedClient = this.adaptor(params.databaseClient);
|
|
946
|
+
return await new Create({
|
|
947
|
+
schema: this.schema,
|
|
948
|
+
channel: this.channel,
|
|
949
|
+
content: params.content,
|
|
950
|
+
dequeueAt: params.dequeueAt ?? null
|
|
951
|
+
}).execute(adaptedClient);
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
//#endregion
|
|
955
|
+
//#region src/queue/module/channel/index.ts
|
|
956
|
+
var Channel = class {
|
|
957
|
+
schema;
|
|
958
|
+
adaptor;
|
|
959
|
+
channel;
|
|
960
|
+
message;
|
|
961
|
+
constructor(params) {
|
|
962
|
+
this.schema = params.schema;
|
|
963
|
+
this.adaptor = params.adaptor;
|
|
964
|
+
this.channel = params.channel;
|
|
965
|
+
this.message = new Message$1({
|
|
966
|
+
schema: params.schema,
|
|
967
|
+
adaptor: params.adaptor,
|
|
968
|
+
channel: params.channel
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
set(params) {
|
|
972
|
+
const adaptedClient = this.adaptor(params.databaseClient);
|
|
973
|
+
return new Set({
|
|
974
|
+
schema: this.schema,
|
|
975
|
+
channel: this.channel,
|
|
976
|
+
maxConcurrency: params.maxConcurrency ?? 2147483647,
|
|
977
|
+
maxSize: params.maxSize ?? 2147483647,
|
|
978
|
+
releaseIntervalMs: params.releaseIntervalMs ?? 0
|
|
979
|
+
}).execute(adaptedClient);
|
|
980
|
+
}
|
|
981
|
+
release(params) {
|
|
982
|
+
const adaptedClient = this.adaptor(params.databaseClient);
|
|
983
|
+
return new Release({
|
|
984
|
+
schema: this.schema,
|
|
985
|
+
channel: this.channel
|
|
986
|
+
}).execute(adaptedClient);
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
//#endregion
|
|
990
|
+
//#region src/event.ts
|
|
991
|
+
const decode = (payload) => {
|
|
992
|
+
const parsed = JSON.parse(payload);
|
|
993
|
+
if (parsed.type === 0) return {
|
|
994
|
+
eventType: "MESSAGE_CREATED",
|
|
995
|
+
id: parsed.id,
|
|
996
|
+
dequeueAt: Number(parsed.dequeue_at)
|
|
997
|
+
};
|
|
998
|
+
else if (parsed.type === 6) return {
|
|
999
|
+
eventType: "MESSAGE_COMPLETED",
|
|
1000
|
+
id: parsed.id
|
|
1001
|
+
};
|
|
1002
|
+
else if (parsed.type === 7) return {
|
|
1003
|
+
eventType: "MESSAGE_DEFERRED",
|
|
1004
|
+
id: parsed.id,
|
|
1005
|
+
dequeueAt: Number(parsed.dequeue_at)
|
|
1006
|
+
};
|
|
1007
|
+
else throw new Error("Unknown event type");
|
|
1008
|
+
};
|
|
1009
|
+
//#endregion
|
|
1010
|
+
//#region src/queue/message.ts
|
|
1011
|
+
var Message = class {
|
|
1012
|
+
schema;
|
|
1013
|
+
adaptor;
|
|
1014
|
+
id;
|
|
1015
|
+
isUnlocked;
|
|
1016
|
+
content;
|
|
1017
|
+
state;
|
|
1018
|
+
numAttempts;
|
|
1019
|
+
constructor(params) {
|
|
1020
|
+
this.schema = params.schema;
|
|
1021
|
+
this.adaptor = params.adaptor;
|
|
1022
|
+
this.id = params.id;
|
|
1023
|
+
this.isUnlocked = params.isUnlocked;
|
|
1024
|
+
this.content = params.content;
|
|
1025
|
+
this.state = params.state;
|
|
1026
|
+
this.numAttempts = params.numAttempts;
|
|
1027
|
+
}
|
|
1028
|
+
async defer(params) {
|
|
1029
|
+
const adaptedClient = this.adaptor(params.databaseClient);
|
|
1030
|
+
return new Defer({
|
|
1031
|
+
schema: this.schema,
|
|
1032
|
+
id: this.id,
|
|
1033
|
+
numAttempts: this.numAttempts,
|
|
1034
|
+
dequeueAt: params.dequeueAt ?? null,
|
|
1035
|
+
state: params.state ?? null
|
|
1036
|
+
}).execute(adaptedClient);
|
|
1037
|
+
}
|
|
1038
|
+
async complete(params) {
|
|
1039
|
+
const adaptedClient = this.adaptor(params.databaseClient);
|
|
1040
|
+
return new Complete({
|
|
1041
|
+
schema: this.schema,
|
|
1042
|
+
numAttempts: this.numAttempts,
|
|
1043
|
+
id: this.id
|
|
1044
|
+
}).execute(adaptedClient);
|
|
1045
|
+
}
|
|
1046
|
+
async heartbeat(params) {
|
|
1047
|
+
const adaptedClient = this.adaptor(params.databaseClient);
|
|
1048
|
+
return new Heartbeat({
|
|
1049
|
+
schema: this.schema,
|
|
1050
|
+
id: this.id,
|
|
1051
|
+
numAttempts: this.numAttempts,
|
|
1052
|
+
lockMs: params.lockMs
|
|
1053
|
+
}).execute(adaptedClient);
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
//#endregion
|
|
1057
|
+
//#region src/queue/index.ts
|
|
1058
|
+
var Queue = class {
|
|
1059
|
+
schema;
|
|
1060
|
+
lockMs;
|
|
1061
|
+
adaptor;
|
|
1062
|
+
constructor(params) {
|
|
1063
|
+
this.schema = params.schema;
|
|
1064
|
+
this.lockMs = params.lockMs;
|
|
1065
|
+
this.adaptor = params.adaptor ? params.adaptor : (x) => x;
|
|
1066
|
+
}
|
|
1067
|
+
async dequeue(params) {
|
|
1068
|
+
const dequeue = new Dequeue({
|
|
1069
|
+
schema: this.schema,
|
|
1070
|
+
lockMs: this.lockMs
|
|
1071
|
+
});
|
|
1072
|
+
const adaptedClient = this.adaptor(params.databaseClient);
|
|
1073
|
+
const result = await dequeue.execute(adaptedClient);
|
|
1074
|
+
if (result.resultType === "MESSAGE_DEQUEUED") return {
|
|
1075
|
+
resultType: "MESSAGE_DEQUEUED",
|
|
1076
|
+
message: new Message({
|
|
1077
|
+
schema: this.schema,
|
|
1078
|
+
adaptor: this.adaptor,
|
|
1079
|
+
id: result.id,
|
|
1080
|
+
isUnlocked: result.isUnlocked,
|
|
1081
|
+
content: result.content,
|
|
1082
|
+
state: result.state,
|
|
1083
|
+
numAttempts: result.numAttempts
|
|
1084
|
+
})
|
|
1085
|
+
};
|
|
1086
|
+
else return result;
|
|
1087
|
+
}
|
|
1088
|
+
channel(channel) {
|
|
1089
|
+
return new Channel({
|
|
1090
|
+
adaptor: this.adaptor,
|
|
1091
|
+
schema: this.schema,
|
|
1092
|
+
channel
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
install(params = {}) {
|
|
1096
|
+
return [
|
|
1097
|
+
channel,
|
|
1098
|
+
message,
|
|
1099
|
+
epoch,
|
|
1100
|
+
set,
|
|
1101
|
+
release,
|
|
1102
|
+
create,
|
|
1103
|
+
dequeue,
|
|
1104
|
+
complete,
|
|
1105
|
+
defer,
|
|
1106
|
+
heartbeat
|
|
1107
|
+
].flatMap((install) => install({
|
|
1108
|
+
schema: this.schema,
|
|
1109
|
+
eventChannel: params.eventChannel ?? null
|
|
1110
|
+
})).map((sql) => dedent(sql.value));
|
|
1111
|
+
}
|
|
1112
|
+
static decode(payload) {
|
|
1113
|
+
return decode(payload);
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
//#endregion
|
|
1117
|
+
export { Queue };
|