@nagi-js/pgmq 0.1.1-rc.6 → 0.1.1-rc.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -2,39 +2,14 @@ import { Queue, Tx, Millis } from '@nagi-js/core';
2
2
  import { Kysely } from 'kysely';
3
3
 
4
4
  interface PgmqQueueOpts {
5
- /** Kysely instance. The adapter does NOT own the connection lifecycle. */
6
5
  readonly db: Kysely<unknown>;
7
- /** PGMQ queue name. Default: "nagi". */
8
6
  readonly queueName?: string;
9
- /** Visibility timeout applied on every dequeue, in ms. Default: 30_000. */
10
7
  readonly visibilityTimeoutMs?: Millis;
11
- /**
12
- * Use `pgmq.create_partitioned()` instead of `pgmq.create()` when
13
- * `ensureSchema()` runs. Default: false.
14
- */
15
8
  readonly partitioned?: boolean;
16
- /**
17
- * Use `pgmq.archive()` instead of `pgmq.delete()` on ack — retains messages
18
- * in the archive table for audit. Default: false.
19
- */
20
9
  readonly archiveOnAck?: boolean;
21
10
  }
22
11
  interface PgmqQueue extends Queue {
23
- /**
24
- * Idempotent setup: installs the pgmq extension and creates the queue.
25
- * Requires database privileges to `CREATE EXTENSION`. Suitable for dev/test;
26
- * production should run these statements out-of-band.
27
- */
28
12
  ensureSchema(): Promise<void>;
29
- /**
30
- * Returns a `Queue` whose operations execute on the supplied transaction.
31
- * Pass `ctx.tx` to enqueue messages atomically with the handler's domain
32
- * writes — the pgmq message commits with `step.completed` or rolls back
33
- * with the handler. The user must have wired `@nagi-js/postgres` and
34
- * augmented `Register.tx` so that `Tx` is a Kysely transaction at compile
35
- * time; at runtime, `tx` must be the same Kysely handle the postgres store
36
- * handed to `runStep`.
37
- */
38
13
  withTx(tx: Tx): Queue;
39
14
  }
40
15
  declare function pgmqQueue(opts: PgmqQueueOpts): PgmqQueue;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/pgmq-queue.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type {\n AttemptNumber,\n Millis,\n Queue,\n QueueDequeueOpts,\n QueueEnqueueOpts,\n QueueMessage,\n RunId,\n StepId,\n Tx,\n} from \"@nagi-js/core\";\nimport { type Kysely, sql } from \"kysely\";\n\nconst DEFAULT_QUEUE_NAME = \"nagi\";\nconst DEFAULT_VISIBILITY_TIMEOUT_MS: Millis = 30_000;\n\nexport interface PgmqQueueOpts {\n /** Kysely instance. The adapter does NOT own the connection lifecycle. */\n readonly db: Kysely<unknown>;\n /** PGMQ queue name. Default: \"nagi\". */\n readonly queueName?: string;\n /** Visibility timeout applied on every dequeue, in ms. Default: 30_000. */\n readonly visibilityTimeoutMs?: Millis;\n /**\n * Use `pgmq.create_partitioned()` instead of `pgmq.create()` when\n * `ensureSchema()` runs. Default: false.\n */\n readonly partitioned?: boolean;\n /**\n * Use `pgmq.archive()` instead of `pgmq.delete()` on ack — retains messages\n * in the archive table for audit. Default: false.\n */\n readonly archiveOnAck?: boolean;\n}\n\nexport interface PgmqQueue extends Queue {\n /**\n * Idempotent setup: installs the pgmq extension and creates the queue.\n * Requires database privileges to `CREATE EXTENSION`. Suitable for dev/test;\n * production should run these statements out-of-band.\n */\n ensureSchema(): Promise<void>;\n /**\n * Returns a `Queue` whose operations execute on the supplied transaction.\n * Pass `ctx.tx` to enqueue messages atomically with the handler's domain\n * writes — the pgmq message commits with `step.completed` or rolls back\n * with the handler. The user must have wired `@nagi-js/postgres` and\n * augmented `Register.tx` so that `Tx` is a Kysely transaction at compile\n * time; at runtime, `tx` must be the same Kysely handle the postgres store\n * handed to `runStep`.\n */\n withTx(tx: Tx): Queue;\n}\n\ninterface MessageEnvelope {\n readonly runId: string;\n readonly stepId: string;\n readonly attempt: number;\n}\n\ninterface QueueConfig {\n readonly queueName: string;\n readonly vtSeconds: number;\n readonly archiveOnAck: boolean;\n}\n\nexport function pgmqQueue(opts: PgmqQueueOpts): PgmqQueue {\n const db = opts.db;\n const queueName = opts.queueName ?? DEFAULT_QUEUE_NAME;\n const vtSeconds = Math.max(\n 1,\n Math.ceil(\n (opts.visibilityTimeoutMs ?? DEFAULT_VISIBILITY_TIMEOUT_MS) / 1000,\n ),\n );\n const partitioned = opts.partitioned ?? false;\n const archiveOnAck = opts.archiveOnAck ?? false;\n const config: QueueConfig = { queueName, vtSeconds, archiveOnAck };\n\n return {\n ...buildQueue(db, config),\n\n async ensureSchema(): Promise<void> {\n await sql`CREATE EXTENSION IF NOT EXISTS pgmq`.execute(db);\n if (partitioned) {\n await sql`SELECT pgmq.create_partitioned(${queueName})`.execute(db);\n } else {\n await sql`SELECT pgmq.create(${queueName})`.execute(db);\n }\n },\n\n withTx(tx: Tx): Queue {\n // `Tx` is the user-augmented transaction type from `@nagi-js/core`.\n // When `@nagi-js/postgres` is wired and `Register.tx` is augmented to\n // a Kysely transaction, the cast is structurally sound at runtime.\n return buildQueue(tx as unknown as Kysely<unknown>, config);\n },\n };\n}\n\nfunction buildQueue(executor: Kysely<unknown>, config: QueueConfig): Queue {\n const { queueName, vtSeconds, archiveOnAck } = config;\n\n return {\n async enqueue(\n runId: RunId,\n stepId: StepId,\n options?: QueueEnqueueOpts,\n ): Promise<void> {\n const envelope: MessageEnvelope = {\n runId,\n stepId,\n attempt: options?.attempt ?? 1,\n };\n const delaySeconds = Math.max(\n 0,\n Math.ceil((options?.delayMs ?? 0) / 1000),\n );\n await sql`SELECT pgmq.send(${queueName}, ${JSON.stringify(envelope)}::jsonb, ${delaySeconds}::int)`.execute(\n executor,\n );\n },\n\n async dequeue({\n count,\n }: QueueDequeueOpts): Promise<readonly QueueMessage[]> {\n const { rows } = await sql<{\n msg_id: string | number | bigint;\n message: unknown;\n }>`SELECT msg_id, message FROM pgmq.read(${queueName}, ${vtSeconds}::int, ${count}::int)`.execute(\n executor,\n );\n return rows.map((row) => projectMessage(row.msg_id, row.message));\n },\n\n async ack(receipt: string): Promise<void> {\n const msgId = parseReceipt(receipt);\n if (archiveOnAck) {\n await sql`SELECT pgmq.archive(${queueName}, ${msgId}::bigint)`.execute(\n executor,\n );\n } else {\n await sql`SELECT pgmq.delete(${queueName}, ${msgId}::bigint)`.execute(\n executor,\n );\n }\n },\n\n async nack(\n receipt: string,\n options?: { readonly delayMs?: Millis },\n ): Promise<void> {\n const msgId = parseReceipt(receipt);\n // set_vt expects seconds offset from now. 0 = immediately re-visible.\n // Attempt counters live in the dispatcher — nack must not mutate them.\n const delaySeconds = Math.max(\n 0,\n Math.ceil((options?.delayMs ?? 0) / 1000),\n );\n await sql`SELECT pgmq.set_vt(${queueName}, ${msgId}::bigint, ${delaySeconds}::int)`.execute(\n executor,\n );\n },\n\n async extend(receipt: string, leaseMs: Millis): Promise<void> {\n const msgId = parseReceipt(receipt);\n const vt = Math.max(1, Math.ceil(leaseMs / 1000));\n await sql`SELECT pgmq.set_vt(${queueName}, ${msgId}::bigint, ${vt}::int)`.execute(\n executor,\n );\n },\n };\n}\n\nfunction projectMessage(\n rawMsgId: string | number | bigint,\n raw: unknown,\n): QueueMessage {\n if (\n raw === null ||\n typeof raw !== \"object\" ||\n typeof (raw as { runId?: unknown }).runId !== \"string\" ||\n typeof (raw as { stepId?: unknown }).stepId !== \"string\" ||\n typeof (raw as { attempt?: unknown }).attempt !== \"number\"\n ) {\n throw new Error(\n `pgmq: malformed message envelope ${JSON.stringify(raw)} — expected { runId, stepId, attempt }`,\n );\n }\n const envelope = raw as MessageEnvelope;\n return {\n receipt: String(rawMsgId),\n runId: envelope.runId as RunId,\n stepId: envelope.stepId as StepId,\n attempt: envelope.attempt as AttemptNumber,\n payload: null,\n };\n}\n\nfunction parseReceipt(receipt: string): string {\n try {\n BigInt(receipt);\n } catch {\n throw new Error(\n `pgmq: malformed receipt ${JSON.stringify(receipt)} — expected stringified bigint msg_id`,\n );\n }\n return receipt;\n}\n"]}
1
+ {"version":3,"sources":["../src/pgmq-queue.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type {\n AttemptNumber,\n Millis,\n Queue,\n QueueDequeueOpts,\n QueueEnqueueOpts,\n QueueMessage,\n RunId,\n StepId,\n Tx,\n} from \"@nagi-js/core\";\nimport { type Kysely, sql } from \"kysely\";\n\nconst DEFAULT_QUEUE_NAME = \"nagi\";\nconst DEFAULT_VISIBILITY_TIMEOUT_MS: Millis = 30_000;\n\nexport interface PgmqQueueOpts {\n readonly db: Kysely<unknown>;\n readonly queueName?: string;\n readonly visibilityTimeoutMs?: Millis;\n readonly partitioned?: boolean;\n readonly archiveOnAck?: boolean;\n}\n\nexport interface PgmqQueue extends Queue {\n ensureSchema(): Promise<void>;\n withTx(tx: Tx): Queue;\n}\n\ninterface MessageEnvelope {\n readonly runId: string;\n readonly stepId: string;\n readonly attempt: number;\n}\n\ninterface QueueConfig {\n readonly queueName: string;\n readonly vtSeconds: number;\n readonly archiveOnAck: boolean;\n}\n\nexport function pgmqQueue(opts: PgmqQueueOpts): PgmqQueue {\n const db = opts.db;\n const queueName = opts.queueName ?? DEFAULT_QUEUE_NAME;\n const vtSeconds = Math.max(\n 1,\n Math.ceil(\n (opts.visibilityTimeoutMs ?? DEFAULT_VISIBILITY_TIMEOUT_MS) / 1000,\n ),\n );\n const partitioned = opts.partitioned ?? false;\n const archiveOnAck = opts.archiveOnAck ?? false;\n const config: QueueConfig = { queueName, vtSeconds, archiveOnAck };\n\n return {\n ...buildQueue(db, config),\n\n async ensureSchema(): Promise<void> {\n await sql`CREATE EXTENSION IF NOT EXISTS pgmq`.execute(db);\n if (partitioned) {\n await sql`SELECT pgmq.create_partitioned(${queueName})`.execute(db);\n } else {\n await sql`SELECT pgmq.create(${queueName})`.execute(db);\n }\n },\n\n withTx(tx: Tx): Queue {\n return buildQueue(tx as unknown as Kysely<unknown>, config);\n },\n };\n}\n\nfunction buildQueue(executor: Kysely<unknown>, config: QueueConfig): Queue {\n const { queueName, vtSeconds, archiveOnAck } = config;\n\n return {\n async enqueue(\n runId: RunId,\n stepId: StepId,\n options?: QueueEnqueueOpts,\n ): Promise<void> {\n const envelope: MessageEnvelope = {\n runId,\n stepId,\n attempt: options?.attempt ?? 1,\n };\n const delaySeconds = Math.max(\n 0,\n Math.ceil((options?.delayMs ?? 0) / 1000),\n );\n await sql`SELECT pgmq.send(${queueName}, ${JSON.stringify(envelope)}::jsonb, ${delaySeconds}::int)`.execute(\n executor,\n );\n },\n\n async dequeue({\n count,\n }: QueueDequeueOpts): Promise<readonly QueueMessage[]> {\n const { rows } = await sql<{\n msg_id: string | number | bigint;\n message: unknown;\n }>`SELECT msg_id, message FROM pgmq.read(${queueName}, ${vtSeconds}::int, ${count}::int)`.execute(\n executor,\n );\n return rows.map((row) => projectMessage(row.msg_id, row.message));\n },\n\n async ack(receipt: string): Promise<void> {\n const msgId = parseReceipt(receipt);\n if (archiveOnAck) {\n await sql`SELECT pgmq.archive(${queueName}, ${msgId}::bigint)`.execute(\n executor,\n );\n } else {\n await sql`SELECT pgmq.delete(${queueName}, ${msgId}::bigint)`.execute(\n executor,\n );\n }\n },\n\n async nack(\n receipt: string,\n options?: { readonly delayMs?: Millis },\n ): Promise<void> {\n const msgId = parseReceipt(receipt);\n const delaySeconds = Math.max(\n 0,\n Math.ceil((options?.delayMs ?? 0) / 1000),\n );\n await sql`SELECT pgmq.set_vt(${queueName}, ${msgId}::bigint, ${delaySeconds}::int)`.execute(\n executor,\n );\n },\n\n async extend(receipt: string, leaseMs: Millis): Promise<void> {\n const msgId = parseReceipt(receipt);\n const vt = Math.max(1, Math.ceil(leaseMs / 1000));\n await sql`SELECT pgmq.set_vt(${queueName}, ${msgId}::bigint, ${vt}::int)`.execute(\n executor,\n );\n },\n };\n}\n\nfunction projectMessage(\n rawMsgId: string | number | bigint,\n raw: unknown,\n): QueueMessage {\n if (\n raw === null ||\n typeof raw !== \"object\" ||\n typeof (raw as { runId?: unknown }).runId !== \"string\" ||\n typeof (raw as { stepId?: unknown }).stepId !== \"string\" ||\n typeof (raw as { attempt?: unknown }).attempt !== \"number\"\n ) {\n throw new Error(\n `pgmq: malformed message envelope ${JSON.stringify(raw)} — expected { runId, stepId, attempt }`,\n );\n }\n const envelope = raw as MessageEnvelope;\n return {\n receipt: String(rawMsgId),\n runId: envelope.runId as RunId,\n stepId: envelope.stepId as StepId,\n attempt: envelope.attempt as AttemptNumber,\n payload: null,\n };\n}\n\nfunction parseReceipt(receipt: string): string {\n try {\n BigInt(receipt);\n } catch {\n throw new Error(\n `pgmq: malformed receipt ${JSON.stringify(receipt)} — expected stringified bigint msg_id`,\n );\n }\n return receipt;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nagi-js/pgmq",
3
- "version": "0.1.1-rc.6",
3
+ "version": "0.1.1-rc.9",
4
4
  "description": "PGMQ Queue adapter for nagi.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -35,7 +35,7 @@
35
35
  "LICENSE"
36
36
  ],
37
37
  "dependencies": {
38
- "@nagi-js/core": "0.1.1-rc.6"
38
+ "@nagi-js/core": "0.1.1-rc.9"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "kysely": "^0.28.0"