@holo-js/queue 0.2.6 → 0.3.1
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/chunk-5MPOVBVE.mjs +274 -0
- package/dist/config-Bleufve4.d.ts +394 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.mjs +32 -0
- package/dist/index.d.ts +7 -434
- package/dist/index.mjs +102 -399
- package/package.json +9 -9
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_DATABASE_QUEUE_TABLE,
|
|
3
|
+
DEFAULT_FAILED_JOBS_CONNECTION,
|
|
4
|
+
DEFAULT_FAILED_JOBS_TABLE,
|
|
5
|
+
DEFAULT_QUEUE_BLOCK_FOR,
|
|
6
|
+
DEFAULT_QUEUE_CONNECTION,
|
|
7
|
+
DEFAULT_QUEUE_NAME,
|
|
8
|
+
DEFAULT_QUEUE_RETRY_AFTER,
|
|
9
|
+
DEFAULT_QUEUE_SLEEP,
|
|
10
|
+
holoQueueDefaults,
|
|
11
|
+
normalizeQueueConfig,
|
|
12
|
+
queueInternals
|
|
13
|
+
} from "./chunk-5MPOVBVE.mjs";
|
|
14
|
+
|
|
1
15
|
// src/contracts.ts
|
|
2
16
|
function getQueueJobDispatcher() {
|
|
3
17
|
return globalThis.__holoQueueJobDispatcher__;
|
|
@@ -229,247 +243,6 @@ var queueJobInternals = {
|
|
|
229
243
|
setQueueJobSyncDispatcher
|
|
230
244
|
};
|
|
231
245
|
|
|
232
|
-
// src/config.ts
|
|
233
|
-
var DEFAULT_QUEUE_CONNECTION = "sync";
|
|
234
|
-
var DEFAULT_QUEUE_NAME = "default";
|
|
235
|
-
var DEFAULT_QUEUE_RETRY_AFTER = 90;
|
|
236
|
-
var DEFAULT_QUEUE_BLOCK_FOR = 5;
|
|
237
|
-
var DEFAULT_QUEUE_SLEEP = 1;
|
|
238
|
-
var DEFAULT_FAILED_JOBS_CONNECTION = "default";
|
|
239
|
-
var DEFAULT_FAILED_JOBS_TABLE = "failed_jobs";
|
|
240
|
-
var DEFAULT_DATABASE_QUEUE_TABLE = "jobs";
|
|
241
|
-
var DEFAULT_REDIS_HOST = "127.0.0.1";
|
|
242
|
-
var DEFAULT_REDIS_PORT = 6379;
|
|
243
|
-
var DEFAULT_REDIS_DB = 0;
|
|
244
|
-
var DEFAULT_QUEUE_CONFIG = Object.freeze({
|
|
245
|
-
default: DEFAULT_QUEUE_CONNECTION,
|
|
246
|
-
failed: Object.freeze({
|
|
247
|
-
driver: "database",
|
|
248
|
-
connection: DEFAULT_FAILED_JOBS_CONNECTION,
|
|
249
|
-
table: DEFAULT_FAILED_JOBS_TABLE
|
|
250
|
-
}),
|
|
251
|
-
connections: Object.freeze({
|
|
252
|
-
[DEFAULT_QUEUE_CONNECTION]: Object.freeze({
|
|
253
|
-
name: DEFAULT_QUEUE_CONNECTION,
|
|
254
|
-
driver: "sync",
|
|
255
|
-
queue: DEFAULT_QUEUE_NAME
|
|
256
|
-
})
|
|
257
|
-
})
|
|
258
|
-
});
|
|
259
|
-
function parseInteger(value, fallback, label, options = {}) {
|
|
260
|
-
if (typeof value === "undefined") {
|
|
261
|
-
return fallback;
|
|
262
|
-
}
|
|
263
|
-
const normalized = typeof value === "number" ? value : value.trim();
|
|
264
|
-
if (typeof normalized === "string" && !/^[+-]?\d+$/.test(normalized)) {
|
|
265
|
-
throw new Error(`[Holo Queue] ${label} must be an integer.`);
|
|
266
|
-
}
|
|
267
|
-
const integer = typeof normalized === "number" ? normalized : Number(normalized);
|
|
268
|
-
if (!Number.isInteger(integer)) {
|
|
269
|
-
throw new Error(`[Holo Queue] ${label} must be an integer.`);
|
|
270
|
-
}
|
|
271
|
-
if (typeof options.minimum === "number" && integer < options.minimum) {
|
|
272
|
-
throw new Error(`[Holo Queue] ${label} must be greater than or equal to ${options.minimum}.`);
|
|
273
|
-
}
|
|
274
|
-
return integer;
|
|
275
|
-
}
|
|
276
|
-
function normalizeConnectionName(value, label) {
|
|
277
|
-
const normalized = value?.trim();
|
|
278
|
-
if (!normalized) {
|
|
279
|
-
throw new Error(`[Holo Queue] ${label} must be a non-empty string.`);
|
|
280
|
-
}
|
|
281
|
-
return normalized;
|
|
282
|
-
}
|
|
283
|
-
function normalizeQueueName(value) {
|
|
284
|
-
return value?.trim() || DEFAULT_QUEUE_NAME;
|
|
285
|
-
}
|
|
286
|
-
function normalizeOptionalRedisString(value) {
|
|
287
|
-
return value?.trim() || void 0;
|
|
288
|
-
}
|
|
289
|
-
function normalizeSyncConnection(name, config) {
|
|
290
|
-
return Object.freeze({
|
|
291
|
-
name,
|
|
292
|
-
driver: "sync",
|
|
293
|
-
queue: normalizeQueueName(config.queue)
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
function resolveSharedRedisConnection(redisConfig, connectionName) {
|
|
297
|
-
const resolvedConnection = redisConfig.connections[connectionName];
|
|
298
|
-
if (!resolvedConnection) {
|
|
299
|
-
const availableConnections = Object.keys(redisConfig.connections);
|
|
300
|
-
throw new Error(
|
|
301
|
-
`[Holo Queue] Queue Redis connection "${connectionName}" was not found in shared Redis config. Available connections: ${availableConnections.join(", ") || "(none)"}.`
|
|
302
|
-
);
|
|
303
|
-
}
|
|
304
|
-
return resolvedConnection;
|
|
305
|
-
}
|
|
306
|
-
function parseRedisDatabaseFromUrl(url, label) {
|
|
307
|
-
if (typeof url === "undefined") {
|
|
308
|
-
return void 0;
|
|
309
|
-
}
|
|
310
|
-
let parsed;
|
|
311
|
-
try {
|
|
312
|
-
parsed = new URL(url);
|
|
313
|
-
} catch (error) {
|
|
314
|
-
throw new Error(`[Holo Queue] ${label} is invalid: ${String(error)}`);
|
|
315
|
-
}
|
|
316
|
-
if (parsed.protocol !== "redis:" && parsed.protocol !== "rediss:") {
|
|
317
|
-
throw new Error(`[Holo Queue] ${label} is invalid: unsupported protocol "${parsed.protocol}".`);
|
|
318
|
-
}
|
|
319
|
-
const pathname = parsed.pathname.replace(/^\/+/, "");
|
|
320
|
-
if (!pathname) {
|
|
321
|
-
return void 0;
|
|
322
|
-
}
|
|
323
|
-
const [databaseSegment] = pathname.split("/");
|
|
324
|
-
if (!databaseSegment || !/^\d+$/.test(databaseSegment) || pathname !== databaseSegment) {
|
|
325
|
-
throw new Error(`[Holo Queue] ${label} must include at most one integer database path segment.`);
|
|
326
|
-
}
|
|
327
|
-
return Number(databaseSegment);
|
|
328
|
-
}
|
|
329
|
-
function normalizeRedisClusterNodes(connectionName, nodes) {
|
|
330
|
-
if (!nodes || nodes.length === 0) {
|
|
331
|
-
return void 0;
|
|
332
|
-
}
|
|
333
|
-
return Object.freeze(nodes.map((node, index) => {
|
|
334
|
-
const label = `queue connection "${connectionName}" redis cluster node ${index + 1}`;
|
|
335
|
-
const url = normalizeOptionalRedisString(node.url);
|
|
336
|
-
if (typeof url !== "undefined") {
|
|
337
|
-
const database = parseRedisDatabaseFromUrl(url, `${label} url`);
|
|
338
|
-
if (typeof database !== "undefined") {
|
|
339
|
-
throw new Error(`[Holo Queue] ${label} url cannot include a database path in cluster mode.`);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
return Object.freeze({
|
|
343
|
-
...typeof url === "undefined" ? {} : { url },
|
|
344
|
-
host: normalizeOptionalRedisString(node.host) ?? DEFAULT_REDIS_HOST,
|
|
345
|
-
port: parseInteger(node.port, DEFAULT_REDIS_PORT, `${label} port`, {
|
|
346
|
-
minimum: 1
|
|
347
|
-
})
|
|
348
|
-
});
|
|
349
|
-
}));
|
|
350
|
-
}
|
|
351
|
-
function normalizeRedisOptions(connectionName, base, overrides) {
|
|
352
|
-
const hasTargetOverride = typeof overrides?.url !== "undefined" || typeof overrides?.clusters !== "undefined" || typeof overrides?.host !== "undefined" || typeof overrides?.port !== "undefined";
|
|
353
|
-
const url = normalizeOptionalRedisString(overrides?.url) ?? (hasTargetOverride ? void 0 : base?.url);
|
|
354
|
-
const clusters = typeof overrides?.clusters === "undefined" ? hasTargetOverride ? void 0 : base?.clusters : normalizeRedisClusterNodes(connectionName, overrides.clusters);
|
|
355
|
-
const dbFromUrl = parseRedisDatabaseFromUrl(url, `queue connection "${connectionName}" redis url`);
|
|
356
|
-
const db = parseInteger(overrides?.db ?? base?.db ?? dbFromUrl, DEFAULT_REDIS_DB, `queue connection "${connectionName}" redis db`, {
|
|
357
|
-
minimum: 0
|
|
358
|
-
});
|
|
359
|
-
if (typeof clusters !== "undefined" && db !== 0) {
|
|
360
|
-
throw new Error(`[Holo Queue] queue connection "${connectionName}" cannot select redis.db=${db} in cluster mode; Redis Cluster only supports database 0.`);
|
|
361
|
-
}
|
|
362
|
-
return Object.freeze({
|
|
363
|
-
...typeof url === "undefined" ? {} : { url },
|
|
364
|
-
...typeof clusters === "undefined" ? {} : { clusters },
|
|
365
|
-
host: normalizeOptionalRedisString(overrides?.host) ?? base?.host ?? DEFAULT_REDIS_HOST,
|
|
366
|
-
port: parseInteger(overrides?.port ?? base?.port, DEFAULT_REDIS_PORT, `queue connection "${connectionName}" redis port`, {
|
|
367
|
-
minimum: 1
|
|
368
|
-
}),
|
|
369
|
-
password: normalizeOptionalRedisString(overrides?.password) ?? base?.password,
|
|
370
|
-
username: normalizeOptionalRedisString(overrides?.username) ?? base?.username,
|
|
371
|
-
db
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
function normalizeRedisConnection(name, config, redisConfig) {
|
|
375
|
-
const explicitConnectionName = config.connection?.trim();
|
|
376
|
-
const connectionName = explicitConnectionName || redisConfig?.default;
|
|
377
|
-
if (!connectionName && !config.redis) {
|
|
378
|
-
throw new Error(
|
|
379
|
-
`[Holo Queue] Queue Redis connection "${name}" requires a shared Redis config with a default connection or an explicit connection name.`
|
|
380
|
-
);
|
|
381
|
-
}
|
|
382
|
-
if (!redisConfig && !config.redis) {
|
|
383
|
-
throw new Error(
|
|
384
|
-
`[Holo Queue] Queue Redis connection "${name}" references shared Redis connection "${connectionName}" but no shared Redis config was provided.`
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
const resolvedRedisConnection = redisConfig && connectionName ? resolveSharedRedisConnection(redisConfig, connectionName) : void 0;
|
|
388
|
-
return Object.freeze({
|
|
389
|
-
name,
|
|
390
|
-
driver: "redis",
|
|
391
|
-
connection: resolvedRedisConnection?.name ?? connectionName ?? name,
|
|
392
|
-
queue: normalizeQueueName(config.queue),
|
|
393
|
-
retryAfter: parseInteger(config.retryAfter, DEFAULT_QUEUE_RETRY_AFTER, `queue connection "${name}" retryAfter`, {
|
|
394
|
-
minimum: 0
|
|
395
|
-
}),
|
|
396
|
-
blockFor: parseInteger(config.blockFor, DEFAULT_QUEUE_BLOCK_FOR, `queue connection "${name}" blockFor`, {
|
|
397
|
-
minimum: 0
|
|
398
|
-
}),
|
|
399
|
-
redis: normalizeRedisOptions(name, resolvedRedisConnection, config.redis)
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
function normalizeDatabaseConnection(name, config) {
|
|
403
|
-
return Object.freeze({
|
|
404
|
-
name,
|
|
405
|
-
driver: "database",
|
|
406
|
-
queue: normalizeQueueName(config.queue),
|
|
407
|
-
retryAfter: parseInteger(config.retryAfter, DEFAULT_QUEUE_RETRY_AFTER, `queue connection "${name}" retryAfter`, {
|
|
408
|
-
minimum: 0
|
|
409
|
-
}),
|
|
410
|
-
sleep: parseInteger(config.sleep, DEFAULT_QUEUE_SLEEP, `queue connection "${name}" sleep`, {
|
|
411
|
-
minimum: 0
|
|
412
|
-
}),
|
|
413
|
-
connection: config.connection?.trim() || DEFAULT_FAILED_JOBS_CONNECTION,
|
|
414
|
-
table: config.table?.trim() || DEFAULT_DATABASE_QUEUE_TABLE
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
function normalizeConnectionConfig(name, config, redisConfig) {
|
|
418
|
-
switch (config.driver) {
|
|
419
|
-
case "sync":
|
|
420
|
-
return normalizeSyncConnection(name, config);
|
|
421
|
-
case "redis":
|
|
422
|
-
return normalizeRedisConnection(name, config, redisConfig);
|
|
423
|
-
case "database":
|
|
424
|
-
return normalizeDatabaseConnection(name, config);
|
|
425
|
-
default:
|
|
426
|
-
throw new Error(`[Holo Queue] Unsupported queue driver "${String(config.driver)}" on connection "${name}".`);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
function normalizeConnections(connections, redisConfig) {
|
|
430
|
-
if (!connections || Object.keys(connections).length === 0) {
|
|
431
|
-
return DEFAULT_QUEUE_CONFIG.connections;
|
|
432
|
-
}
|
|
433
|
-
const normalizedEntries = Object.entries(connections).map(([name, config]) => {
|
|
434
|
-
const normalizedName = normalizeConnectionName(name, "Queue connection name");
|
|
435
|
-
return [normalizedName, normalizeConnectionConfig(normalizedName, config, redisConfig)];
|
|
436
|
-
});
|
|
437
|
-
return Object.freeze(Object.fromEntries(normalizedEntries));
|
|
438
|
-
}
|
|
439
|
-
function normalizeFailedStore(config) {
|
|
440
|
-
if (config === false) {
|
|
441
|
-
return false;
|
|
442
|
-
}
|
|
443
|
-
const normalized = config ?? DEFAULT_QUEUE_CONFIG.failed;
|
|
444
|
-
if (normalized.driver && normalized.driver !== "database") {
|
|
445
|
-
throw new Error(`[Holo Queue] Unsupported failed job store driver "${normalized.driver}".`);
|
|
446
|
-
}
|
|
447
|
-
return Object.freeze({
|
|
448
|
-
driver: "database",
|
|
449
|
-
connection: normalized.connection?.trim() || DEFAULT_FAILED_JOBS_CONNECTION,
|
|
450
|
-
table: normalized.table?.trim() || DEFAULT_FAILED_JOBS_TABLE
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
function normalizeQueueConfig(config = {}, redisConfig) {
|
|
454
|
-
const connections = normalizeConnections(config.connections, redisConfig);
|
|
455
|
-
const connectionNames = Object.keys(connections);
|
|
456
|
-
const defaultConnection = config.default?.trim() || connectionNames[0];
|
|
457
|
-
if (!connections[defaultConnection]) {
|
|
458
|
-
throw new Error(
|
|
459
|
-
`[Holo Queue] default queue connection "${defaultConnection}" is not configured. Available connections: ${connectionNames.join(", ")}`
|
|
460
|
-
);
|
|
461
|
-
}
|
|
462
|
-
return Object.freeze({
|
|
463
|
-
default: defaultConnection,
|
|
464
|
-
failed: normalizeFailedStore(config.failed),
|
|
465
|
-
connections
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
var holoQueueDefaults = DEFAULT_QUEUE_CONFIG;
|
|
469
|
-
var queueInternals = {
|
|
470
|
-
parseInteger
|
|
471
|
-
};
|
|
472
|
-
|
|
473
246
|
// src/registry.ts
|
|
474
247
|
function toPosixPath(value) {
|
|
475
248
|
return value.replaceAll("\\", "/");
|
|
@@ -544,149 +317,6 @@ var queueRegistryInternals = {
|
|
|
544
317
|
deriveJobNameFromSourcePath
|
|
545
318
|
};
|
|
546
319
|
|
|
547
|
-
// src/drivers/redis.ts
|
|
548
|
-
function isModuleNotFoundError(error) {
|
|
549
|
-
return !!error && typeof error === "object" && "code" in error && error.code === "ERR_MODULE_NOT_FOUND";
|
|
550
|
-
}
|
|
551
|
-
async function loadRedisDriverModule() {
|
|
552
|
-
try {
|
|
553
|
-
const specifier = "@holo-js/queue-redis";
|
|
554
|
-
return await import(
|
|
555
|
-
/* @vite-ignore */
|
|
556
|
-
specifier
|
|
557
|
-
);
|
|
558
|
-
} catch (error) {
|
|
559
|
-
if (isModuleNotFoundError(error)) {
|
|
560
|
-
throw new Error("[@holo-js/queue] Redis queue support requires @holo-js/queue-redis to be installed.", {
|
|
561
|
-
cause: error
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
throw error;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
function normalizeRedisErrorMessage(error) {
|
|
568
|
-
if (error instanceof Error) {
|
|
569
|
-
return error.message;
|
|
570
|
-
}
|
|
571
|
-
return String(error);
|
|
572
|
-
}
|
|
573
|
-
function isRecord(value) {
|
|
574
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
575
|
-
}
|
|
576
|
-
function isQueueEnvelope(value) {
|
|
577
|
-
return isRecord(value) && typeof value.id === "string" && typeof value.name === "string" && typeof value.connection === "string" && typeof value.queue === "string" && "payload" in value && typeof value.attempts === "number" && Number.isInteger(value.attempts) && value.attempts >= 0 && typeof value.maxAttempts === "number" && Number.isInteger(value.maxAttempts) && value.maxAttempts >= 1 && typeof value.createdAt === "number" && Number.isFinite(value.createdAt) && (typeof value.availableAt === "undefined" || typeof value.availableAt === "number" && Number.isFinite(value.availableAt));
|
|
578
|
-
}
|
|
579
|
-
function isRedisSocketConnectionTarget(value) {
|
|
580
|
-
return value.startsWith("unix://") || value.startsWith("/");
|
|
581
|
-
}
|
|
582
|
-
function toRedisSocketPath(value) {
|
|
583
|
-
return value.startsWith("unix://") ? value.slice("unix://".length) : value;
|
|
584
|
-
}
|
|
585
|
-
function resolveBullConnectionOptions(connection) {
|
|
586
|
-
const redisHost = connection.redis.host;
|
|
587
|
-
return {
|
|
588
|
-
...typeof connection.redis.url === "string" ? { url: connection.redis.url } : connection.redis.clusters && connection.redis.clusters.length > 0 ? { clusters: connection.redis.clusters } : typeof redisHost === "string" && isRedisSocketConnectionTarget(redisHost) ? { path: toRedisSocketPath(redisHost) } : {
|
|
589
|
-
host: redisHost,
|
|
590
|
-
port: connection.redis.port
|
|
591
|
-
},
|
|
592
|
-
username: connection.redis.username,
|
|
593
|
-
password: connection.redis.password,
|
|
594
|
-
db: connection.redis.db,
|
|
595
|
-
maxRetriesPerRequest: null
|
|
596
|
-
};
|
|
597
|
-
}
|
|
598
|
-
var RedisQueueDriverError = class extends Error {
|
|
599
|
-
constructor(connectionName, action, cause) {
|
|
600
|
-
super(
|
|
601
|
-
`[Holo Queue] Redis queue connection "${connectionName}" failed to ${action}: ${normalizeRedisErrorMessage(cause)}`,
|
|
602
|
-
{ cause }
|
|
603
|
-
);
|
|
604
|
-
this.name = "RedisQueueDriverError";
|
|
605
|
-
}
|
|
606
|
-
};
|
|
607
|
-
function wrapRedisError(connectionName, action, error) {
|
|
608
|
-
if (error instanceof RedisQueueDriverError) {
|
|
609
|
-
return error;
|
|
610
|
-
}
|
|
611
|
-
return new RedisQueueDriverError(connectionName, action, error);
|
|
612
|
-
}
|
|
613
|
-
function resolveAttempts(job) {
|
|
614
|
-
const attemptsStarted = typeof job.attemptsStarted === "number" && Number.isInteger(job.attemptsStarted) ? job.attemptsStarted : 0;
|
|
615
|
-
const attemptsMade = typeof job.attemptsMade === "number" && Number.isInteger(job.attemptsMade) ? job.attemptsMade : 0;
|
|
616
|
-
return Math.max(
|
|
617
|
-
attemptsStarted > 0 ? attemptsStarted - 1 : 0,
|
|
618
|
-
attemptsMade,
|
|
619
|
-
0
|
|
620
|
-
);
|
|
621
|
-
}
|
|
622
|
-
var RedisQueueDriver = class {
|
|
623
|
-
constructor(connection, context) {
|
|
624
|
-
this.connection = connection;
|
|
625
|
-
this.context = context;
|
|
626
|
-
this.name = connection.name;
|
|
627
|
-
}
|
|
628
|
-
name;
|
|
629
|
-
driver = "redis";
|
|
630
|
-
mode = "async";
|
|
631
|
-
driverInstance;
|
|
632
|
-
pending;
|
|
633
|
-
async resolveDriver() {
|
|
634
|
-
if (this.driverInstance) {
|
|
635
|
-
return this.driverInstance;
|
|
636
|
-
}
|
|
637
|
-
this.pending ??= loadRedisDriverModule().then((module) => {
|
|
638
|
-
const driver = module.redisQueueDriverFactory.create(this.connection, this.context);
|
|
639
|
-
if (driver.mode !== "async") {
|
|
640
|
-
throw new Error("[Holo Queue] Redis queue driver must be async.");
|
|
641
|
-
}
|
|
642
|
-
this.driverInstance = driver;
|
|
643
|
-
return driver;
|
|
644
|
-
}).finally(() => {
|
|
645
|
-
this.pending = void 0;
|
|
646
|
-
});
|
|
647
|
-
return this.pending;
|
|
648
|
-
}
|
|
649
|
-
async dispatch(job) {
|
|
650
|
-
return (await this.resolveDriver()).dispatch(job);
|
|
651
|
-
}
|
|
652
|
-
async reserve(input) {
|
|
653
|
-
return (await this.resolveDriver()).reserve(input);
|
|
654
|
-
}
|
|
655
|
-
async acknowledge(job) {
|
|
656
|
-
await (await this.resolveDriver()).acknowledge(job);
|
|
657
|
-
}
|
|
658
|
-
async release(job, options) {
|
|
659
|
-
await (await this.resolveDriver()).release(job, options);
|
|
660
|
-
}
|
|
661
|
-
async delete(job) {
|
|
662
|
-
await (await this.resolveDriver()).delete(job);
|
|
663
|
-
}
|
|
664
|
-
async clear(input) {
|
|
665
|
-
return (await this.resolveDriver()).clear(input);
|
|
666
|
-
}
|
|
667
|
-
async close() {
|
|
668
|
-
if (!this.driverInstance && !this.pending) {
|
|
669
|
-
return;
|
|
670
|
-
}
|
|
671
|
-
await (await this.resolveDriver()).close();
|
|
672
|
-
}
|
|
673
|
-
};
|
|
674
|
-
var redisQueueDriverFactory = {
|
|
675
|
-
driver: "redis",
|
|
676
|
-
create(connection, context) {
|
|
677
|
-
return new RedisQueueDriver(connection, context);
|
|
678
|
-
}
|
|
679
|
-
};
|
|
680
|
-
var redisQueueDriverInternals = {
|
|
681
|
-
isQueueEnvelope,
|
|
682
|
-
loadRedisDriverModule,
|
|
683
|
-
normalizeRedisErrorMessage,
|
|
684
|
-
resolveAttempts,
|
|
685
|
-
resolveBullConnectionOptions,
|
|
686
|
-
toRedisSocketPath,
|
|
687
|
-
wrapRedisError
|
|
688
|
-
};
|
|
689
|
-
|
|
690
320
|
// src/drivers/sync.ts
|
|
691
321
|
var SyncQueueDriver = class {
|
|
692
322
|
name;
|
|
@@ -723,6 +353,57 @@ var syncQueueDriverInternals = {
|
|
|
723
353
|
|
|
724
354
|
// src/runtime.ts
|
|
725
355
|
import { randomUUID } from "crypto";
|
|
356
|
+
|
|
357
|
+
// src/plugins.ts
|
|
358
|
+
import { resolve } from "path";
|
|
359
|
+
import {
|
|
360
|
+
loadHoloPluginContributionModules,
|
|
361
|
+
loadHoloPluginDefinitions
|
|
362
|
+
} from "@holo-js/kernel";
|
|
363
|
+
var loadedFactoriesByProjectRoot = /* @__PURE__ */ new Map();
|
|
364
|
+
var failedLoadsByProjectRoot = /* @__PURE__ */ new Map();
|
|
365
|
+
function isRecord(value) {
|
|
366
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
367
|
+
}
|
|
368
|
+
function resolveQueueDriverFactory(moduleValue, packageName, driverName) {
|
|
369
|
+
const candidate = isRecord(moduleValue) && typeof moduleValue.default !== "undefined" ? moduleValue.default : isRecord(moduleValue) && typeof moduleValue.factory !== "undefined" ? moduleValue.factory : moduleValue;
|
|
370
|
+
if (!isRecord(candidate) || candidate.driver !== driverName || typeof candidate.create !== "function") {
|
|
371
|
+
throw new Error(`[@holo-js/queue] Plugin ${packageName} queue driver "${driverName}" must export a matching QueueDriverFactory.`);
|
|
372
|
+
}
|
|
373
|
+
return candidate;
|
|
374
|
+
}
|
|
375
|
+
async function loadQueuePluginDriverFactories(projectRoot = process.cwd(), pluginNames = []) {
|
|
376
|
+
const root = resolve(projectRoot);
|
|
377
|
+
const cacheKey = `${root}\0${[...pluginNames].sort().join("\0")}`;
|
|
378
|
+
const loadedFactories = loadedFactoriesByProjectRoot.get(cacheKey);
|
|
379
|
+
if (loadedFactories) {
|
|
380
|
+
return loadedFactories;
|
|
381
|
+
}
|
|
382
|
+
if (failedLoadsByProjectRoot.has(cacheKey)) {
|
|
383
|
+
throw failedLoadsByProjectRoot.get(cacheKey);
|
|
384
|
+
}
|
|
385
|
+
const plugins = await loadHoloPluginDefinitions(root, pluginNames);
|
|
386
|
+
const contributions = await loadHoloPluginContributionModules(root, plugins, "queue", "drivers");
|
|
387
|
+
let factories;
|
|
388
|
+
try {
|
|
389
|
+
factories = Object.freeze(contributions.map((contribution) => resolveQueueDriverFactory(
|
|
390
|
+
contribution.module,
|
|
391
|
+
contribution.plugin.packageName,
|
|
392
|
+
contribution.name
|
|
393
|
+
)));
|
|
394
|
+
} catch (error) {
|
|
395
|
+
failedLoadsByProjectRoot.set(cacheKey, error);
|
|
396
|
+
throw error;
|
|
397
|
+
}
|
|
398
|
+
loadedFactoriesByProjectRoot.set(cacheKey, factories);
|
|
399
|
+
return factories;
|
|
400
|
+
}
|
|
401
|
+
function resetQueuePluginDriverFactories() {
|
|
402
|
+
loadedFactoriesByProjectRoot.clear();
|
|
403
|
+
failedLoadsByProjectRoot.clear();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/runtime.ts
|
|
726
407
|
var INLINE_SYNC_DRIVER_KEY = "__inline_sync__";
|
|
727
408
|
var QueueReleaseUnsupportedError = class extends Error {
|
|
728
409
|
constructor() {
|
|
@@ -775,14 +456,14 @@ function assertQueueJsonValue(value, path, state) {
|
|
|
775
456
|
function validateQueuePayload(payload) {
|
|
776
457
|
assertQueueJsonValue(payload, "payload", { seen: /* @__PURE__ */ new Set() });
|
|
777
458
|
}
|
|
778
|
-
function
|
|
459
|
+
function normalizeConnectionName(name) {
|
|
779
460
|
const normalized = name.trim();
|
|
780
461
|
if (!normalized) {
|
|
781
462
|
throw new Error("[Holo Queue] Queue connection names must be non-empty strings.");
|
|
782
463
|
}
|
|
783
464
|
return normalized;
|
|
784
465
|
}
|
|
785
|
-
function
|
|
466
|
+
function normalizeQueueName(name) {
|
|
786
467
|
const normalized = name.trim();
|
|
787
468
|
if (!normalized) {
|
|
788
469
|
throw new Error("[Holo Queue] Queue names must be non-empty strings.");
|
|
@@ -824,7 +505,6 @@ function normalizeDelay(delay) {
|
|
|
824
505
|
}
|
|
825
506
|
function createDefaultDriverFactories() {
|
|
826
507
|
return /* @__PURE__ */ new Map([
|
|
827
|
-
[redisQueueDriverFactory.driver, redisQueueDriverFactory],
|
|
828
508
|
[syncQueueDriverFactory.driver, syncQueueDriverFactory]
|
|
829
509
|
]);
|
|
830
510
|
}
|
|
@@ -868,7 +548,7 @@ function requireRegisteredQueueJob(jobName) {
|
|
|
868
548
|
return registered;
|
|
869
549
|
}
|
|
870
550
|
function resolveConnectionConfig(config, requestedConnection) {
|
|
871
|
-
const connectionName = requestedConnection ?
|
|
551
|
+
const connectionName = requestedConnection ? normalizeConnectionName(requestedConnection) : config.default;
|
|
872
552
|
const connection = config.connections[connectionName];
|
|
873
553
|
if (!connection) {
|
|
874
554
|
throw new Error(
|
|
@@ -975,6 +655,29 @@ function resolveDriverFactory(state, connection) {
|
|
|
975
655
|
}
|
|
976
656
|
return factory;
|
|
977
657
|
}
|
|
658
|
+
async function clearCachedDriversForFactoryNames(state, driverNames) {
|
|
659
|
+
const staleDrivers = [];
|
|
660
|
+
for (const [connectionName, driver] of state.drivers.entries()) {
|
|
661
|
+
const connection = state.config.connections[connectionName];
|
|
662
|
+
const driverName = connection?.driver ?? driver.driver;
|
|
663
|
+
if (driverNames.has(driverName)) {
|
|
664
|
+
staleDrivers.push(driver);
|
|
665
|
+
state.drivers.delete(connectionName);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
await closeQueueDrivers(staleDrivers);
|
|
669
|
+
}
|
|
670
|
+
async function loadQueuePluginDrivers(projectRoot = process.cwd(), pluginNames = []) {
|
|
671
|
+
const state = getQueueRuntimeState();
|
|
672
|
+
const loadedDriverNames = /* @__PURE__ */ new Set();
|
|
673
|
+
for (const factory of await loadQueuePluginDriverFactories(projectRoot, pluginNames)) {
|
|
674
|
+
state.driverFactories.set(factory.driver, factory);
|
|
675
|
+
loadedDriverNames.add(factory.driver);
|
|
676
|
+
}
|
|
677
|
+
if (loadedDriverNames.size > 0) {
|
|
678
|
+
await clearCachedDriversForFactoryNames(state, loadedDriverNames);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
978
681
|
function resolveConnectionDriver(connectionName) {
|
|
979
682
|
const state = getQueueRuntimeState();
|
|
980
683
|
const connection = resolveConnectionConfig(state.config, connectionName);
|
|
@@ -1010,7 +713,7 @@ function createJobEnvelope(jobName, payload, options) {
|
|
|
1010
713
|
runtime.config,
|
|
1011
714
|
options.connection ?? registered.definition.connection
|
|
1012
715
|
);
|
|
1013
|
-
const queue =
|
|
716
|
+
const queue = normalizeQueueName(options.queue ?? registered.definition.queue ?? connection.queue ?? DEFAULT_QUEUE_NAME);
|
|
1014
717
|
return Object.freeze({
|
|
1015
718
|
id: randomUUID(),
|
|
1016
719
|
name: registered.name,
|
|
@@ -1054,13 +757,13 @@ var PendingQueueDispatch = class _PendingQueueDispatch {
|
|
|
1054
757
|
onConnection(name) {
|
|
1055
758
|
return new _PendingQueueDispatch(this.jobName, this.payload, {
|
|
1056
759
|
...this.options,
|
|
1057
|
-
connection:
|
|
760
|
+
connection: normalizeConnectionName(name)
|
|
1058
761
|
}, this.completedHooks, this.failedHooks);
|
|
1059
762
|
}
|
|
1060
763
|
onQueue(name) {
|
|
1061
764
|
return new _PendingQueueDispatch(this.jobName, this.payload, {
|
|
1062
765
|
...this.options,
|
|
1063
|
-
queue:
|
|
766
|
+
queue: normalizeQueueName(name)
|
|
1064
767
|
}, this.completedHooks, this.failedHooks);
|
|
1065
768
|
}
|
|
1066
769
|
delay(value) {
|
|
@@ -1130,7 +833,7 @@ var PendingQueueDispatch = class _PendingQueueDispatch {
|
|
|
1130
833
|
}
|
|
1131
834
|
};
|
|
1132
835
|
function createQueueConnection(name) {
|
|
1133
|
-
const resolvedName = name ?
|
|
836
|
+
const resolvedName = name ? normalizeConnectionName(name) : getQueueRuntimeState().config.default;
|
|
1134
837
|
const dispatchViaConnection = ((jobName, payload, options = {}) => {
|
|
1135
838
|
return new PendingQueueDispatch(jobName, payload, {
|
|
1136
839
|
...options,
|
|
@@ -1182,6 +885,7 @@ function resetQueueRuntime() {
|
|
|
1182
885
|
const state = getQueueRuntimeState();
|
|
1183
886
|
void closeQueueDrivers(state.drivers.values());
|
|
1184
887
|
resetQueueRuntimeState(state);
|
|
888
|
+
resetQueuePluginDriverFactories();
|
|
1185
889
|
}
|
|
1186
890
|
function getQueueRuntime() {
|
|
1187
891
|
return createQueueRuntimeBinding(getQueueRuntimeState());
|
|
@@ -1232,9 +936,9 @@ var queueRuntimeInternals = {
|
|
|
1232
936
|
executeRegisteredQueueJobFailedHook,
|
|
1233
937
|
getQueueRuntimeState,
|
|
1234
938
|
isPlainObject,
|
|
1235
|
-
normalizeConnectionName
|
|
939
|
+
normalizeConnectionName,
|
|
1236
940
|
normalizeDelay,
|
|
1237
|
-
normalizeQueueName
|
|
941
|
+
normalizeQueueName,
|
|
1238
942
|
resolveConnectionConfig,
|
|
1239
943
|
resolveConnectionDriver,
|
|
1240
944
|
resolveDriverFactory,
|
|
@@ -1368,7 +1072,7 @@ var QueueWorkerTimeoutError = class extends Error {
|
|
|
1368
1072
|
}
|
|
1369
1073
|
};
|
|
1370
1074
|
function sleep(milliseconds) {
|
|
1371
|
-
return new Promise((
|
|
1075
|
+
return new Promise((resolve2) => setTimeout(resolve2, milliseconds));
|
|
1372
1076
|
}
|
|
1373
1077
|
function requireAsyncDriver(driver, connectionName) {
|
|
1374
1078
|
if (driver.mode !== "async") {
|
|
@@ -1759,8 +1463,6 @@ export {
|
|
|
1759
1463
|
QueueReleaseUnsupportedError,
|
|
1760
1464
|
QueueWorkerTimeoutError,
|
|
1761
1465
|
QueueWorkerUnsupportedDriverError,
|
|
1762
|
-
RedisQueueDriver,
|
|
1763
|
-
RedisQueueDriverError,
|
|
1764
1466
|
clearQueueConnection,
|
|
1765
1467
|
configureQueueRuntime,
|
|
1766
1468
|
defineJob,
|
|
@@ -1775,6 +1477,8 @@ export {
|
|
|
1775
1477
|
isQueueJobDefinition,
|
|
1776
1478
|
listFailedQueueJobs,
|
|
1777
1479
|
listRegisteredQueueJobs,
|
|
1480
|
+
loadQueuePluginDriverFactories,
|
|
1481
|
+
loadQueuePluginDrivers,
|
|
1778
1482
|
normalizeQueueConfig,
|
|
1779
1483
|
normalizeQueueJobDefinition,
|
|
1780
1484
|
persistFailedQueueJob,
|
|
@@ -1784,10 +1488,9 @@ export {
|
|
|
1784
1488
|
queueRegistryInternals,
|
|
1785
1489
|
queueRuntimeInternals,
|
|
1786
1490
|
queueWorkerInternals,
|
|
1787
|
-
redisQueueDriverFactory,
|
|
1788
|
-
redisQueueDriverInternals,
|
|
1789
1491
|
registerQueueJob,
|
|
1790
1492
|
registerQueueJobs,
|
|
1493
|
+
resetQueuePluginDriverFactories,
|
|
1791
1494
|
resetQueueRegistry,
|
|
1792
1495
|
resetQueueRuntime,
|
|
1793
1496
|
retryFailedQueueJobs,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holo-js/queue",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Holo-JS Framework - queue contracts and config helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
10
|
"import": "./dist/index.mjs",
|
|
11
11
|
"default": "./dist/index.mjs"
|
|
12
|
+
},
|
|
13
|
+
"./config": {
|
|
14
|
+
"types": "./dist/config.d.ts",
|
|
15
|
+
"import": "./dist/config.mjs",
|
|
16
|
+
"default": "./dist/config.mjs"
|
|
12
17
|
}
|
|
13
18
|
},
|
|
14
19
|
"main": "./dist/index.mjs",
|
|
@@ -22,16 +27,11 @@
|
|
|
22
27
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
23
28
|
"test": "vitest --run"
|
|
24
29
|
},
|
|
25
|
-
"
|
|
26
|
-
"@holo-js/
|
|
27
|
-
|
|
28
|
-
"peerDependenciesMeta": {
|
|
29
|
-
"@holo-js/queue-redis": {
|
|
30
|
-
"optional": true
|
|
31
|
-
}
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@holo-js/config": "^0.3.1",
|
|
32
|
+
"@holo-js/kernel": "^0.3.1"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
|
-
"@holo-js/queue-redis": "^0.2.6",
|
|
35
35
|
"@types/node": "^22.10.2",
|
|
36
36
|
"tsup": "^8.3.5",
|
|
37
37
|
"typescript": "^5.7.2",
|