@storyshelf/queue-sqs 0.1.3 → 0.3.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/README.md CHANGED
@@ -58,7 +58,7 @@ See `docs/architecture.md` and ADR 0009.
58
58
  A separate worker process (Node, Bun, etc.) polls the SQS queue and calls `executeCaptureJob` from `@storyshelf/core`:
59
59
 
60
60
  ```ts
61
- import { executeCaptureJob } from "@storyshelf/core";
61
+ import { executeCaptureJob } from "@storyshelf/core/capture";
62
62
  import { SqsClient, ReceiveMessageCommand, DeleteMessageCommand } from "@aws-sdk/client-sqs";
63
63
 
64
64
  while (true) {
package/dist/index.d.mts CHANGED
@@ -1,8 +1,8 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
3
  import { SQSClient } from "@aws-sdk/client-sqs";
4
- import { CaptureQueue } from "@storyshelf/core";
5
- import { Logger } from "@storyshelf/core/types";
4
+ import { CaptureQueue } from "@storyshelf/core/adapter/capture-queue";
5
+ import { Logger } from "@storyshelf/core/logger";
6
6
  //#region src/index.d.ts
7
7
  /** Options for configuring an SQS-backed CaptureQueue. */
8
8
  interface SqsCaptureQueueOptions {
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
- import { DeleteMessageCommand, ReceiveMessageCommand, SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs";
4
+ import { DeleteMessageCommand, GetQueueAttributesCommand, ReceiveMessageCommand, SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs";
5
5
  //#region src/index.ts
6
6
  function parseBody(raw) {
7
7
  return JSON.parse(raw);
@@ -36,8 +36,12 @@ function createSqsCaptureQueue(options) {
36
36
  name: "SQS Queue",
37
37
  version: globalThis.__PKG_VERSION__ ?? "0.0.0",
38
38
  description: "SQS-backed capture queue",
39
- kind: "sqs"
39
+ kind: "sqs",
40
+ category: "capture-queue"
40
41
  },
42
+ lifecycle: { init: async () => {
43
+ await client.send(new GetQueueAttributesCommand({ QueueUrl: options.queueUrl }));
44
+ } },
41
45
  /**
42
46
  * Submit a build for capture. Resolves once the message is sent to SQS.
43
47
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import {\n DeleteMessageCommand,\n ReceiveMessageCommand,\n SendMessageCommand,\n SQSClient,\n} from \"@aws-sdk/client-sqs\";\n\nimport type {\n CaptureJob,\n CaptureQueue,\n JobStatus,\n QueueEntry,\n} from \"@storyshelf/core\";\n\nimport type { Logger } from \"@storyshelf/core/types\";\n\ndeclare const __PKG_VERSION__: string | undefined;\n\n/** Options for configuring an SQS-backed CaptureQueue. */\nexport interface SqsCaptureQueueOptions {\n /** SQS queue URL. */\n queueUrl: string;\n /** Optional pre-configured SQSClient. */\n client?: SQSClient;\n /** Optional logger for queue diagnostics. */\n logger?: Logger;\n}\n\ninterface QueuedBody {\n buildId?: string;\n status?: JobStatus;\n queuedAt?: string;\n startedAt?: string;\n finishedAt?: string;\n error?: string;\n reqId?: string;\n}\n\nfunction parseBody(raw: string): QueuedBody {\n return JSON.parse(raw) as QueuedBody;\n}\n\nfunction hasQueuedOrRunningStatus(body: QueuedBody): boolean {\n const status = body.status ?? \"queued\";\n return [\"queued\", \"running\"].includes(status);\n}\n\nfunction mapQueueEntry(raw: { Body?: string }): QueueEntry {\n const body = parseBody(raw.Body ?? \"{}\");\n return {\n buildId: body.buildId ?? \"unknown\",\n status: body.status ?? \"queued\",\n queuedAt: body.queuedAt ?? new Date().toISOString(),\n };\n}\n\n/**\n * Create an SQS-backed `CaptureQueue`.\n *\n * Jobs are submitted via `SendMessage` and retrieved via `ReceiveMessage`;\n * messages are deleted after reading to prevent re-processing.\n * A separately-assembled worker polls the queue and calls\n * `executeCaptureJob` from `@storyshelf/core`.\n *\n * @param options - SQS queue URL and optional client configuration.\n * @returns A `CaptureQueue` satisfying the core interface.\n */\n/* oxlint-disable max-lines-per-function */\nexport function createSqsCaptureQueue(\n options: SqsCaptureQueueOptions,\n): CaptureQueue {\n const client = options.client ?? new SQSClient({});\n\n return {\n metadata: {\n name: \"SQS Queue\",\n version: (globalThis as unknown as { __PKG_VERSION__?: string }).__PKG_VERSION__ ?? \"0.0.0\",\n description: \"SQS-backed capture queue\",\n kind: \"sqs\",\n },\n /**\n * Submit a build for capture. Resolves once the message is sent to SQS.\n *\n * The actual capture execution happens in a separate worker that\n * polls the queue and calls `executeCaptureJob`.\n */\n async enqueue(job: CaptureJob): Promise<void> {\n await client.send(\n new SendMessageCommand({\n QueueUrl: options.queueUrl,\n MessageBody: JSON.stringify({\n buildId: job.buildId,\n reqId: job.reqId,\n }),\n MessageAttributes: {\n buildId: {\n DataType: \"String\",\n StringValue: job.buildId,\n },\n status: {\n DataType: \"String\",\n StringValue: \"queued\",\n },\n },\n }),\n );\n },\n\n /**\n * Return the current status entry for a build, or null if untracked.\n *\n * Polls the SQS queue for a message matching the buildId. If found,\n * the message is deleted so it is not re-processed.\n */\n async status(buildId: string): Promise<QueueEntry | null> {\n const resp = await client.send(\n new ReceiveMessageCommand({\n QueueUrl: options.queueUrl,\n MaxNumberOfMessages: 1,\n MessageAttributeNames: [\"All\"],\n }),\n );\n\n const messages = resp.Messages ?? [];\n if (messages.length === 0) {\n return null;\n }\n\n const [msg] = messages;\n if (!msg?.Body) {\n return null;\n }\n\n const body = parseBody(msg.Body);\n\n await client.send(\n new DeleteMessageCommand({\n QueueUrl: options.queueUrl,\n ReceiptHandle: msg.ReceiptHandle,\n }),\n );\n\n return {\n buildId: body.buildId ?? buildId,\n status: body.status ?? \"queued\",\n queuedAt: body.queuedAt ?? new Date().toISOString(),\n startedAt: body.startedAt,\n finishedAt: body.finishedAt,\n error: body.error,\n };\n },\n\n /**\n * Return queue entries that are queued or running, newest first.\n *\n * Short poll for up to 10 messages. Filters by status.\n */\n async active(): Promise<QueueEntry[]> {\n const resp = await client.send(\n new ReceiveMessageCommand({\n QueueUrl: options.queueUrl,\n MaxNumberOfMessages: 10,\n MessageAttributeNames: [\"All\"],\n }),\n );\n\n return (resp.Messages ?? [])\n .filter((message) => message.Body?.length && hasQueuedOrRunningStatus(parseBody(message.Body)))\n .map((msg) => mapQueueEntry(msg))\n .toSorted((left, right) => right.queuedAt.localeCompare(left.queuedAt));\n },\n\n /**\n * Return the most recent queue entries, newest first.\n *\n * Short poll for up to `limit` messages, sorted by queuedAt descending.\n */\n async recent(limit: number): Promise<QueueEntry[]> {\n const resp = await client.send(\n new ReceiveMessageCommand({\n QueueUrl: options.queueUrl,\n MaxNumberOfMessages: limit,\n MessageAttributeNames: [\"All\"],\n }),\n );\n\n return (resp.Messages ?? [])\n .map((msg) => mapQueueEntry(msg))\n .toSorted((left, right) => right.queuedAt.localeCompare(left.queuedAt));\n },\n };\n}"],"mappings":";;;;;AAsCA,SAAS,UAAU,KAAyB;CAC1C,OAAO,KAAK,MAAM,GAAG;AACvB;AAEA,SAAS,yBAAyB,MAA2B;CAC3D,MAAM,SAAS,KAAK,UAAU;CAC9B,OAAO,CAAC,UAAU,SAAS,CAAC,CAAC,SAAS,MAAM;AAC9C;AAEA,SAAS,cAAc,KAAoC;CACzD,MAAM,OAAO,UAAU,IAAI,QAAQ,IAAI;CACvC,OAAO;EACL,SAAS,KAAK,WAAW;EACzB,QAAQ,KAAK,UAAU;EACvB,UAAU,KAAK,6BAAY,IAAI,KAAK,EAAA,CAAE,YAAY;CACpD;AACF;;;;;;;;;;;;AAcA,SAAgB,sBACd,SACc;CACd,MAAM,SAAS,QAAQ,UAAU,IAAI,UAAU,CAAC,CAAC;CAEjD,OAAO;EACL,UAAU;GACR,MAAM;GACN,SAAU,WAAuD,mBAAmB;GACpF,aAAa;GACb,MAAM;EACR;;;;;;;EAOA,MAAM,QAAQ,KAAgC;GAC5C,MAAM,OAAO,KACX,IAAI,mBAAmB;IACrB,UAAU,QAAQ;IAClB,aAAa,KAAK,UAAU;KAC1B,SAAS,IAAI;KACb,OAAO,IAAI;IACb,CAAC;IACD,mBAAmB;KACjB,SAAS;MACP,UAAU;MACV,aAAa,IAAI;KACnB;KACA,QAAQ;MACN,UAAU;MACV,aAAa;KACf;IACF;GACF,CAAC,CACH;EACF;;;;;;;EAQA,MAAM,OAAO,SAA6C;GASxD,MAAM,YAAW,MARE,OAAO,KACxB,IAAI,sBAAsB;IACxB,UAAU,QAAQ;IAClB,qBAAqB;IACrB,uBAAuB,CAAC,KAAK;GAC/B,CAAC,CACH,EAAA,CAEsB,YAAY,CAAC;GACnC,IAAI,SAAS,WAAW,GACtB,OAAO;GAGT,MAAM,CAAC,OAAO;GACd,IAAI,CAAC,KAAK,MACR,OAAO;GAGT,MAAM,OAAO,UAAU,IAAI,IAAI;GAE/B,MAAM,OAAO,KACX,IAAI,qBAAqB;IACvB,UAAU,QAAQ;IAClB,eAAe,IAAI;GACrB,CAAC,CACH;GAEA,OAAO;IACL,SAAS,KAAK,WAAW;IACzB,QAAQ,KAAK,UAAU;IACvB,UAAU,KAAK,6BAAY,IAAI,KAAK,EAAA,CAAE,YAAY;IAClD,WAAW,KAAK;IAChB,YAAY,KAAK;IACjB,OAAO,KAAK;GACd;EACF;;;;;;EAOA,MAAM,SAAgC;GASpC,SAAQ,MARW,OAAO,KACxB,IAAI,sBAAsB;IACxB,UAAU,QAAQ;IAClB,qBAAqB;IACrB,uBAAuB,CAAC,KAAK;GAC/B,CAAC,CACH,EAAA,CAEa,YAAY,CAAC,EAAA,CACvB,QAAQ,YAAY,QAAQ,MAAM,UAAU,yBAAyB,UAAU,QAAQ,IAAI,CAAC,CAAC,CAAC,CAC9F,KAAK,QAAQ,cAAc,GAAG,CAAC,CAAC,CAChC,UAAU,MAAM,UAAU,MAAM,SAAS,cAAc,KAAK,QAAQ,CAAC;EAC1E;;;;;;EAOA,MAAM,OAAO,OAAsC;GASjD,SAAQ,MARW,OAAO,KACxB,IAAI,sBAAsB;IACxB,UAAU,QAAQ;IAClB,qBAAqB;IACrB,uBAAuB,CAAC,KAAK;GAC/B,CAAC,CACH,EAAA,CAEa,YAAY,CAAC,EAAA,CACvB,KAAK,QAAQ,cAAc,GAAG,CAAC,CAAC,CAChC,UAAU,MAAM,UAAU,MAAM,SAAS,cAAc,KAAK,QAAQ,CAAC;EAC1E;CACF;AACF"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import {\n DeleteMessageCommand,\n GetQueueAttributesCommand,\n ReceiveMessageCommand,\n SendMessageCommand,\n SQSClient,\n} from \"@aws-sdk/client-sqs\";\nimport type {\n CaptureJob,\n CaptureQueue,\n JobStatus,\n QueueEntry,\n} from \"@storyshelf/core/adapter/capture-queue\";\nimport type { Logger } from \"@storyshelf/core/logger\";\n\ndeclare const __PKG_VERSION__: string | undefined;\n\n/** Options for configuring an SQS-backed CaptureQueue. */\nexport interface SqsCaptureQueueOptions {\n /** SQS queue URL. */\n queueUrl: string;\n /** Optional pre-configured SQSClient. */\n client?: SQSClient;\n /** Optional logger for queue diagnostics. */\n logger?: Logger;\n}\n\ninterface QueuedBody {\n buildId?: string;\n status?: JobStatus;\n queuedAt?: string;\n startedAt?: string;\n finishedAt?: string;\n error?: string;\n reqId?: string;\n}\n\nfunction parseBody(raw: string): QueuedBody {\n return JSON.parse(raw) as QueuedBody;\n}\n\nfunction hasQueuedOrRunningStatus(body: QueuedBody): boolean {\n const status = body.status ?? \"queued\";\n return [\"queued\", \"running\"].includes(status);\n}\n\nfunction mapQueueEntry(raw: { Body?: string }): QueueEntry {\n const body = parseBody(raw.Body ?? \"{}\");\n return {\n buildId: body.buildId ?? \"unknown\",\n status: body.status ?? \"queued\",\n queuedAt: body.queuedAt ?? new Date().toISOString(),\n };\n}\n\n/**\n * Create an SQS-backed `CaptureQueue`.\n *\n * Jobs are submitted via `SendMessage` and retrieved via `ReceiveMessage`;\n * messages are deleted after reading to prevent re-processing.\n * A separately-assembled worker polls the queue and calls\n * `executeCaptureJob` from `@storyshelf/core`.\n *\n * @param options - SQS queue URL and optional client configuration.\n * @returns A `CaptureQueue` satisfying the core interface.\n */\n/* oxlint-disable max-lines-per-function */\nexport function createSqsCaptureQueue(options: SqsCaptureQueueOptions): CaptureQueue {\n const client = options.client ?? new SQSClient({});\n\n return {\n metadata: {\n name: \"SQS Queue\",\n version: (globalThis as unknown as { __PKG_VERSION__?: string }).__PKG_VERSION__ ?? \"0.0.0\",\n description: \"SQS-backed capture queue\",\n kind: \"sqs\",\n category: \"capture-queue\",\n },\n lifecycle: {\n init: async () => {\n await client.send(new GetQueueAttributesCommand({ QueueUrl: options.queueUrl }));\n },\n },\n /**\n * Submit a build for capture. Resolves once the message is sent to SQS.\n *\n * The actual capture execution happens in a separate worker that\n * polls the queue and calls `executeCaptureJob`.\n */\n async enqueue(job: CaptureJob): Promise<void> {\n await client.send(\n new SendMessageCommand({\n QueueUrl: options.queueUrl,\n MessageBody: JSON.stringify({\n buildId: job.buildId,\n reqId: job.reqId,\n }),\n MessageAttributes: {\n buildId: {\n DataType: \"String\",\n StringValue: job.buildId,\n },\n status: {\n DataType: \"String\",\n StringValue: \"queued\",\n },\n },\n }),\n );\n },\n\n /**\n * Return the current status entry for a build, or null if untracked.\n *\n * Polls the SQS queue for a message matching the buildId. If found,\n * the message is deleted so it is not re-processed.\n */\n async status(buildId: string): Promise<QueueEntry | null> {\n const resp = await client.send(\n new ReceiveMessageCommand({\n QueueUrl: options.queueUrl,\n MaxNumberOfMessages: 1,\n MessageAttributeNames: [\"All\"],\n }),\n );\n\n const messages = resp.Messages ?? [];\n if (messages.length === 0) {\n return null;\n }\n\n const [msg] = messages;\n if (!msg?.Body) {\n return null;\n }\n\n const body = parseBody(msg.Body);\n\n await client.send(\n new DeleteMessageCommand({\n QueueUrl: options.queueUrl,\n ReceiptHandle: msg.ReceiptHandle,\n }),\n );\n\n return {\n buildId: body.buildId ?? buildId,\n status: body.status ?? \"queued\",\n queuedAt: body.queuedAt ?? new Date().toISOString(),\n startedAt: body.startedAt,\n finishedAt: body.finishedAt,\n error: body.error,\n };\n },\n\n /**\n * Return queue entries that are queued or running, newest first.\n *\n * Short poll for up to 10 messages. Filters by status.\n */\n async active(): Promise<QueueEntry[]> {\n const resp = await client.send(\n new ReceiveMessageCommand({\n QueueUrl: options.queueUrl,\n MaxNumberOfMessages: 10,\n MessageAttributeNames: [\"All\"],\n }),\n );\n\n return (resp.Messages ?? [])\n .filter(\n (message) => message.Body?.length && hasQueuedOrRunningStatus(parseBody(message.Body)),\n )\n .map((msg) => mapQueueEntry(msg))\n .toSorted((left, right) => right.queuedAt.localeCompare(left.queuedAt));\n },\n\n /**\n * Return the most recent queue entries, newest first.\n *\n * Short poll for up to `limit` messages, sorted by queuedAt descending.\n */\n async recent(limit: number): Promise<QueueEntry[]> {\n const resp = await client.send(\n new ReceiveMessageCommand({\n QueueUrl: options.queueUrl,\n MaxNumberOfMessages: limit,\n MessageAttributeNames: [\"All\"],\n }),\n );\n\n return (resp.Messages ?? [])\n .map((msg) => mapQueueEntry(msg))\n .toSorted((left, right) => right.queuedAt.localeCompare(left.queuedAt));\n },\n };\n}\n"],"mappings":";;;;;AAqCA,SAAS,UAAU,KAAyB;CAC1C,OAAO,KAAK,MAAM,GAAG;AACvB;AAEA,SAAS,yBAAyB,MAA2B;CAC3D,MAAM,SAAS,KAAK,UAAU;CAC9B,OAAO,CAAC,UAAU,SAAS,CAAC,CAAC,SAAS,MAAM;AAC9C;AAEA,SAAS,cAAc,KAAoC;CACzD,MAAM,OAAO,UAAU,IAAI,QAAQ,IAAI;CACvC,OAAO;EACL,SAAS,KAAK,WAAW;EACzB,QAAQ,KAAK,UAAU;EACvB,UAAU,KAAK,6BAAY,IAAI,KAAK,EAAA,CAAE,YAAY;CACpD;AACF;;;;;;;;;;;;AAcA,SAAgB,sBAAsB,SAA+C;CACnF,MAAM,SAAS,QAAQ,UAAU,IAAI,UAAU,CAAC,CAAC;CAEjD,OAAO;EACL,UAAU;GACR,MAAM;GACN,SAAU,WAAuD,mBAAmB;GACpF,aAAa;GACb,MAAM;GACN,UAAU;EACZ;EACA,WAAW,EACT,MAAM,YAAY;GAChB,MAAM,OAAO,KAAK,IAAI,0BAA0B,EAAE,UAAU,QAAQ,SAAS,CAAC,CAAC;EACjF,EACF;;;;;;;EAOA,MAAM,QAAQ,KAAgC;GAC5C,MAAM,OAAO,KACX,IAAI,mBAAmB;IACrB,UAAU,QAAQ;IAClB,aAAa,KAAK,UAAU;KAC1B,SAAS,IAAI;KACb,OAAO,IAAI;IACb,CAAC;IACD,mBAAmB;KACjB,SAAS;MACP,UAAU;MACV,aAAa,IAAI;KACnB;KACA,QAAQ;MACN,UAAU;MACV,aAAa;KACf;IACF;GACF,CAAC,CACH;EACF;;;;;;;EAQA,MAAM,OAAO,SAA6C;GASxD,MAAM,YAAW,MARE,OAAO,KACxB,IAAI,sBAAsB;IACxB,UAAU,QAAQ;IAClB,qBAAqB;IACrB,uBAAuB,CAAC,KAAK;GAC/B,CAAC,CACH,EAAA,CAEsB,YAAY,CAAC;GACnC,IAAI,SAAS,WAAW,GACtB,OAAO;GAGT,MAAM,CAAC,OAAO;GACd,IAAI,CAAC,KAAK,MACR,OAAO;GAGT,MAAM,OAAO,UAAU,IAAI,IAAI;GAE/B,MAAM,OAAO,KACX,IAAI,qBAAqB;IACvB,UAAU,QAAQ;IAClB,eAAe,IAAI;GACrB,CAAC,CACH;GAEA,OAAO;IACL,SAAS,KAAK,WAAW;IACzB,QAAQ,KAAK,UAAU;IACvB,UAAU,KAAK,6BAAY,IAAI,KAAK,EAAA,CAAE,YAAY;IAClD,WAAW,KAAK;IAChB,YAAY,KAAK;IACjB,OAAO,KAAK;GACd;EACF;;;;;;EAOA,MAAM,SAAgC;GASpC,SAAQ,MARW,OAAO,KACxB,IAAI,sBAAsB;IACxB,UAAU,QAAQ;IAClB,qBAAqB;IACrB,uBAAuB,CAAC,KAAK;GAC/B,CAAC,CACH,EAAA,CAEa,YAAY,CAAC,EAAA,CACvB,QACE,YAAY,QAAQ,MAAM,UAAU,yBAAyB,UAAU,QAAQ,IAAI,CAAC,CACvF,CAAC,CACA,KAAK,QAAQ,cAAc,GAAG,CAAC,CAAC,CAChC,UAAU,MAAM,UAAU,MAAM,SAAS,cAAc,KAAK,QAAQ,CAAC;EAC1E;;;;;;EAOA,MAAM,OAAO,OAAsC;GASjD,SAAQ,MARW,OAAO,KACxB,IAAI,sBAAsB;IACxB,UAAU,QAAQ;IAClB,qBAAqB;IACrB,uBAAuB,CAAC,KAAK;GAC/B,CAAC,CACH,EAAA,CAEa,YAAY,CAAC,EAAA,CACvB,KAAK,QAAQ,cAAc,GAAG,CAAC,CAAC,CAChC,UAAU,MAAM,UAAU,MAAM,SAAS,cAAc,KAAK,QAAQ,CAAC;EAC1E;CACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storyshelf/queue-sqs",
3
- "version": "0.1.3",
3
+ "version": "0.3.2",
4
4
  "description": "SQS capture job queue adapter for StoryShelf (AWS cloud deployments).",
5
5
  "homepage": "https://github.com/GuptaSiddhant/StoryShelf#readme",
6
6
  "bugs": {
@@ -46,7 +46,7 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@aws-sdk/client-sqs": "^3.700.0",
49
- "@storyshelf/core": "^0.1.3"
49
+ "@storyshelf/core": "^0.3.2"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/node": "catalog:",