@objectstack/service-queue 17.0.0-rc.0 → 17.0.0-rc.2
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/CHANGELOG.md +1815 -0
- package/dist/index.cjs +14 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +14 -2
- package/dist/index.js.map +1 -1
- package/package.json +8 -6
package/dist/index.cjs
CHANGED
|
@@ -195,7 +195,7 @@ var DbQueueAdapter = class {
|
|
|
195
195
|
});
|
|
196
196
|
for (const row of rows ?? []) {
|
|
197
197
|
try {
|
|
198
|
-
await this.engine.delete(QUEUE_TABLE, { id: row.id, context: SYSTEM_CTX });
|
|
198
|
+
await this.engine.delete(QUEUE_TABLE, { where: { id: row.id }, context: SYSTEM_CTX });
|
|
199
199
|
} catch (err) {
|
|
200
200
|
this.logger?.warn?.("DbQueueAdapter: purge delete failed", err);
|
|
201
201
|
}
|
|
@@ -237,7 +237,7 @@ var DbQueueAdapter = class {
|
|
|
237
237
|
if (row.status !== "dlq" && row.status !== "failed") {
|
|
238
238
|
throw new Error(`INVALID_STATE: cannot purge message in status=${row.status}`);
|
|
239
239
|
}
|
|
240
|
-
await this.engine.delete(QUEUE_TABLE, { id: messageId, context: SYSTEM_CTX });
|
|
240
|
+
await this.engine.delete(QUEUE_TABLE, { where: { id: messageId }, context: SYSTEM_CTX });
|
|
241
241
|
}
|
|
242
242
|
// ── Worker lifecycle ─────────────────────────────────────────────
|
|
243
243
|
start() {
|
|
@@ -430,6 +430,18 @@ var DbQueueAdapter = class {
|
|
|
430
430
|
var QueueServicePlugin = class {
|
|
431
431
|
constructor(options = {}) {
|
|
432
432
|
this.name = "com.objectstack.service.queue";
|
|
433
|
+
/**
|
|
434
|
+
* Services init() registers on every path (ADR-0116, #4131) — lets the
|
|
435
|
+
* kernel name this plugin when a consumer requires one before it inits.
|
|
436
|
+
*/
|
|
437
|
+
this.providesServices = ["queue"];
|
|
438
|
+
/**
|
|
439
|
+
* init() registers sys_job_queue through the `manifest` service
|
|
440
|
+
* ObjectQLPlugin provides — order-if-present so the registration is
|
|
441
|
+
* deterministic (ADR-0116, #4471). Soft, not hard: without an engine the
|
|
442
|
+
* plugin degrades on purpose (in-memory queue adapter).
|
|
443
|
+
*/
|
|
444
|
+
this.optionalDependencies = ["com.objectstack.engine.objectql"];
|
|
433
445
|
this.version = "1.1.0";
|
|
434
446
|
this.type = "standard";
|
|
435
447
|
this.options = { adapter: "auto", ...options };
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/queue-service-plugin.ts","../src/memory-queue-adapter.ts","../src/common.ts","../src/db-queue-adapter.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nexport { QueueServicePlugin } from './queue-service-plugin.js';\nexport type { QueueServicePluginOptions } from './queue-service-plugin.js';\nexport { MemoryQueueAdapter } from './memory-queue-adapter.js';\nexport type { MemoryQueueAdapterOptions } from './memory-queue-adapter.js';\nexport { DbQueueAdapter } from './db-queue-adapter.js';\nexport type { DbQueueAdapterOptions } from './db-queue-adapter.js';\nexport type { JobEngine, JobClock, JobLogger } from './common.js';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport { SysJobQueue } from '@objectstack/platform-objects/audit';\nimport { MemoryQueueAdapter } from './memory-queue-adapter.js';\nimport type { MemoryQueueAdapterOptions } from './memory-queue-adapter.js';\nimport { DbQueueAdapter } from './db-queue-adapter.js';\nimport type { DbQueueAdapterOptions } from './db-queue-adapter.js';\n\n/**\n * Configuration options for the QueueServicePlugin.\n */\nexport interface QueueServicePluginOptions {\n /**\n * Queue adapter type.\n * - 'auto' (default): use DbQueueAdapter when objectql engine available, else MemoryQueueAdapter\n * - 'db': require objectql; persists messages, retries, and DLQ to sys_job_queue\n * - 'memory': in-process MemoryQueueAdapter (non-durable, dev/test)\n */\n adapter?: 'auto' | 'db' | 'memory';\n /** Options for the memory queue adapter */\n memory?: MemoryQueueAdapterOptions;\n /** Options for the DB adapter (polling, batch, lease, idempotency window…) */\n db?: DbQueueAdapterOptions;\n}\n\n/**\n * QueueServicePlugin — Production IQueueService implementation.\n *\n * Default: registers MemoryQueueAdapter synchronously so producers can\n * publish during plugin init; upgrades to DbQueueAdapter on `kernel:ready`\n * when an ObjectQL engine is available. Subscribers registered against\n * the (now-replaced) memory queue must re-subscribe after upgrade — for\n * that reason most plugins register subscribers inside their own\n * `kernel:ready` hook, which fires after this one.\n */\nexport class QueueServicePlugin implements Plugin {\n name = 'com.objectstack.service.queue';\n version = '1.1.0';\n type = 'standard';\n\n private readonly options: QueueServicePluginOptions;\n private dbAdapter?: DbQueueAdapter;\n\n constructor(options: QueueServicePluginOptions = {}) {\n this.options = { adapter: 'auto', ...options };\n }\n\n async init(ctx: PluginContext): Promise<void> {\n // Register sys_job_queue (also serves as DLQ view) so Studio can list/replay.\n try {\n ctx.getService<{ register(m: any): void }>('manifest').register({\n id: 'com.objectstack.service.queue',\n name: 'Queue Service',\n version: '1.1.0',\n type: 'plugin',\n scope: 'system',\n defaultDatasource: 'cloud',\n namespace: 'sys',\n objects: [SysJobQueue],\n });\n } catch (err) {\n ctx.logger.warn('QueueServicePlugin: manifest service unavailable; sys_job_queue not registered', err as any);\n }\n\n const choice = this.options.adapter ?? 'auto';\n\n if (choice === 'memory') {\n const q = new MemoryQueueAdapter(this.options.memory);\n ctx.registerService('queue', q);\n ctx.logger.info('QueueServicePlugin: registered MemoryQueueAdapter');\n return;\n }\n\n // auto / db — register memory placeholder, upgrade on kernel:ready\n ctx.registerService('queue', new MemoryQueueAdapter(this.options.memory));\n\n ctx.hook('kernel:ready', async () => {\n let engine: any = null;\n try { engine = ctx.getService<any>('objectql'); }\n catch { try { engine = ctx.getService<any>('data'); } catch { /* ignore */ } }\n\n if (!engine) {\n if (choice === 'db') {\n ctx.logger.warn('QueueServicePlugin: db adapter requested but no ObjectQL engine — staying on MemoryQueueAdapter');\n } else {\n ctx.logger.info('QueueServicePlugin: no ObjectQL engine — staying on MemoryQueueAdapter');\n }\n return;\n }\n\n this.dbAdapter = new DbQueueAdapter({\n engine,\n logger: ctx.logger,\n options: this.options.db,\n });\n\n try {\n (ctx as any).replaceService?.('queue', this.dbAdapter);\n this.dbAdapter.start();\n ctx.logger.info('QueueServicePlugin: upgraded to DbQueueAdapter (sys_job_queue persistence)');\n } catch (err) {\n ctx.logger.warn('QueueServicePlugin: replaceService failed; staying on MemoryQueueAdapter', err as any);\n }\n });\n }\n\n async destroy(): Promise<void> {\n await this.dbAdapter?.stop();\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IQueueService, QueuePublishOptions, QueueMessage, QueueHandler } from '@objectstack/spec/contracts';\n\n/**\n * Configuration options for MemoryQueueAdapter.\n */\nexport interface MemoryQueueAdapterOptions {\n /** Maximum number of messages retained per queue (0 = unlimited) */\n maxQueueSize?: number;\n}\n\n/**\n * In-memory queue adapter implementing IQueueService.\n *\n * Provides synchronous in-process pub/sub delivery.\n * Suitable for single-process environments, development, and testing.\n */\nexport class MemoryQueueAdapter implements IQueueService {\n private readonly handlers = new Map<string, QueueHandler[]>();\n private readonly deadLetters: QueueMessage[] = [];\n private msgCounter = 0;\n private readonly maxQueueSize: number;\n\n constructor(options: MemoryQueueAdapterOptions = {}) {\n this.maxQueueSize = options.maxQueueSize ?? 0;\n }\n\n async publish<T = unknown>(queue: string, data: T, options?: QueuePublishOptions): Promise<string> {\n const id = `msg-${++this.msgCounter}`;\n const msg: QueueMessage<T> = {\n id,\n data,\n attempts: 0,\n timestamp: Date.now(),\n };\n\n const fns = this.handlers.get(queue) ?? [];\n if (fns.length === 0) {\n // No subscribers — retain as dead letter if within limits\n if (this.maxQueueSize === 0 || this.deadLetters.length < this.maxQueueSize) {\n this.deadLetters.push(msg);\n }\n return id;\n }\n\n const maxRetries = options?.retries ?? 0;\n for (const handler of fns) {\n let attempt = 0;\n let success = false;\n while (!success && attempt <= maxRetries) {\n try {\n msg.attempts = attempt + 1;\n await handler(msg as QueueMessage);\n success = true;\n } catch {\n attempt++;\n }\n }\n }\n\n return id;\n }\n\n async subscribe<T = unknown>(queue: string, handler: QueueHandler<T>): Promise<void> {\n const existing = this.handlers.get(queue) ?? [];\n this.handlers.set(queue, [...existing, handler as QueueHandler]);\n }\n\n async unsubscribe(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n\n async getQueueSize(_queue: string): Promise<number> {\n // In-memory: no persistent queue depth tracking\n return 0;\n }\n\n async purge(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Narrow ObjectQL engine surface used by job/queue adapters.\n * Keeps the adapter testable without booting a real kernel.\n *\n * IMPORTANT: matches the canonical engine API:\n * - find: `where:` (NOT `filter:`)\n * - update: `(table, {id, ...patch}, opts)`\n */\nexport interface JobEngine {\n find(object: string, options?: any): Promise<any[]>;\n insert(object: string, data: any, options?: any): Promise<any>;\n update(object: string, idOrData: any, dataOrOptions?: any, options?: any): Promise<any>;\n delete(object: string, options?: any): Promise<any>;\n}\n\n/** Stamped only in tests to make `now` deterministic. */\nexport interface JobClock { now(): Date }\n\nexport interface JobLogger {\n info(msg: string, meta?: unknown): void;\n warn(msg: string, meta?: unknown): void;\n error?(msg: string, meta?: unknown): void;\n}\n\nexport const SYSTEM_CTX = { isSystem: true, positions: [], permissions: [] } as const;\n\nexport function uid(prefix: string): string {\n const g: any = globalThis as any;\n if (g.crypto?.randomUUID) return `${prefix}_${g.crypto.randomUUID()}`;\n return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n}\n\nexport function nowIso(clock?: JobClock): string {\n return (clock?.now() ?? new Date()).toISOString();\n}\n\nexport function parseJson<T = unknown>(raw: unknown, fallback?: T): T | undefined {\n if (raw == null) return fallback;\n if (typeof raw === 'string') {\n try { return JSON.parse(raw) as T; } catch { return fallback; }\n }\n if (typeof raw === 'object') return raw as T;\n return fallback;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type {\n IQueueService,\n QueuePublishOptions,\n QueueMessage,\n QueueMessageRecord,\n QueueHandler,\n} from '@objectstack/spec/contracts';\nimport {\n SYSTEM_CTX,\n uid,\n nowIso,\n parseJson,\n type JobEngine,\n type JobClock,\n type JobLogger,\n} from './common.js';\n\nconst QUEUE_TABLE = 'sys_job_queue';\n\nexport interface DbQueueAdapterOptions {\n /** Polling interval for the worker loop (ms, default 1000) */\n pollIntervalMs?: number;\n /** Max messages claimed per poll tick (default 10) */\n batchSize?: number;\n /** Lease duration before another worker may reclaim (ms, default 30000) */\n leaseMs?: number;\n /** Idempotency window — how long the same key blocks re-publish (ms, default 24h) */\n idempotencyWindowMs?: number;\n /** Default maxAttempts when publish doesn't specify (default 3) */\n defaultMaxAttempts?: number;\n /** Unique identifier for this worker (default: random) */\n workerId?: string;\n /** Whether to auto-start the polling worker (default true) */\n autoStart?: boolean;\n}\n\ninterface RegisteredHandler {\n queue: string;\n fn: QueueHandler;\n}\n\n/**\n * DbQueueAdapter — durable, polling, DB-backed IQueueService.\n *\n * Persists every message to `sys_job_queue`. A polling worker leases\n * pending messages (CAS update status pending→running with a lease),\n * invokes registered subscribers, and retries with backoff on failure.\n * Messages that exceed `max_attempts` land in `status='dlq'`.\n *\n * Idempotency: publish suppresses duplicates within a configurable\n * window when `(queue, idempotencyKey)` is non-null.\n *\n * Designed for SQLite and Postgres alike — uses CAS via WHERE-clauses,\n * not row-level locking.\n */\nexport class DbQueueAdapter implements IQueueService {\n private readonly engine: JobEngine;\n private readonly logger?: JobLogger;\n private readonly clock?: JobClock;\n private readonly opts: Required<Omit<DbQueueAdapterOptions, 'workerId'>> & { workerId: string };\n\n private readonly handlers = new Map<string, RegisteredHandler[]>();\n private timer?: ReturnType<typeof setInterval>;\n private running = false;\n\n constructor(args: {\n engine: JobEngine;\n logger?: JobLogger;\n clock?: JobClock;\n options?: DbQueueAdapterOptions;\n }) {\n this.engine = args.engine;\n this.logger = args.logger;\n this.clock = args.clock;\n const o = args.options ?? {};\n this.opts = {\n pollIntervalMs: o.pollIntervalMs ?? 1000,\n batchSize: o.batchSize ?? 10,\n leaseMs: o.leaseMs ?? 30_000,\n idempotencyWindowMs: o.idempotencyWindowMs ?? 24 * 60 * 60 * 1000,\n defaultMaxAttempts: o.defaultMaxAttempts ?? 3,\n autoStart: o.autoStart ?? true,\n workerId: o.workerId ?? uid('worker'),\n };\n }\n\n // ── IQueueService ────────────────────────────────────────────────\n\n async publish<T = unknown>(\n queue: string,\n data: T,\n options?: QueuePublishOptions,\n ): Promise<string> {\n const opts = options ?? {};\n const now = this.now();\n\n // Idempotency check\n if (opts.idempotencyKey) {\n const windowStart = new Date(now.getTime() - this.opts.idempotencyWindowMs).toISOString();\n const existing = await this.engine.find(QUEUE_TABLE, {\n where: {\n queue,\n idempotency_key: opts.idempotencyKey,\n // Only block if not yet terminal — completed/dlq dedup is by window via created_at\n },\n limit: 5,\n context: SYSTEM_CTX,\n });\n const blocking = (existing ?? []).find((row: any) => {\n if (row.status === 'pending' || row.status === 'running') return true;\n return String(row.created_at ?? '') >= windowStart;\n });\n if (blocking) return String(blocking.id);\n }\n\n const id = uid('msg');\n const scheduledFor = opts.scheduledFor\n ? new Date(opts.scheduledFor).toISOString()\n : opts.delay\n ? new Date(now.getTime() + opts.delay).toISOString()\n : now.toISOString();\n\n const maxAttempts = opts.maxAttempts\n ?? (opts.retries != null ? opts.retries + 1 : this.opts.defaultMaxAttempts);\n const backoff = opts.backoff ?? { type: 'exponential' as const, delayMs: 1000 };\n\n await this.engine.insert(QUEUE_TABLE, {\n id,\n queue,\n idempotency_key: opts.idempotencyKey ?? null,\n payload_json: JSON.stringify(data ?? null),\n metadata_json: opts.metadata ? JSON.stringify(opts.metadata) : null,\n status: 'pending',\n priority: opts.priority ?? 100,\n attempts: 0,\n max_attempts: maxAttempts,\n backoff_type: backoff.type,\n backoff_delay_ms: backoff.delayMs,\n backoff_max_delay_ms: backoff.maxDelayMs ?? null,\n scheduled_for: scheduledFor,\n created_at: now.toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n\n return id;\n }\n\n async subscribe<T = unknown>(queue: string, handler: QueueHandler<T>): Promise<void> {\n const existing = this.handlers.get(queue) ?? [];\n existing.push({ queue, fn: handler as QueueHandler });\n this.handlers.set(queue, existing);\n if (this.opts.autoStart) this.start();\n }\n\n async unsubscribe(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n\n async getQueueSize(queue: string): Promise<number> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: 10_000,\n context: SYSTEM_CTX,\n });\n return rows?.length ?? 0;\n }\n\n async purge(queue: string): Promise<void> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: 10_000,\n context: SYSTEM_CTX,\n });\n for (const row of rows ?? []) {\n try { await this.engine.delete(QUEUE_TABLE, { id: row.id, context: SYSTEM_CTX }); }\n catch (err) { this.logger?.warn?.('DbQueueAdapter: purge delete failed', err as any); }\n }\n }\n\n async listFailed(\n queue?: string,\n options?: { limit?: number; offset?: number },\n ): Promise<QueueMessageRecord[]> {\n const where: any = { status: 'dlq' };\n if (queue) where.queue = queue;\n const rows = await this.engine.find(QUEUE_TABLE, {\n where,\n limit: options?.limit ?? 100,\n offset: options?.offset,\n orderBy: [{ field: 'created_at', order: 'desc' }],\n context: SYSTEM_CTX,\n });\n return (rows ?? []).map((r: any) => this.rowToRecord(r));\n }\n\n async replay(messageId: string): Promise<void> {\n const row = await this.loadById(messageId);\n if (!row) throw new Error(`MESSAGE_NOT_FOUND: ${messageId}`);\n if (row.status !== 'dlq' && row.status !== 'failed') {\n throw new Error(`INVALID_STATE: cannot replay message in status=${row.status}`);\n }\n const now = this.now();\n await this.engine.update(QUEUE_TABLE, {\n id: messageId,\n status: 'pending',\n attempts: 0,\n last_error: null,\n locked_by: null,\n locked_until: null,\n scheduled_for: now.toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n }\n\n async purgeFailed(messageId: string): Promise<void> {\n const row = await this.loadById(messageId);\n if (!row) return;\n if (row.status !== 'dlq' && row.status !== 'failed') {\n throw new Error(`INVALID_STATE: cannot purge message in status=${row.status}`);\n }\n await this.engine.delete(QUEUE_TABLE, { id: messageId, context: SYSTEM_CTX });\n }\n\n // ── Worker lifecycle ─────────────────────────────────────────────\n\n start(): void {\n if (this.timer) return;\n this.timer = setInterval(() => {\n if (this.running) return;\n this.running = true;\n this.pollOnce()\n .catch((err) => { this.logger?.warn?.('DbQueueAdapter: poll tick failed', err); })\n .finally(() => { this.running = false; });\n }, this.opts.pollIntervalMs);\n (this.timer as any)?.unref?.();\n }\n\n async stop(): Promise<void> {\n if (this.timer) { clearInterval(this.timer); this.timer = undefined; }\n }\n\n /** Test-friendly synchronous poll. */\n async pollOnce(): Promise<number> {\n const queues = [...this.handlers.keys()];\n if (queues.length === 0) return 0;\n\n let processed = 0;\n for (const queue of queues) {\n const claimed = await this.claimBatch(queue, this.opts.batchSize);\n for (const row of claimed) {\n await this.dispatch(row);\n processed++;\n }\n }\n return processed;\n }\n\n // ── Internals ────────────────────────────────────────────────────\n\n private async claimBatch(queue: string, max: number): Promise<any[]> {\n const now = this.now();\n const candidates = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: max * 3, // over-fetch in case of CAS contention\n orderBy: [\n { field: 'priority', order: 'asc' },\n { field: 'scheduled_for', order: 'asc' },\n ],\n context: SYSTEM_CTX,\n });\n\n const out: any[] = [];\n for (const row of candidates ?? []) {\n if (out.length >= max) break;\n const sched = row.scheduled_for ? new Date(row.scheduled_for).getTime() : 0;\n if (sched > now.getTime()) continue;\n // Honor existing lease\n const lockedUntil = row.locked_until ? new Date(row.locked_until).getTime() : 0;\n if (row.locked_by && lockedUntil > now.getTime()) continue;\n\n // CAS — only update if still pending (best-effort with engine.update which\n // typically does row-level update by id; concurrent workers will overwrite\n // each other but the dispatcher tolerates duplicate delivery via attempts).\n try {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'running',\n locked_by: this.opts.workerId,\n locked_until: new Date(now.getTime() + this.opts.leaseMs).toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n out.push({ ...row, status: 'running' });\n } catch (err) {\n this.logger?.warn?.('DbQueueAdapter: claim CAS failed', err as any);\n }\n }\n return out;\n }\n\n private async dispatch(row: any): Promise<void> {\n const handlers = this.handlers.get(row.queue) ?? [];\n if (handlers.length === 0) {\n // No handler — release lease so another process can pick it up\n await this.releasePending(row.id);\n return;\n }\n\n const msg: QueueMessage = {\n id: String(row.id),\n data: parseJson(row.payload_json),\n attempts: (row.attempts ?? 0) + 1,\n timestamp: row.created_at ? new Date(row.created_at).getTime() : Date.now(),\n };\n\n let success = true;\n let lastError: string | undefined;\n for (const h of handlers) {\n try { await h.fn(msg); }\n catch (err) {\n success = false;\n lastError = err instanceof Error ? err.message : String(err);\n this.logger?.warn?.(`DbQueueAdapter: handler failed on ${row.queue}`, err as any);\n break;\n }\n }\n\n const now = this.now();\n if (success) {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'completed',\n attempts: msg.attempts,\n completed_at: now.toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n return;\n }\n\n const attempts = msg.attempts;\n const max = row.max_attempts ?? this.opts.defaultMaxAttempts;\n if (attempts >= max) {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'dlq',\n attempts,\n last_error: lastError ?? 'unknown error',\n completed_at: now.toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n return;\n }\n\n const backoffMs = this.computeBackoff(row, attempts);\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'pending',\n attempts,\n last_error: lastError ?? 'unknown error',\n scheduled_for: new Date(now.getTime() + backoffMs).toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n }\n\n private computeBackoff(row: any, attempt: number): number {\n const base = row.backoff_delay_ms ?? 1000;\n const cap = row.backoff_max_delay_ms ?? undefined;\n if ((row.backoff_type ?? 'exponential') === 'fixed') return base;\n const exp = base * Math.pow(2, Math.max(0, attempt - 1));\n return cap ? Math.min(exp, cap) : exp;\n }\n\n private async releasePending(id: string): Promise<void> {\n const now = this.now();\n try {\n await this.engine.update(QUEUE_TABLE, {\n id,\n status: 'pending',\n locked_by: null,\n locked_until: null,\n scheduled_for: new Date(now.getTime() + this.opts.pollIntervalMs * 5).toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n } catch (err) {\n this.logger?.warn?.('DbQueueAdapter: release failed', err as any);\n }\n }\n\n private async loadById(id: string): Promise<any | null> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { id },\n limit: 1,\n context: SYSTEM_CTX,\n });\n return rows?.[0] ?? null;\n }\n\n private rowToRecord(r: any): QueueMessageRecord {\n return {\n id: String(r.id),\n queue: String(r.queue),\n data: parseJson(r.payload_json),\n status: r.status,\n attempts: r.attempts ?? 0,\n maxAttempts: r.max_attempts ?? this.opts.defaultMaxAttempts,\n scheduledFor: r.scheduled_for ?? undefined,\n lockedBy: r.locked_by ?? undefined,\n lockedUntil: r.locked_until ?? undefined,\n lastError: r.last_error ?? undefined,\n idempotencyKey: r.idempotency_key ?? undefined,\n metadata: parseJson(r.metadata_json),\n createdAt: r.created_at ?? nowIso(this.clock),\n updatedAt: r.updated_at ?? undefined,\n completedAt: r.completed_at ?? undefined,\n };\n }\n\n private now(): Date {\n return this.clock?.now() ?? new Date();\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/queue-service-plugin.ts","../src/memory-queue-adapter.ts","../src/common.ts","../src/db-queue-adapter.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nexport { QueueServicePlugin } from './queue-service-plugin.js';\nexport type { QueueServicePluginOptions } from './queue-service-plugin.js';\nexport { MemoryQueueAdapter } from './memory-queue-adapter.js';\nexport type { MemoryQueueAdapterOptions } from './memory-queue-adapter.js';\nexport { DbQueueAdapter } from './db-queue-adapter.js';\nexport type { DbQueueAdapterOptions } from './db-queue-adapter.js';\nexport type { JobEngine, JobClock, JobLogger } from './common.js';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport { SysJobQueue } from '@objectstack/platform-objects/audit';\nimport { MemoryQueueAdapter } from './memory-queue-adapter.js';\nimport type { MemoryQueueAdapterOptions } from './memory-queue-adapter.js';\nimport { DbQueueAdapter } from './db-queue-adapter.js';\nimport type { DbQueueAdapterOptions } from './db-queue-adapter.js';\n\n/**\n * Configuration options for the QueueServicePlugin.\n */\nexport interface QueueServicePluginOptions {\n /**\n * Queue adapter type.\n * - 'auto' (default): use DbQueueAdapter when objectql engine available, else MemoryQueueAdapter\n * - 'db': require objectql; persists messages, retries, and DLQ to sys_job_queue\n * - 'memory': in-process MemoryQueueAdapter (non-durable, dev/test)\n */\n adapter?: 'auto' | 'db' | 'memory';\n /** Options for the memory queue adapter */\n memory?: MemoryQueueAdapterOptions;\n /** Options for the DB adapter (polling, batch, lease, idempotency window…) */\n db?: DbQueueAdapterOptions;\n}\n\n/**\n * QueueServicePlugin — Production IQueueService implementation.\n *\n * Default: registers MemoryQueueAdapter synchronously so producers can\n * publish during plugin init; upgrades to DbQueueAdapter on `kernel:ready`\n * when an ObjectQL engine is available. Subscribers registered against\n * the (now-replaced) memory queue must re-subscribe after upgrade — for\n * that reason most plugins register subscribers inside their own\n * `kernel:ready` hook, which fires after this one.\n */\nexport class QueueServicePlugin implements Plugin {\n name = 'com.objectstack.service.queue';\n /**\n * Services init() registers on every path (ADR-0116, #4131) — lets the\n * kernel name this plugin when a consumer requires one before it inits.\n */\n providesServices = ['queue'];\n /**\n * init() registers sys_job_queue through the `manifest` service\n * ObjectQLPlugin provides — order-if-present so the registration is\n * deterministic (ADR-0116, #4471). Soft, not hard: without an engine the\n * plugin degrades on purpose (in-memory queue adapter).\n */\n optionalDependencies = ['com.objectstack.engine.objectql'];\n version = '1.1.0';\n type = 'standard';\n\n private readonly options: QueueServicePluginOptions;\n private dbAdapter?: DbQueueAdapter;\n\n constructor(options: QueueServicePluginOptions = {}) {\n this.options = { adapter: 'auto', ...options };\n }\n\n async init(ctx: PluginContext): Promise<void> {\n // Register sys_job_queue (also serves as DLQ view) so Studio can list/replay.\n try {\n ctx.getService<{ register(m: any): void }>('manifest').register({\n id: 'com.objectstack.service.queue',\n name: 'Queue Service',\n version: '1.1.0',\n type: 'plugin',\n scope: 'system',\n defaultDatasource: 'cloud',\n namespace: 'sys',\n objects: [SysJobQueue],\n });\n } catch (err) {\n ctx.logger.warn('QueueServicePlugin: manifest service unavailable; sys_job_queue not registered', err as any);\n }\n\n const choice = this.options.adapter ?? 'auto';\n\n if (choice === 'memory') {\n const q = new MemoryQueueAdapter(this.options.memory);\n ctx.registerService('queue', q);\n ctx.logger.info('QueueServicePlugin: registered MemoryQueueAdapter');\n return;\n }\n\n // auto / db — register memory placeholder, upgrade on kernel:ready\n ctx.registerService('queue', new MemoryQueueAdapter(this.options.memory));\n\n ctx.hook('kernel:ready', async () => {\n let engine: any = null;\n try { engine = ctx.getService<any>('objectql'); }\n catch { try { engine = ctx.getService<any>('data'); } catch { /* ignore */ } }\n\n if (!engine) {\n if (choice === 'db') {\n ctx.logger.warn('QueueServicePlugin: db adapter requested but no ObjectQL engine — staying on MemoryQueueAdapter');\n } else {\n ctx.logger.info('QueueServicePlugin: no ObjectQL engine — staying on MemoryQueueAdapter');\n }\n return;\n }\n\n this.dbAdapter = new DbQueueAdapter({\n engine,\n logger: ctx.logger,\n options: this.options.db,\n });\n\n try {\n (ctx as any).replaceService?.('queue', this.dbAdapter);\n this.dbAdapter.start();\n ctx.logger.info('QueueServicePlugin: upgraded to DbQueueAdapter (sys_job_queue persistence)');\n } catch (err) {\n ctx.logger.warn('QueueServicePlugin: replaceService failed; staying on MemoryQueueAdapter', err as any);\n }\n });\n }\n\n async destroy(): Promise<void> {\n await this.dbAdapter?.stop();\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IQueueService, QueuePublishOptions, QueueMessage, QueueHandler } from '@objectstack/spec/contracts';\n\n/**\n * Configuration options for MemoryQueueAdapter.\n */\nexport interface MemoryQueueAdapterOptions {\n /** Maximum number of messages retained per queue (0 = unlimited) */\n maxQueueSize?: number;\n}\n\n/**\n * In-memory queue adapter implementing IQueueService.\n *\n * Provides synchronous in-process pub/sub delivery.\n * Suitable for single-process environments, development, and testing.\n */\nexport class MemoryQueueAdapter implements IQueueService {\n private readonly handlers = new Map<string, QueueHandler[]>();\n private readonly deadLetters: QueueMessage[] = [];\n private msgCounter = 0;\n private readonly maxQueueSize: number;\n\n constructor(options: MemoryQueueAdapterOptions = {}) {\n this.maxQueueSize = options.maxQueueSize ?? 0;\n }\n\n async publish<T = unknown>(queue: string, data: T, options?: QueuePublishOptions): Promise<string> {\n const id = `msg-${++this.msgCounter}`;\n const msg: QueueMessage<T> = {\n id,\n data,\n attempts: 0,\n timestamp: Date.now(),\n };\n\n const fns = this.handlers.get(queue) ?? [];\n if (fns.length === 0) {\n // No subscribers — retain as dead letter if within limits\n if (this.maxQueueSize === 0 || this.deadLetters.length < this.maxQueueSize) {\n this.deadLetters.push(msg);\n }\n return id;\n }\n\n const maxRetries = options?.retries ?? 0;\n for (const handler of fns) {\n let attempt = 0;\n let success = false;\n while (!success && attempt <= maxRetries) {\n try {\n msg.attempts = attempt + 1;\n await handler(msg as QueueMessage);\n success = true;\n } catch {\n attempt++;\n }\n }\n }\n\n return id;\n }\n\n async subscribe<T = unknown>(queue: string, handler: QueueHandler<T>): Promise<void> {\n const existing = this.handlers.get(queue) ?? [];\n this.handlers.set(queue, [...existing, handler as QueueHandler]);\n }\n\n async unsubscribe(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n\n async getQueueSize(_queue: string): Promise<number> {\n // In-memory: no persistent queue depth tracking\n return 0;\n }\n\n async purge(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Narrow ObjectQL engine surface used by job/queue adapters.\n * Keeps the adapter testable without booting a real kernel.\n *\n * IMPORTANT: matches the canonical engine API:\n * - find: `where:` (NOT `filter:`)\n * - update: `(table, {id, ...patch}, opts)`\n */\nexport interface JobEngine {\n find(object: string, options?: any): Promise<any[]>;\n insert(object: string, data: any, options?: any): Promise<any>;\n update(object: string, idOrData: any, dataOrOptions?: any, options?: any): Promise<any>;\n delete(object: string, options?: any): Promise<any>;\n}\n\n/** Stamped only in tests to make `now` deterministic. */\nexport interface JobClock { now(): Date }\n\nexport interface JobLogger {\n info(msg: string, meta?: unknown): void;\n warn(msg: string, meta?: unknown): void;\n error?(msg: string, meta?: unknown): void;\n}\n\nexport const SYSTEM_CTX = { isSystem: true, positions: [], permissions: [] } as const;\n\nexport function uid(prefix: string): string {\n const g: any = globalThis as any;\n if (g.crypto?.randomUUID) return `${prefix}_${g.crypto.randomUUID()}`;\n return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n}\n\nexport function nowIso(clock?: JobClock): string {\n return (clock?.now() ?? new Date()).toISOString();\n}\n\nexport function parseJson<T = unknown>(raw: unknown, fallback?: T): T | undefined {\n if (raw == null) return fallback;\n if (typeof raw === 'string') {\n try { return JSON.parse(raw) as T; } catch { return fallback; }\n }\n if (typeof raw === 'object') return raw as T;\n return fallback;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type {\n IQueueService,\n QueuePublishOptions,\n QueueMessage,\n QueueMessageRecord,\n QueueHandler,\n} from '@objectstack/spec/contracts';\nimport {\n SYSTEM_CTX,\n uid,\n nowIso,\n parseJson,\n type JobEngine,\n type JobClock,\n type JobLogger,\n} from './common.js';\n\nconst QUEUE_TABLE = 'sys_job_queue';\n\nexport interface DbQueueAdapterOptions {\n /** Polling interval for the worker loop (ms, default 1000) */\n pollIntervalMs?: number;\n /** Max messages claimed per poll tick (default 10) */\n batchSize?: number;\n /** Lease duration before another worker may reclaim (ms, default 30000) */\n leaseMs?: number;\n /** Idempotency window — how long the same key blocks re-publish (ms, default 24h) */\n idempotencyWindowMs?: number;\n /** Default maxAttempts when publish doesn't specify (default 3) */\n defaultMaxAttempts?: number;\n /** Unique identifier for this worker (default: random) */\n workerId?: string;\n /** Whether to auto-start the polling worker (default true) */\n autoStart?: boolean;\n}\n\ninterface RegisteredHandler {\n queue: string;\n fn: QueueHandler;\n}\n\n/**\n * DbQueueAdapter — durable, polling, DB-backed IQueueService.\n *\n * Persists every message to `sys_job_queue`. A polling worker leases\n * pending messages (CAS update status pending→running with a lease),\n * invokes registered subscribers, and retries with backoff on failure.\n * Messages that exceed `max_attempts` land in `status='dlq'`.\n *\n * Idempotency: publish suppresses duplicates within a configurable\n * window when `(queue, idempotencyKey)` is non-null.\n *\n * Designed for SQLite and Postgres alike — uses CAS via WHERE-clauses,\n * not row-level locking.\n */\nexport class DbQueueAdapter implements IQueueService {\n private readonly engine: JobEngine;\n private readonly logger?: JobLogger;\n private readonly clock?: JobClock;\n private readonly opts: Required<Omit<DbQueueAdapterOptions, 'workerId'>> & { workerId: string };\n\n private readonly handlers = new Map<string, RegisteredHandler[]>();\n private timer?: ReturnType<typeof setInterval>;\n private running = false;\n\n constructor(args: {\n engine: JobEngine;\n logger?: JobLogger;\n clock?: JobClock;\n options?: DbQueueAdapterOptions;\n }) {\n this.engine = args.engine;\n this.logger = args.logger;\n this.clock = args.clock;\n const o = args.options ?? {};\n this.opts = {\n pollIntervalMs: o.pollIntervalMs ?? 1000,\n batchSize: o.batchSize ?? 10,\n leaseMs: o.leaseMs ?? 30_000,\n idempotencyWindowMs: o.idempotencyWindowMs ?? 24 * 60 * 60 * 1000,\n defaultMaxAttempts: o.defaultMaxAttempts ?? 3,\n autoStart: o.autoStart ?? true,\n workerId: o.workerId ?? uid('worker'),\n };\n }\n\n // ── IQueueService ────────────────────────────────────────────────\n\n async publish<T = unknown>(\n queue: string,\n data: T,\n options?: QueuePublishOptions,\n ): Promise<string> {\n const opts = options ?? {};\n const now = this.now();\n\n // Idempotency check\n if (opts.idempotencyKey) {\n const windowStart = new Date(now.getTime() - this.opts.idempotencyWindowMs).toISOString();\n const existing = await this.engine.find(QUEUE_TABLE, {\n where: {\n queue,\n idempotency_key: opts.idempotencyKey,\n // Only block if not yet terminal — completed/dlq dedup is by window via created_at\n },\n limit: 5,\n context: SYSTEM_CTX,\n });\n const blocking = (existing ?? []).find((row: any) => {\n if (row.status === 'pending' || row.status === 'running') return true;\n return String(row.created_at ?? '') >= windowStart;\n });\n if (blocking) return String(blocking.id);\n }\n\n const id = uid('msg');\n const scheduledFor = opts.scheduledFor\n ? new Date(opts.scheduledFor).toISOString()\n : opts.delay\n ? new Date(now.getTime() + opts.delay).toISOString()\n : now.toISOString();\n\n const maxAttempts = opts.maxAttempts\n ?? (opts.retries != null ? opts.retries + 1 : this.opts.defaultMaxAttempts);\n const backoff = opts.backoff ?? { type: 'exponential' as const, delayMs: 1000 };\n\n await this.engine.insert(QUEUE_TABLE, {\n id,\n queue,\n idempotency_key: opts.idempotencyKey ?? null,\n payload_json: JSON.stringify(data ?? null),\n metadata_json: opts.metadata ? JSON.stringify(opts.metadata) : null,\n status: 'pending',\n priority: opts.priority ?? 100,\n attempts: 0,\n max_attempts: maxAttempts,\n backoff_type: backoff.type,\n backoff_delay_ms: backoff.delayMs,\n backoff_max_delay_ms: backoff.maxDelayMs ?? null,\n scheduled_for: scheduledFor,\n created_at: now.toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n\n return id;\n }\n\n async subscribe<T = unknown>(queue: string, handler: QueueHandler<T>): Promise<void> {\n const existing = this.handlers.get(queue) ?? [];\n existing.push({ queue, fn: handler as QueueHandler });\n this.handlers.set(queue, existing);\n if (this.opts.autoStart) this.start();\n }\n\n async unsubscribe(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n\n async getQueueSize(queue: string): Promise<number> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: 10_000,\n context: SYSTEM_CTX,\n });\n return rows?.length ?? 0;\n }\n\n async purge(queue: string): Promise<void> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: 10_000,\n context: SYSTEM_CTX,\n });\n for (const row of rows ?? []) {\n // `where: { id }` — the engine's delete has no top-level `id` option.\n // The old `{ id: row.id }` bag carried no predicate at all, so every\n // purge delete threw \"Delete requires an ID or options.multi=true\"\n // straight into this catch: purge logged a warn per row and deleted\n // NOTHING (#4371 option-2 survey).\n try { await this.engine.delete(QUEUE_TABLE, { where: { id: row.id }, context: SYSTEM_CTX }); }\n catch (err) { this.logger?.warn?.('DbQueueAdapter: purge delete failed', err as any); }\n }\n }\n\n async listFailed(\n queue?: string,\n options?: { limit?: number; offset?: number },\n ): Promise<QueueMessageRecord[]> {\n const where: any = { status: 'dlq' };\n if (queue) where.queue = queue;\n const rows = await this.engine.find(QUEUE_TABLE, {\n where,\n limit: options?.limit ?? 100,\n offset: options?.offset,\n orderBy: [{ field: 'created_at', order: 'desc' }],\n context: SYSTEM_CTX,\n });\n return (rows ?? []).map((r: any) => this.rowToRecord(r));\n }\n\n async replay(messageId: string): Promise<void> {\n const row = await this.loadById(messageId);\n if (!row) throw new Error(`MESSAGE_NOT_FOUND: ${messageId}`);\n if (row.status !== 'dlq' && row.status !== 'failed') {\n throw new Error(`INVALID_STATE: cannot replay message in status=${row.status}`);\n }\n const now = this.now();\n await this.engine.update(QUEUE_TABLE, {\n id: messageId,\n status: 'pending',\n attempts: 0,\n last_error: null,\n locked_by: null,\n locked_until: null,\n scheduled_for: now.toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n }\n\n async purgeFailed(messageId: string): Promise<void> {\n const row = await this.loadById(messageId);\n if (!row) return;\n if (row.status !== 'dlq' && row.status !== 'failed') {\n throw new Error(`INVALID_STATE: cannot purge message in status=${row.status}`);\n }\n await this.engine.delete(QUEUE_TABLE, { where: { id: messageId }, context: SYSTEM_CTX });\n }\n\n // ── Worker lifecycle ─────────────────────────────────────────────\n\n start(): void {\n if (this.timer) return;\n this.timer = setInterval(() => {\n if (this.running) return;\n this.running = true;\n this.pollOnce()\n .catch((err) => { this.logger?.warn?.('DbQueueAdapter: poll tick failed', err); })\n .finally(() => { this.running = false; });\n }, this.opts.pollIntervalMs);\n (this.timer as any)?.unref?.();\n }\n\n async stop(): Promise<void> {\n if (this.timer) { clearInterval(this.timer); this.timer = undefined; }\n }\n\n /** Test-friendly synchronous poll. */\n async pollOnce(): Promise<number> {\n const queues = [...this.handlers.keys()];\n if (queues.length === 0) return 0;\n\n let processed = 0;\n for (const queue of queues) {\n const claimed = await this.claimBatch(queue, this.opts.batchSize);\n for (const row of claimed) {\n await this.dispatch(row);\n processed++;\n }\n }\n return processed;\n }\n\n // ── Internals ────────────────────────────────────────────────────\n\n private async claimBatch(queue: string, max: number): Promise<any[]> {\n const now = this.now();\n const candidates = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: max * 3, // over-fetch in case of CAS contention\n orderBy: [\n { field: 'priority', order: 'asc' },\n { field: 'scheduled_for', order: 'asc' },\n ],\n context: SYSTEM_CTX,\n });\n\n const out: any[] = [];\n for (const row of candidates ?? []) {\n if (out.length >= max) break;\n const sched = row.scheduled_for ? new Date(row.scheduled_for).getTime() : 0;\n if (sched > now.getTime()) continue;\n // Honor existing lease\n const lockedUntil = row.locked_until ? new Date(row.locked_until).getTime() : 0;\n if (row.locked_by && lockedUntil > now.getTime()) continue;\n\n // CAS — only update if still pending (best-effort with engine.update which\n // typically does row-level update by id; concurrent workers will overwrite\n // each other but the dispatcher tolerates duplicate delivery via attempts).\n try {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'running',\n locked_by: this.opts.workerId,\n locked_until: new Date(now.getTime() + this.opts.leaseMs).toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n out.push({ ...row, status: 'running' });\n } catch (err) {\n this.logger?.warn?.('DbQueueAdapter: claim CAS failed', err as any);\n }\n }\n return out;\n }\n\n private async dispatch(row: any): Promise<void> {\n const handlers = this.handlers.get(row.queue) ?? [];\n if (handlers.length === 0) {\n // No handler — release lease so another process can pick it up\n await this.releasePending(row.id);\n return;\n }\n\n const msg: QueueMessage = {\n id: String(row.id),\n data: parseJson(row.payload_json),\n attempts: (row.attempts ?? 0) + 1,\n timestamp: row.created_at ? new Date(row.created_at).getTime() : Date.now(),\n };\n\n let success = true;\n let lastError: string | undefined;\n for (const h of handlers) {\n try { await h.fn(msg); }\n catch (err) {\n success = false;\n lastError = err instanceof Error ? err.message : String(err);\n this.logger?.warn?.(`DbQueueAdapter: handler failed on ${row.queue}`, err as any);\n break;\n }\n }\n\n const now = this.now();\n if (success) {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'completed',\n attempts: msg.attempts,\n completed_at: now.toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n return;\n }\n\n const attempts = msg.attempts;\n const max = row.max_attempts ?? this.opts.defaultMaxAttempts;\n if (attempts >= max) {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'dlq',\n attempts,\n last_error: lastError ?? 'unknown error',\n completed_at: now.toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n return;\n }\n\n const backoffMs = this.computeBackoff(row, attempts);\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'pending',\n attempts,\n last_error: lastError ?? 'unknown error',\n scheduled_for: new Date(now.getTime() + backoffMs).toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n }\n\n private computeBackoff(row: any, attempt: number): number {\n const base = row.backoff_delay_ms ?? 1000;\n const cap = row.backoff_max_delay_ms ?? undefined;\n if ((row.backoff_type ?? 'exponential') === 'fixed') return base;\n const exp = base * Math.pow(2, Math.max(0, attempt - 1));\n return cap ? Math.min(exp, cap) : exp;\n }\n\n private async releasePending(id: string): Promise<void> {\n const now = this.now();\n try {\n await this.engine.update(QUEUE_TABLE, {\n id,\n status: 'pending',\n locked_by: null,\n locked_until: null,\n scheduled_for: new Date(now.getTime() + this.opts.pollIntervalMs * 5).toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n } catch (err) {\n this.logger?.warn?.('DbQueueAdapter: release failed', err as any);\n }\n }\n\n private async loadById(id: string): Promise<any | null> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { id },\n limit: 1,\n context: SYSTEM_CTX,\n });\n return rows?.[0] ?? null;\n }\n\n private rowToRecord(r: any): QueueMessageRecord {\n return {\n id: String(r.id),\n queue: String(r.queue),\n data: parseJson(r.payload_json),\n status: r.status,\n attempts: r.attempts ?? 0,\n maxAttempts: r.max_attempts ?? this.opts.defaultMaxAttempts,\n scheduledFor: r.scheduled_for ?? undefined,\n lockedBy: r.locked_by ?? undefined,\n lockedUntil: r.locked_until ?? undefined,\n lastError: r.last_error ?? undefined,\n idempotencyKey: r.idempotency_key ?? undefined,\n metadata: parseJson(r.metadata_json),\n createdAt: r.created_at ?? nowIso(this.clock),\n updatedAt: r.updated_at ?? undefined,\n completedAt: r.completed_at ?? undefined,\n };\n }\n\n private now(): Date {\n return this.clock?.now() ?? new Date();\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -147,6 +147,18 @@ interface QueueServicePluginOptions {
|
|
|
147
147
|
*/
|
|
148
148
|
declare class QueueServicePlugin implements Plugin {
|
|
149
149
|
name: string;
|
|
150
|
+
/**
|
|
151
|
+
* Services init() registers on every path (ADR-0116, #4131) — lets the
|
|
152
|
+
* kernel name this plugin when a consumer requires one before it inits.
|
|
153
|
+
*/
|
|
154
|
+
providesServices: string[];
|
|
155
|
+
/**
|
|
156
|
+
* init() registers sys_job_queue through the `manifest` service
|
|
157
|
+
* ObjectQLPlugin provides — order-if-present so the registration is
|
|
158
|
+
* deterministic (ADR-0116, #4471). Soft, not hard: without an engine the
|
|
159
|
+
* plugin degrades on purpose (in-memory queue adapter).
|
|
160
|
+
*/
|
|
161
|
+
optionalDependencies: string[];
|
|
150
162
|
version: string;
|
|
151
163
|
type: string;
|
|
152
164
|
private readonly options;
|
package/dist/index.d.ts
CHANGED
|
@@ -147,6 +147,18 @@ interface QueueServicePluginOptions {
|
|
|
147
147
|
*/
|
|
148
148
|
declare class QueueServicePlugin implements Plugin {
|
|
149
149
|
name: string;
|
|
150
|
+
/**
|
|
151
|
+
* Services init() registers on every path (ADR-0116, #4131) — lets the
|
|
152
|
+
* kernel name this plugin when a consumer requires one before it inits.
|
|
153
|
+
*/
|
|
154
|
+
providesServices: string[];
|
|
155
|
+
/**
|
|
156
|
+
* init() registers sys_job_queue through the `manifest` service
|
|
157
|
+
* ObjectQLPlugin provides — order-if-present so the registration is
|
|
158
|
+
* deterministic (ADR-0116, #4471). Soft, not hard: without an engine the
|
|
159
|
+
* plugin degrades on purpose (in-memory queue adapter).
|
|
160
|
+
*/
|
|
161
|
+
optionalDependencies: string[];
|
|
150
162
|
version: string;
|
|
151
163
|
type: string;
|
|
152
164
|
private readonly options;
|
package/dist/index.js
CHANGED
|
@@ -167,7 +167,7 @@ var DbQueueAdapter = class {
|
|
|
167
167
|
});
|
|
168
168
|
for (const row of rows ?? []) {
|
|
169
169
|
try {
|
|
170
|
-
await this.engine.delete(QUEUE_TABLE, { id: row.id, context: SYSTEM_CTX });
|
|
170
|
+
await this.engine.delete(QUEUE_TABLE, { where: { id: row.id }, context: SYSTEM_CTX });
|
|
171
171
|
} catch (err) {
|
|
172
172
|
this.logger?.warn?.("DbQueueAdapter: purge delete failed", err);
|
|
173
173
|
}
|
|
@@ -209,7 +209,7 @@ var DbQueueAdapter = class {
|
|
|
209
209
|
if (row.status !== "dlq" && row.status !== "failed") {
|
|
210
210
|
throw new Error(`INVALID_STATE: cannot purge message in status=${row.status}`);
|
|
211
211
|
}
|
|
212
|
-
await this.engine.delete(QUEUE_TABLE, { id: messageId, context: SYSTEM_CTX });
|
|
212
|
+
await this.engine.delete(QUEUE_TABLE, { where: { id: messageId }, context: SYSTEM_CTX });
|
|
213
213
|
}
|
|
214
214
|
// ── Worker lifecycle ─────────────────────────────────────────────
|
|
215
215
|
start() {
|
|
@@ -402,6 +402,18 @@ var DbQueueAdapter = class {
|
|
|
402
402
|
var QueueServicePlugin = class {
|
|
403
403
|
constructor(options = {}) {
|
|
404
404
|
this.name = "com.objectstack.service.queue";
|
|
405
|
+
/**
|
|
406
|
+
* Services init() registers on every path (ADR-0116, #4131) — lets the
|
|
407
|
+
* kernel name this plugin when a consumer requires one before it inits.
|
|
408
|
+
*/
|
|
409
|
+
this.providesServices = ["queue"];
|
|
410
|
+
/**
|
|
411
|
+
* init() registers sys_job_queue through the `manifest` service
|
|
412
|
+
* ObjectQLPlugin provides — order-if-present so the registration is
|
|
413
|
+
* deterministic (ADR-0116, #4471). Soft, not hard: without an engine the
|
|
414
|
+
* plugin degrades on purpose (in-memory queue adapter).
|
|
415
|
+
*/
|
|
416
|
+
this.optionalDependencies = ["com.objectstack.engine.objectql"];
|
|
405
417
|
this.version = "1.1.0";
|
|
406
418
|
this.type = "standard";
|
|
407
419
|
this.options = { adapter: "auto", ...options };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queue-service-plugin.ts","../src/memory-queue-adapter.ts","../src/common.ts","../src/db-queue-adapter.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport { SysJobQueue } from '@objectstack/platform-objects/audit';\nimport { MemoryQueueAdapter } from './memory-queue-adapter.js';\nimport type { MemoryQueueAdapterOptions } from './memory-queue-adapter.js';\nimport { DbQueueAdapter } from './db-queue-adapter.js';\nimport type { DbQueueAdapterOptions } from './db-queue-adapter.js';\n\n/**\n * Configuration options for the QueueServicePlugin.\n */\nexport interface QueueServicePluginOptions {\n /**\n * Queue adapter type.\n * - 'auto' (default): use DbQueueAdapter when objectql engine available, else MemoryQueueAdapter\n * - 'db': require objectql; persists messages, retries, and DLQ to sys_job_queue\n * - 'memory': in-process MemoryQueueAdapter (non-durable, dev/test)\n */\n adapter?: 'auto' | 'db' | 'memory';\n /** Options for the memory queue adapter */\n memory?: MemoryQueueAdapterOptions;\n /** Options for the DB adapter (polling, batch, lease, idempotency window…) */\n db?: DbQueueAdapterOptions;\n}\n\n/**\n * QueueServicePlugin — Production IQueueService implementation.\n *\n * Default: registers MemoryQueueAdapter synchronously so producers can\n * publish during plugin init; upgrades to DbQueueAdapter on `kernel:ready`\n * when an ObjectQL engine is available. Subscribers registered against\n * the (now-replaced) memory queue must re-subscribe after upgrade — for\n * that reason most plugins register subscribers inside their own\n * `kernel:ready` hook, which fires after this one.\n */\nexport class QueueServicePlugin implements Plugin {\n name = 'com.objectstack.service.queue';\n version = '1.1.0';\n type = 'standard';\n\n private readonly options: QueueServicePluginOptions;\n private dbAdapter?: DbQueueAdapter;\n\n constructor(options: QueueServicePluginOptions = {}) {\n this.options = { adapter: 'auto', ...options };\n }\n\n async init(ctx: PluginContext): Promise<void> {\n // Register sys_job_queue (also serves as DLQ view) so Studio can list/replay.\n try {\n ctx.getService<{ register(m: any): void }>('manifest').register({\n id: 'com.objectstack.service.queue',\n name: 'Queue Service',\n version: '1.1.0',\n type: 'plugin',\n scope: 'system',\n defaultDatasource: 'cloud',\n namespace: 'sys',\n objects: [SysJobQueue],\n });\n } catch (err) {\n ctx.logger.warn('QueueServicePlugin: manifest service unavailable; sys_job_queue not registered', err as any);\n }\n\n const choice = this.options.adapter ?? 'auto';\n\n if (choice === 'memory') {\n const q = new MemoryQueueAdapter(this.options.memory);\n ctx.registerService('queue', q);\n ctx.logger.info('QueueServicePlugin: registered MemoryQueueAdapter');\n return;\n }\n\n // auto / db — register memory placeholder, upgrade on kernel:ready\n ctx.registerService('queue', new MemoryQueueAdapter(this.options.memory));\n\n ctx.hook('kernel:ready', async () => {\n let engine: any = null;\n try { engine = ctx.getService<any>('objectql'); }\n catch { try { engine = ctx.getService<any>('data'); } catch { /* ignore */ } }\n\n if (!engine) {\n if (choice === 'db') {\n ctx.logger.warn('QueueServicePlugin: db adapter requested but no ObjectQL engine — staying on MemoryQueueAdapter');\n } else {\n ctx.logger.info('QueueServicePlugin: no ObjectQL engine — staying on MemoryQueueAdapter');\n }\n return;\n }\n\n this.dbAdapter = new DbQueueAdapter({\n engine,\n logger: ctx.logger,\n options: this.options.db,\n });\n\n try {\n (ctx as any).replaceService?.('queue', this.dbAdapter);\n this.dbAdapter.start();\n ctx.logger.info('QueueServicePlugin: upgraded to DbQueueAdapter (sys_job_queue persistence)');\n } catch (err) {\n ctx.logger.warn('QueueServicePlugin: replaceService failed; staying on MemoryQueueAdapter', err as any);\n }\n });\n }\n\n async destroy(): Promise<void> {\n await this.dbAdapter?.stop();\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IQueueService, QueuePublishOptions, QueueMessage, QueueHandler } from '@objectstack/spec/contracts';\n\n/**\n * Configuration options for MemoryQueueAdapter.\n */\nexport interface MemoryQueueAdapterOptions {\n /** Maximum number of messages retained per queue (0 = unlimited) */\n maxQueueSize?: number;\n}\n\n/**\n * In-memory queue adapter implementing IQueueService.\n *\n * Provides synchronous in-process pub/sub delivery.\n * Suitable for single-process environments, development, and testing.\n */\nexport class MemoryQueueAdapter implements IQueueService {\n private readonly handlers = new Map<string, QueueHandler[]>();\n private readonly deadLetters: QueueMessage[] = [];\n private msgCounter = 0;\n private readonly maxQueueSize: number;\n\n constructor(options: MemoryQueueAdapterOptions = {}) {\n this.maxQueueSize = options.maxQueueSize ?? 0;\n }\n\n async publish<T = unknown>(queue: string, data: T, options?: QueuePublishOptions): Promise<string> {\n const id = `msg-${++this.msgCounter}`;\n const msg: QueueMessage<T> = {\n id,\n data,\n attempts: 0,\n timestamp: Date.now(),\n };\n\n const fns = this.handlers.get(queue) ?? [];\n if (fns.length === 0) {\n // No subscribers — retain as dead letter if within limits\n if (this.maxQueueSize === 0 || this.deadLetters.length < this.maxQueueSize) {\n this.deadLetters.push(msg);\n }\n return id;\n }\n\n const maxRetries = options?.retries ?? 0;\n for (const handler of fns) {\n let attempt = 0;\n let success = false;\n while (!success && attempt <= maxRetries) {\n try {\n msg.attempts = attempt + 1;\n await handler(msg as QueueMessage);\n success = true;\n } catch {\n attempt++;\n }\n }\n }\n\n return id;\n }\n\n async subscribe<T = unknown>(queue: string, handler: QueueHandler<T>): Promise<void> {\n const existing = this.handlers.get(queue) ?? [];\n this.handlers.set(queue, [...existing, handler as QueueHandler]);\n }\n\n async unsubscribe(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n\n async getQueueSize(_queue: string): Promise<number> {\n // In-memory: no persistent queue depth tracking\n return 0;\n }\n\n async purge(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Narrow ObjectQL engine surface used by job/queue adapters.\n * Keeps the adapter testable without booting a real kernel.\n *\n * IMPORTANT: matches the canonical engine API:\n * - find: `where:` (NOT `filter:`)\n * - update: `(table, {id, ...patch}, opts)`\n */\nexport interface JobEngine {\n find(object: string, options?: any): Promise<any[]>;\n insert(object: string, data: any, options?: any): Promise<any>;\n update(object: string, idOrData: any, dataOrOptions?: any, options?: any): Promise<any>;\n delete(object: string, options?: any): Promise<any>;\n}\n\n/** Stamped only in tests to make `now` deterministic. */\nexport interface JobClock { now(): Date }\n\nexport interface JobLogger {\n info(msg: string, meta?: unknown): void;\n warn(msg: string, meta?: unknown): void;\n error?(msg: string, meta?: unknown): void;\n}\n\nexport const SYSTEM_CTX = { isSystem: true, positions: [], permissions: [] } as const;\n\nexport function uid(prefix: string): string {\n const g: any = globalThis as any;\n if (g.crypto?.randomUUID) return `${prefix}_${g.crypto.randomUUID()}`;\n return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n}\n\nexport function nowIso(clock?: JobClock): string {\n return (clock?.now() ?? new Date()).toISOString();\n}\n\nexport function parseJson<T = unknown>(raw: unknown, fallback?: T): T | undefined {\n if (raw == null) return fallback;\n if (typeof raw === 'string') {\n try { return JSON.parse(raw) as T; } catch { return fallback; }\n }\n if (typeof raw === 'object') return raw as T;\n return fallback;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type {\n IQueueService,\n QueuePublishOptions,\n QueueMessage,\n QueueMessageRecord,\n QueueHandler,\n} from '@objectstack/spec/contracts';\nimport {\n SYSTEM_CTX,\n uid,\n nowIso,\n parseJson,\n type JobEngine,\n type JobClock,\n type JobLogger,\n} from './common.js';\n\nconst QUEUE_TABLE = 'sys_job_queue';\n\nexport interface DbQueueAdapterOptions {\n /** Polling interval for the worker loop (ms, default 1000) */\n pollIntervalMs?: number;\n /** Max messages claimed per poll tick (default 10) */\n batchSize?: number;\n /** Lease duration before another worker may reclaim (ms, default 30000) */\n leaseMs?: number;\n /** Idempotency window — how long the same key blocks re-publish (ms, default 24h) */\n idempotencyWindowMs?: number;\n /** Default maxAttempts when publish doesn't specify (default 3) */\n defaultMaxAttempts?: number;\n /** Unique identifier for this worker (default: random) */\n workerId?: string;\n /** Whether to auto-start the polling worker (default true) */\n autoStart?: boolean;\n}\n\ninterface RegisteredHandler {\n queue: string;\n fn: QueueHandler;\n}\n\n/**\n * DbQueueAdapter — durable, polling, DB-backed IQueueService.\n *\n * Persists every message to `sys_job_queue`. A polling worker leases\n * pending messages (CAS update status pending→running with a lease),\n * invokes registered subscribers, and retries with backoff on failure.\n * Messages that exceed `max_attempts` land in `status='dlq'`.\n *\n * Idempotency: publish suppresses duplicates within a configurable\n * window when `(queue, idempotencyKey)` is non-null.\n *\n * Designed for SQLite and Postgres alike — uses CAS via WHERE-clauses,\n * not row-level locking.\n */\nexport class DbQueueAdapter implements IQueueService {\n private readonly engine: JobEngine;\n private readonly logger?: JobLogger;\n private readonly clock?: JobClock;\n private readonly opts: Required<Omit<DbQueueAdapterOptions, 'workerId'>> & { workerId: string };\n\n private readonly handlers = new Map<string, RegisteredHandler[]>();\n private timer?: ReturnType<typeof setInterval>;\n private running = false;\n\n constructor(args: {\n engine: JobEngine;\n logger?: JobLogger;\n clock?: JobClock;\n options?: DbQueueAdapterOptions;\n }) {\n this.engine = args.engine;\n this.logger = args.logger;\n this.clock = args.clock;\n const o = args.options ?? {};\n this.opts = {\n pollIntervalMs: o.pollIntervalMs ?? 1000,\n batchSize: o.batchSize ?? 10,\n leaseMs: o.leaseMs ?? 30_000,\n idempotencyWindowMs: o.idempotencyWindowMs ?? 24 * 60 * 60 * 1000,\n defaultMaxAttempts: o.defaultMaxAttempts ?? 3,\n autoStart: o.autoStart ?? true,\n workerId: o.workerId ?? uid('worker'),\n };\n }\n\n // ── IQueueService ────────────────────────────────────────────────\n\n async publish<T = unknown>(\n queue: string,\n data: T,\n options?: QueuePublishOptions,\n ): Promise<string> {\n const opts = options ?? {};\n const now = this.now();\n\n // Idempotency check\n if (opts.idempotencyKey) {\n const windowStart = new Date(now.getTime() - this.opts.idempotencyWindowMs).toISOString();\n const existing = await this.engine.find(QUEUE_TABLE, {\n where: {\n queue,\n idempotency_key: opts.idempotencyKey,\n // Only block if not yet terminal — completed/dlq dedup is by window via created_at\n },\n limit: 5,\n context: SYSTEM_CTX,\n });\n const blocking = (existing ?? []).find((row: any) => {\n if (row.status === 'pending' || row.status === 'running') return true;\n return String(row.created_at ?? '') >= windowStart;\n });\n if (blocking) return String(blocking.id);\n }\n\n const id = uid('msg');\n const scheduledFor = opts.scheduledFor\n ? new Date(opts.scheduledFor).toISOString()\n : opts.delay\n ? new Date(now.getTime() + opts.delay).toISOString()\n : now.toISOString();\n\n const maxAttempts = opts.maxAttempts\n ?? (opts.retries != null ? opts.retries + 1 : this.opts.defaultMaxAttempts);\n const backoff = opts.backoff ?? { type: 'exponential' as const, delayMs: 1000 };\n\n await this.engine.insert(QUEUE_TABLE, {\n id,\n queue,\n idempotency_key: opts.idempotencyKey ?? null,\n payload_json: JSON.stringify(data ?? null),\n metadata_json: opts.metadata ? JSON.stringify(opts.metadata) : null,\n status: 'pending',\n priority: opts.priority ?? 100,\n attempts: 0,\n max_attempts: maxAttempts,\n backoff_type: backoff.type,\n backoff_delay_ms: backoff.delayMs,\n backoff_max_delay_ms: backoff.maxDelayMs ?? null,\n scheduled_for: scheduledFor,\n created_at: now.toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n\n return id;\n }\n\n async subscribe<T = unknown>(queue: string, handler: QueueHandler<T>): Promise<void> {\n const existing = this.handlers.get(queue) ?? [];\n existing.push({ queue, fn: handler as QueueHandler });\n this.handlers.set(queue, existing);\n if (this.opts.autoStart) this.start();\n }\n\n async unsubscribe(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n\n async getQueueSize(queue: string): Promise<number> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: 10_000,\n context: SYSTEM_CTX,\n });\n return rows?.length ?? 0;\n }\n\n async purge(queue: string): Promise<void> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: 10_000,\n context: SYSTEM_CTX,\n });\n for (const row of rows ?? []) {\n try { await this.engine.delete(QUEUE_TABLE, { id: row.id, context: SYSTEM_CTX }); }\n catch (err) { this.logger?.warn?.('DbQueueAdapter: purge delete failed', err as any); }\n }\n }\n\n async listFailed(\n queue?: string,\n options?: { limit?: number; offset?: number },\n ): Promise<QueueMessageRecord[]> {\n const where: any = { status: 'dlq' };\n if (queue) where.queue = queue;\n const rows = await this.engine.find(QUEUE_TABLE, {\n where,\n limit: options?.limit ?? 100,\n offset: options?.offset,\n orderBy: [{ field: 'created_at', order: 'desc' }],\n context: SYSTEM_CTX,\n });\n return (rows ?? []).map((r: any) => this.rowToRecord(r));\n }\n\n async replay(messageId: string): Promise<void> {\n const row = await this.loadById(messageId);\n if (!row) throw new Error(`MESSAGE_NOT_FOUND: ${messageId}`);\n if (row.status !== 'dlq' && row.status !== 'failed') {\n throw new Error(`INVALID_STATE: cannot replay message in status=${row.status}`);\n }\n const now = this.now();\n await this.engine.update(QUEUE_TABLE, {\n id: messageId,\n status: 'pending',\n attempts: 0,\n last_error: null,\n locked_by: null,\n locked_until: null,\n scheduled_for: now.toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n }\n\n async purgeFailed(messageId: string): Promise<void> {\n const row = await this.loadById(messageId);\n if (!row) return;\n if (row.status !== 'dlq' && row.status !== 'failed') {\n throw new Error(`INVALID_STATE: cannot purge message in status=${row.status}`);\n }\n await this.engine.delete(QUEUE_TABLE, { id: messageId, context: SYSTEM_CTX });\n }\n\n // ── Worker lifecycle ─────────────────────────────────────────────\n\n start(): void {\n if (this.timer) return;\n this.timer = setInterval(() => {\n if (this.running) return;\n this.running = true;\n this.pollOnce()\n .catch((err) => { this.logger?.warn?.('DbQueueAdapter: poll tick failed', err); })\n .finally(() => { this.running = false; });\n }, this.opts.pollIntervalMs);\n (this.timer as any)?.unref?.();\n }\n\n async stop(): Promise<void> {\n if (this.timer) { clearInterval(this.timer); this.timer = undefined; }\n }\n\n /** Test-friendly synchronous poll. */\n async pollOnce(): Promise<number> {\n const queues = [...this.handlers.keys()];\n if (queues.length === 0) return 0;\n\n let processed = 0;\n for (const queue of queues) {\n const claimed = await this.claimBatch(queue, this.opts.batchSize);\n for (const row of claimed) {\n await this.dispatch(row);\n processed++;\n }\n }\n return processed;\n }\n\n // ── Internals ────────────────────────────────────────────────────\n\n private async claimBatch(queue: string, max: number): Promise<any[]> {\n const now = this.now();\n const candidates = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: max * 3, // over-fetch in case of CAS contention\n orderBy: [\n { field: 'priority', order: 'asc' },\n { field: 'scheduled_for', order: 'asc' },\n ],\n context: SYSTEM_CTX,\n });\n\n const out: any[] = [];\n for (const row of candidates ?? []) {\n if (out.length >= max) break;\n const sched = row.scheduled_for ? new Date(row.scheduled_for).getTime() : 0;\n if (sched > now.getTime()) continue;\n // Honor existing lease\n const lockedUntil = row.locked_until ? new Date(row.locked_until).getTime() : 0;\n if (row.locked_by && lockedUntil > now.getTime()) continue;\n\n // CAS — only update if still pending (best-effort with engine.update which\n // typically does row-level update by id; concurrent workers will overwrite\n // each other but the dispatcher tolerates duplicate delivery via attempts).\n try {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'running',\n locked_by: this.opts.workerId,\n locked_until: new Date(now.getTime() + this.opts.leaseMs).toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n out.push({ ...row, status: 'running' });\n } catch (err) {\n this.logger?.warn?.('DbQueueAdapter: claim CAS failed', err as any);\n }\n }\n return out;\n }\n\n private async dispatch(row: any): Promise<void> {\n const handlers = this.handlers.get(row.queue) ?? [];\n if (handlers.length === 0) {\n // No handler — release lease so another process can pick it up\n await this.releasePending(row.id);\n return;\n }\n\n const msg: QueueMessage = {\n id: String(row.id),\n data: parseJson(row.payload_json),\n attempts: (row.attempts ?? 0) + 1,\n timestamp: row.created_at ? new Date(row.created_at).getTime() : Date.now(),\n };\n\n let success = true;\n let lastError: string | undefined;\n for (const h of handlers) {\n try { await h.fn(msg); }\n catch (err) {\n success = false;\n lastError = err instanceof Error ? err.message : String(err);\n this.logger?.warn?.(`DbQueueAdapter: handler failed on ${row.queue}`, err as any);\n break;\n }\n }\n\n const now = this.now();\n if (success) {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'completed',\n attempts: msg.attempts,\n completed_at: now.toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n return;\n }\n\n const attempts = msg.attempts;\n const max = row.max_attempts ?? this.opts.defaultMaxAttempts;\n if (attempts >= max) {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'dlq',\n attempts,\n last_error: lastError ?? 'unknown error',\n completed_at: now.toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n return;\n }\n\n const backoffMs = this.computeBackoff(row, attempts);\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'pending',\n attempts,\n last_error: lastError ?? 'unknown error',\n scheduled_for: new Date(now.getTime() + backoffMs).toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n }\n\n private computeBackoff(row: any, attempt: number): number {\n const base = row.backoff_delay_ms ?? 1000;\n const cap = row.backoff_max_delay_ms ?? undefined;\n if ((row.backoff_type ?? 'exponential') === 'fixed') return base;\n const exp = base * Math.pow(2, Math.max(0, attempt - 1));\n return cap ? Math.min(exp, cap) : exp;\n }\n\n private async releasePending(id: string): Promise<void> {\n const now = this.now();\n try {\n await this.engine.update(QUEUE_TABLE, {\n id,\n status: 'pending',\n locked_by: null,\n locked_until: null,\n scheduled_for: new Date(now.getTime() + this.opts.pollIntervalMs * 5).toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n } catch (err) {\n this.logger?.warn?.('DbQueueAdapter: release failed', err as any);\n }\n }\n\n private async loadById(id: string): Promise<any | null> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { id },\n limit: 1,\n context: SYSTEM_CTX,\n });\n return rows?.[0] ?? null;\n }\n\n private rowToRecord(r: any): QueueMessageRecord {\n return {\n id: String(r.id),\n queue: String(r.queue),\n data: parseJson(r.payload_json),\n status: r.status,\n attempts: r.attempts ?? 0,\n maxAttempts: r.max_attempts ?? this.opts.defaultMaxAttempts,\n scheduledFor: r.scheduled_for ?? undefined,\n lockedBy: r.locked_by ?? undefined,\n lockedUntil: r.locked_until ?? undefined,\n lastError: r.last_error ?? undefined,\n idempotencyKey: r.idempotency_key ?? undefined,\n metadata: parseJson(r.metadata_json),\n createdAt: r.created_at ?? nowIso(this.clock),\n updatedAt: r.updated_at ?? undefined,\n completedAt: r.completed_at ?? undefined,\n };\n }\n\n private now(): Date {\n return this.clock?.now() ?? new Date();\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/queue-service-plugin.ts","../src/memory-queue-adapter.ts","../src/common.ts","../src/db-queue-adapter.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport { SysJobQueue } from '@objectstack/platform-objects/audit';\nimport { MemoryQueueAdapter } from './memory-queue-adapter.js';\nimport type { MemoryQueueAdapterOptions } from './memory-queue-adapter.js';\nimport { DbQueueAdapter } from './db-queue-adapter.js';\nimport type { DbQueueAdapterOptions } from './db-queue-adapter.js';\n\n/**\n * Configuration options for the QueueServicePlugin.\n */\nexport interface QueueServicePluginOptions {\n /**\n * Queue adapter type.\n * - 'auto' (default): use DbQueueAdapter when objectql engine available, else MemoryQueueAdapter\n * - 'db': require objectql; persists messages, retries, and DLQ to sys_job_queue\n * - 'memory': in-process MemoryQueueAdapter (non-durable, dev/test)\n */\n adapter?: 'auto' | 'db' | 'memory';\n /** Options for the memory queue adapter */\n memory?: MemoryQueueAdapterOptions;\n /** Options for the DB adapter (polling, batch, lease, idempotency window…) */\n db?: DbQueueAdapterOptions;\n}\n\n/**\n * QueueServicePlugin — Production IQueueService implementation.\n *\n * Default: registers MemoryQueueAdapter synchronously so producers can\n * publish during plugin init; upgrades to DbQueueAdapter on `kernel:ready`\n * when an ObjectQL engine is available. Subscribers registered against\n * the (now-replaced) memory queue must re-subscribe after upgrade — for\n * that reason most plugins register subscribers inside their own\n * `kernel:ready` hook, which fires after this one.\n */\nexport class QueueServicePlugin implements Plugin {\n name = 'com.objectstack.service.queue';\n /**\n * Services init() registers on every path (ADR-0116, #4131) — lets the\n * kernel name this plugin when a consumer requires one before it inits.\n */\n providesServices = ['queue'];\n /**\n * init() registers sys_job_queue through the `manifest` service\n * ObjectQLPlugin provides — order-if-present so the registration is\n * deterministic (ADR-0116, #4471). Soft, not hard: without an engine the\n * plugin degrades on purpose (in-memory queue adapter).\n */\n optionalDependencies = ['com.objectstack.engine.objectql'];\n version = '1.1.0';\n type = 'standard';\n\n private readonly options: QueueServicePluginOptions;\n private dbAdapter?: DbQueueAdapter;\n\n constructor(options: QueueServicePluginOptions = {}) {\n this.options = { adapter: 'auto', ...options };\n }\n\n async init(ctx: PluginContext): Promise<void> {\n // Register sys_job_queue (also serves as DLQ view) so Studio can list/replay.\n try {\n ctx.getService<{ register(m: any): void }>('manifest').register({\n id: 'com.objectstack.service.queue',\n name: 'Queue Service',\n version: '1.1.0',\n type: 'plugin',\n scope: 'system',\n defaultDatasource: 'cloud',\n namespace: 'sys',\n objects: [SysJobQueue],\n });\n } catch (err) {\n ctx.logger.warn('QueueServicePlugin: manifest service unavailable; sys_job_queue not registered', err as any);\n }\n\n const choice = this.options.adapter ?? 'auto';\n\n if (choice === 'memory') {\n const q = new MemoryQueueAdapter(this.options.memory);\n ctx.registerService('queue', q);\n ctx.logger.info('QueueServicePlugin: registered MemoryQueueAdapter');\n return;\n }\n\n // auto / db — register memory placeholder, upgrade on kernel:ready\n ctx.registerService('queue', new MemoryQueueAdapter(this.options.memory));\n\n ctx.hook('kernel:ready', async () => {\n let engine: any = null;\n try { engine = ctx.getService<any>('objectql'); }\n catch { try { engine = ctx.getService<any>('data'); } catch { /* ignore */ } }\n\n if (!engine) {\n if (choice === 'db') {\n ctx.logger.warn('QueueServicePlugin: db adapter requested but no ObjectQL engine — staying on MemoryQueueAdapter');\n } else {\n ctx.logger.info('QueueServicePlugin: no ObjectQL engine — staying on MemoryQueueAdapter');\n }\n return;\n }\n\n this.dbAdapter = new DbQueueAdapter({\n engine,\n logger: ctx.logger,\n options: this.options.db,\n });\n\n try {\n (ctx as any).replaceService?.('queue', this.dbAdapter);\n this.dbAdapter.start();\n ctx.logger.info('QueueServicePlugin: upgraded to DbQueueAdapter (sys_job_queue persistence)');\n } catch (err) {\n ctx.logger.warn('QueueServicePlugin: replaceService failed; staying on MemoryQueueAdapter', err as any);\n }\n });\n }\n\n async destroy(): Promise<void> {\n await this.dbAdapter?.stop();\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IQueueService, QueuePublishOptions, QueueMessage, QueueHandler } from '@objectstack/spec/contracts';\n\n/**\n * Configuration options for MemoryQueueAdapter.\n */\nexport interface MemoryQueueAdapterOptions {\n /** Maximum number of messages retained per queue (0 = unlimited) */\n maxQueueSize?: number;\n}\n\n/**\n * In-memory queue adapter implementing IQueueService.\n *\n * Provides synchronous in-process pub/sub delivery.\n * Suitable for single-process environments, development, and testing.\n */\nexport class MemoryQueueAdapter implements IQueueService {\n private readonly handlers = new Map<string, QueueHandler[]>();\n private readonly deadLetters: QueueMessage[] = [];\n private msgCounter = 0;\n private readonly maxQueueSize: number;\n\n constructor(options: MemoryQueueAdapterOptions = {}) {\n this.maxQueueSize = options.maxQueueSize ?? 0;\n }\n\n async publish<T = unknown>(queue: string, data: T, options?: QueuePublishOptions): Promise<string> {\n const id = `msg-${++this.msgCounter}`;\n const msg: QueueMessage<T> = {\n id,\n data,\n attempts: 0,\n timestamp: Date.now(),\n };\n\n const fns = this.handlers.get(queue) ?? [];\n if (fns.length === 0) {\n // No subscribers — retain as dead letter if within limits\n if (this.maxQueueSize === 0 || this.deadLetters.length < this.maxQueueSize) {\n this.deadLetters.push(msg);\n }\n return id;\n }\n\n const maxRetries = options?.retries ?? 0;\n for (const handler of fns) {\n let attempt = 0;\n let success = false;\n while (!success && attempt <= maxRetries) {\n try {\n msg.attempts = attempt + 1;\n await handler(msg as QueueMessage);\n success = true;\n } catch {\n attempt++;\n }\n }\n }\n\n return id;\n }\n\n async subscribe<T = unknown>(queue: string, handler: QueueHandler<T>): Promise<void> {\n const existing = this.handlers.get(queue) ?? [];\n this.handlers.set(queue, [...existing, handler as QueueHandler]);\n }\n\n async unsubscribe(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n\n async getQueueSize(_queue: string): Promise<number> {\n // In-memory: no persistent queue depth tracking\n return 0;\n }\n\n async purge(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Narrow ObjectQL engine surface used by job/queue adapters.\n * Keeps the adapter testable without booting a real kernel.\n *\n * IMPORTANT: matches the canonical engine API:\n * - find: `where:` (NOT `filter:`)\n * - update: `(table, {id, ...patch}, opts)`\n */\nexport interface JobEngine {\n find(object: string, options?: any): Promise<any[]>;\n insert(object: string, data: any, options?: any): Promise<any>;\n update(object: string, idOrData: any, dataOrOptions?: any, options?: any): Promise<any>;\n delete(object: string, options?: any): Promise<any>;\n}\n\n/** Stamped only in tests to make `now` deterministic. */\nexport interface JobClock { now(): Date }\n\nexport interface JobLogger {\n info(msg: string, meta?: unknown): void;\n warn(msg: string, meta?: unknown): void;\n error?(msg: string, meta?: unknown): void;\n}\n\nexport const SYSTEM_CTX = { isSystem: true, positions: [], permissions: [] } as const;\n\nexport function uid(prefix: string): string {\n const g: any = globalThis as any;\n if (g.crypto?.randomUUID) return `${prefix}_${g.crypto.randomUUID()}`;\n return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n}\n\nexport function nowIso(clock?: JobClock): string {\n return (clock?.now() ?? new Date()).toISOString();\n}\n\nexport function parseJson<T = unknown>(raw: unknown, fallback?: T): T | undefined {\n if (raw == null) return fallback;\n if (typeof raw === 'string') {\n try { return JSON.parse(raw) as T; } catch { return fallback; }\n }\n if (typeof raw === 'object') return raw as T;\n return fallback;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type {\n IQueueService,\n QueuePublishOptions,\n QueueMessage,\n QueueMessageRecord,\n QueueHandler,\n} from '@objectstack/spec/contracts';\nimport {\n SYSTEM_CTX,\n uid,\n nowIso,\n parseJson,\n type JobEngine,\n type JobClock,\n type JobLogger,\n} from './common.js';\n\nconst QUEUE_TABLE = 'sys_job_queue';\n\nexport interface DbQueueAdapterOptions {\n /** Polling interval for the worker loop (ms, default 1000) */\n pollIntervalMs?: number;\n /** Max messages claimed per poll tick (default 10) */\n batchSize?: number;\n /** Lease duration before another worker may reclaim (ms, default 30000) */\n leaseMs?: number;\n /** Idempotency window — how long the same key blocks re-publish (ms, default 24h) */\n idempotencyWindowMs?: number;\n /** Default maxAttempts when publish doesn't specify (default 3) */\n defaultMaxAttempts?: number;\n /** Unique identifier for this worker (default: random) */\n workerId?: string;\n /** Whether to auto-start the polling worker (default true) */\n autoStart?: boolean;\n}\n\ninterface RegisteredHandler {\n queue: string;\n fn: QueueHandler;\n}\n\n/**\n * DbQueueAdapter — durable, polling, DB-backed IQueueService.\n *\n * Persists every message to `sys_job_queue`. A polling worker leases\n * pending messages (CAS update status pending→running with a lease),\n * invokes registered subscribers, and retries with backoff on failure.\n * Messages that exceed `max_attempts` land in `status='dlq'`.\n *\n * Idempotency: publish suppresses duplicates within a configurable\n * window when `(queue, idempotencyKey)` is non-null.\n *\n * Designed for SQLite and Postgres alike — uses CAS via WHERE-clauses,\n * not row-level locking.\n */\nexport class DbQueueAdapter implements IQueueService {\n private readonly engine: JobEngine;\n private readonly logger?: JobLogger;\n private readonly clock?: JobClock;\n private readonly opts: Required<Omit<DbQueueAdapterOptions, 'workerId'>> & { workerId: string };\n\n private readonly handlers = new Map<string, RegisteredHandler[]>();\n private timer?: ReturnType<typeof setInterval>;\n private running = false;\n\n constructor(args: {\n engine: JobEngine;\n logger?: JobLogger;\n clock?: JobClock;\n options?: DbQueueAdapterOptions;\n }) {\n this.engine = args.engine;\n this.logger = args.logger;\n this.clock = args.clock;\n const o = args.options ?? {};\n this.opts = {\n pollIntervalMs: o.pollIntervalMs ?? 1000,\n batchSize: o.batchSize ?? 10,\n leaseMs: o.leaseMs ?? 30_000,\n idempotencyWindowMs: o.idempotencyWindowMs ?? 24 * 60 * 60 * 1000,\n defaultMaxAttempts: o.defaultMaxAttempts ?? 3,\n autoStart: o.autoStart ?? true,\n workerId: o.workerId ?? uid('worker'),\n };\n }\n\n // ── IQueueService ────────────────────────────────────────────────\n\n async publish<T = unknown>(\n queue: string,\n data: T,\n options?: QueuePublishOptions,\n ): Promise<string> {\n const opts = options ?? {};\n const now = this.now();\n\n // Idempotency check\n if (opts.idempotencyKey) {\n const windowStart = new Date(now.getTime() - this.opts.idempotencyWindowMs).toISOString();\n const existing = await this.engine.find(QUEUE_TABLE, {\n where: {\n queue,\n idempotency_key: opts.idempotencyKey,\n // Only block if not yet terminal — completed/dlq dedup is by window via created_at\n },\n limit: 5,\n context: SYSTEM_CTX,\n });\n const blocking = (existing ?? []).find((row: any) => {\n if (row.status === 'pending' || row.status === 'running') return true;\n return String(row.created_at ?? '') >= windowStart;\n });\n if (blocking) return String(blocking.id);\n }\n\n const id = uid('msg');\n const scheduledFor = opts.scheduledFor\n ? new Date(opts.scheduledFor).toISOString()\n : opts.delay\n ? new Date(now.getTime() + opts.delay).toISOString()\n : now.toISOString();\n\n const maxAttempts = opts.maxAttempts\n ?? (opts.retries != null ? opts.retries + 1 : this.opts.defaultMaxAttempts);\n const backoff = opts.backoff ?? { type: 'exponential' as const, delayMs: 1000 };\n\n await this.engine.insert(QUEUE_TABLE, {\n id,\n queue,\n idempotency_key: opts.idempotencyKey ?? null,\n payload_json: JSON.stringify(data ?? null),\n metadata_json: opts.metadata ? JSON.stringify(opts.metadata) : null,\n status: 'pending',\n priority: opts.priority ?? 100,\n attempts: 0,\n max_attempts: maxAttempts,\n backoff_type: backoff.type,\n backoff_delay_ms: backoff.delayMs,\n backoff_max_delay_ms: backoff.maxDelayMs ?? null,\n scheduled_for: scheduledFor,\n created_at: now.toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n\n return id;\n }\n\n async subscribe<T = unknown>(queue: string, handler: QueueHandler<T>): Promise<void> {\n const existing = this.handlers.get(queue) ?? [];\n existing.push({ queue, fn: handler as QueueHandler });\n this.handlers.set(queue, existing);\n if (this.opts.autoStart) this.start();\n }\n\n async unsubscribe(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n\n async getQueueSize(queue: string): Promise<number> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: 10_000,\n context: SYSTEM_CTX,\n });\n return rows?.length ?? 0;\n }\n\n async purge(queue: string): Promise<void> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: 10_000,\n context: SYSTEM_CTX,\n });\n for (const row of rows ?? []) {\n // `where: { id }` — the engine's delete has no top-level `id` option.\n // The old `{ id: row.id }` bag carried no predicate at all, so every\n // purge delete threw \"Delete requires an ID or options.multi=true\"\n // straight into this catch: purge logged a warn per row and deleted\n // NOTHING (#4371 option-2 survey).\n try { await this.engine.delete(QUEUE_TABLE, { where: { id: row.id }, context: SYSTEM_CTX }); }\n catch (err) { this.logger?.warn?.('DbQueueAdapter: purge delete failed', err as any); }\n }\n }\n\n async listFailed(\n queue?: string,\n options?: { limit?: number; offset?: number },\n ): Promise<QueueMessageRecord[]> {\n const where: any = { status: 'dlq' };\n if (queue) where.queue = queue;\n const rows = await this.engine.find(QUEUE_TABLE, {\n where,\n limit: options?.limit ?? 100,\n offset: options?.offset,\n orderBy: [{ field: 'created_at', order: 'desc' }],\n context: SYSTEM_CTX,\n });\n return (rows ?? []).map((r: any) => this.rowToRecord(r));\n }\n\n async replay(messageId: string): Promise<void> {\n const row = await this.loadById(messageId);\n if (!row) throw new Error(`MESSAGE_NOT_FOUND: ${messageId}`);\n if (row.status !== 'dlq' && row.status !== 'failed') {\n throw new Error(`INVALID_STATE: cannot replay message in status=${row.status}`);\n }\n const now = this.now();\n await this.engine.update(QUEUE_TABLE, {\n id: messageId,\n status: 'pending',\n attempts: 0,\n last_error: null,\n locked_by: null,\n locked_until: null,\n scheduled_for: now.toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n }\n\n async purgeFailed(messageId: string): Promise<void> {\n const row = await this.loadById(messageId);\n if (!row) return;\n if (row.status !== 'dlq' && row.status !== 'failed') {\n throw new Error(`INVALID_STATE: cannot purge message in status=${row.status}`);\n }\n await this.engine.delete(QUEUE_TABLE, { where: { id: messageId }, context: SYSTEM_CTX });\n }\n\n // ── Worker lifecycle ─────────────────────────────────────────────\n\n start(): void {\n if (this.timer) return;\n this.timer = setInterval(() => {\n if (this.running) return;\n this.running = true;\n this.pollOnce()\n .catch((err) => { this.logger?.warn?.('DbQueueAdapter: poll tick failed', err); })\n .finally(() => { this.running = false; });\n }, this.opts.pollIntervalMs);\n (this.timer as any)?.unref?.();\n }\n\n async stop(): Promise<void> {\n if (this.timer) { clearInterval(this.timer); this.timer = undefined; }\n }\n\n /** Test-friendly synchronous poll. */\n async pollOnce(): Promise<number> {\n const queues = [...this.handlers.keys()];\n if (queues.length === 0) return 0;\n\n let processed = 0;\n for (const queue of queues) {\n const claimed = await this.claimBatch(queue, this.opts.batchSize);\n for (const row of claimed) {\n await this.dispatch(row);\n processed++;\n }\n }\n return processed;\n }\n\n // ── Internals ────────────────────────────────────────────────────\n\n private async claimBatch(queue: string, max: number): Promise<any[]> {\n const now = this.now();\n const candidates = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: max * 3, // over-fetch in case of CAS contention\n orderBy: [\n { field: 'priority', order: 'asc' },\n { field: 'scheduled_for', order: 'asc' },\n ],\n context: SYSTEM_CTX,\n });\n\n const out: any[] = [];\n for (const row of candidates ?? []) {\n if (out.length >= max) break;\n const sched = row.scheduled_for ? new Date(row.scheduled_for).getTime() : 0;\n if (sched > now.getTime()) continue;\n // Honor existing lease\n const lockedUntil = row.locked_until ? new Date(row.locked_until).getTime() : 0;\n if (row.locked_by && lockedUntil > now.getTime()) continue;\n\n // CAS — only update if still pending (best-effort with engine.update which\n // typically does row-level update by id; concurrent workers will overwrite\n // each other but the dispatcher tolerates duplicate delivery via attempts).\n try {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'running',\n locked_by: this.opts.workerId,\n locked_until: new Date(now.getTime() + this.opts.leaseMs).toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n out.push({ ...row, status: 'running' });\n } catch (err) {\n this.logger?.warn?.('DbQueueAdapter: claim CAS failed', err as any);\n }\n }\n return out;\n }\n\n private async dispatch(row: any): Promise<void> {\n const handlers = this.handlers.get(row.queue) ?? [];\n if (handlers.length === 0) {\n // No handler — release lease so another process can pick it up\n await this.releasePending(row.id);\n return;\n }\n\n const msg: QueueMessage = {\n id: String(row.id),\n data: parseJson(row.payload_json),\n attempts: (row.attempts ?? 0) + 1,\n timestamp: row.created_at ? new Date(row.created_at).getTime() : Date.now(),\n };\n\n let success = true;\n let lastError: string | undefined;\n for (const h of handlers) {\n try { await h.fn(msg); }\n catch (err) {\n success = false;\n lastError = err instanceof Error ? err.message : String(err);\n this.logger?.warn?.(`DbQueueAdapter: handler failed on ${row.queue}`, err as any);\n break;\n }\n }\n\n const now = this.now();\n if (success) {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'completed',\n attempts: msg.attempts,\n completed_at: now.toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n return;\n }\n\n const attempts = msg.attempts;\n const max = row.max_attempts ?? this.opts.defaultMaxAttempts;\n if (attempts >= max) {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'dlq',\n attempts,\n last_error: lastError ?? 'unknown error',\n completed_at: now.toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n return;\n }\n\n const backoffMs = this.computeBackoff(row, attempts);\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'pending',\n attempts,\n last_error: lastError ?? 'unknown error',\n scheduled_for: new Date(now.getTime() + backoffMs).toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n }\n\n private computeBackoff(row: any, attempt: number): number {\n const base = row.backoff_delay_ms ?? 1000;\n const cap = row.backoff_max_delay_ms ?? undefined;\n if ((row.backoff_type ?? 'exponential') === 'fixed') return base;\n const exp = base * Math.pow(2, Math.max(0, attempt - 1));\n return cap ? Math.min(exp, cap) : exp;\n }\n\n private async releasePending(id: string): Promise<void> {\n const now = this.now();\n try {\n await this.engine.update(QUEUE_TABLE, {\n id,\n status: 'pending',\n locked_by: null,\n locked_until: null,\n scheduled_for: new Date(now.getTime() + this.opts.pollIntervalMs * 5).toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n } catch (err) {\n this.logger?.warn?.('DbQueueAdapter: release failed', err as any);\n }\n }\n\n private async loadById(id: string): Promise<any | null> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { id },\n limit: 1,\n context: SYSTEM_CTX,\n });\n return rows?.[0] ?? null;\n }\n\n private rowToRecord(r: any): QueueMessageRecord {\n return {\n id: String(r.id),\n queue: String(r.queue),\n data: parseJson(r.payload_json),\n status: r.status,\n attempts: r.attempts ?? 0,\n maxAttempts: r.max_attempts ?? this.opts.defaultMaxAttempts,\n scheduledFor: r.scheduled_for ?? undefined,\n lockedBy: r.locked_by ?? undefined,\n lockedUntil: r.locked_until ?? undefined,\n lastError: r.last_error ?? undefined,\n idempotencyKey: r.idempotency_key ?? undefined,\n metadata: parseJson(r.metadata_json),\n createdAt: r.created_at ?? nowIso(this.clock),\n updatedAt: r.updated_at ?? undefined,\n completedAt: r.completed_at ?? undefined,\n };\n }\n\n private now(): Date {\n return this.clock?.now() ?? new Date();\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/service-queue",
|
|
3
|
-
"version": "17.0.0-rc.
|
|
3
|
+
"version": "17.0.0-rc.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Queue Service for ObjectStack — implements IQueueService with in-memory and durable DB-backed (sys_job_queue) adapters",
|
|
6
6
|
"type": "module",
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@objectstack/core": "17.0.0-rc.
|
|
18
|
-
"@objectstack/platform-objects": "17.0.0-rc.
|
|
19
|
-
"@objectstack/spec": "17.0.0-rc.
|
|
17
|
+
"@objectstack/core": "17.0.0-rc.2",
|
|
18
|
+
"@objectstack/platform-objects": "17.0.0-rc.2",
|
|
19
|
+
"@objectstack/spec": "17.0.0-rc.2"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
|
-
"@types/node": "^26.1.
|
|
22
|
+
"@types/node": "^26.1.2",
|
|
23
23
|
"typescript": "^6.0.3",
|
|
24
24
|
"vitest": "^4.1.10"
|
|
25
25
|
},
|
|
@@ -43,13 +43,15 @@
|
|
|
43
43
|
},
|
|
44
44
|
"files": [
|
|
45
45
|
"dist",
|
|
46
|
-
"README.md"
|
|
46
|
+
"README.md",
|
|
47
|
+
"CHANGELOG.md"
|
|
47
48
|
],
|
|
48
49
|
"engines": {
|
|
49
50
|
"node": ">=22.0.0"
|
|
50
51
|
},
|
|
51
52
|
"scripts": {
|
|
52
53
|
"build": "tsup --config ../../../tsup.config.ts",
|
|
54
|
+
"typecheck": "tsc --noEmit",
|
|
53
55
|
"test": "vitest run"
|
|
54
56
|
}
|
|
55
57
|
}
|