unnbound-events 2.0.19 → 2.0.20

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
@@ -30,7 +30,7 @@ server.start();
30
30
  The server automatically:
31
31
 
32
32
  - Starts HTTP server on `PORT` (default: `3000`)
33
- - Starts SQS long-polling if `UNNBOUND_SQS_URL` is set
33
+ - Starts SQS long-polling if `UNNBOUND_SQS_URL` is set. Set `queue.enabled: true` to require queue polling and fail fast when the queue URL is missing.
34
34
  - Provides `/healthcheck` endpoint (returns `200 { status: 'ok' }`)
35
35
  - Handles graceful shutdown on `SIGINT`/`SIGTERM`
36
36
 
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- export type { IncomingEvent, IncomingRequest, EventMetadata, EventSource } from './lib/event';
1
+ export { createEnqueuer } from './lib/enqueue';
2
2
  export type { ServerErrorOptions } from './lib/error';
3
- export type { EventServerOptions, EventServer } from './lib/server';
3
+ export { ServerError } from './lib/error';
4
+ export type { EventMetadata, EventSource, IncomingEvent, IncomingRequest } from './lib/event';
4
5
  export type { Handler } from './lib/routing/endpoint';
5
6
  export type { MiddlewareHandler } from './lib/routing/middleware';
6
7
  export type { EventVariables, MaybePromise } from './lib/routing/types';
8
+ export type { EventServer, EventServerOptions } from './lib/server';
7
9
  export { createServer } from './lib/server';
8
- export { ServerError } from './lib/error';
9
- export { createEnqueuer } from './lib/enqueue';
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createEnqueuer = exports.ServerError = exports.createServer = void 0;
4
- var server_1 = require("./lib/server");
5
- Object.defineProperty(exports, "createServer", { enumerable: true, get: function () { return server_1.createServer; } });
6
- var error_1 = require("./lib/error");
7
- Object.defineProperty(exports, "ServerError", { enumerable: true, get: function () { return error_1.ServerError; } });
3
+ exports.createServer = exports.ServerError = exports.createEnqueuer = void 0;
8
4
  var enqueue_1 = require("./lib/enqueue");
9
5
  Object.defineProperty(exports, "createEnqueuer", { enumerable: true, get: function () { return enqueue_1.createEnqueuer; } });
6
+ var error_1 = require("./lib/error");
7
+ Object.defineProperty(exports, "ServerError", { enumerable: true, get: function () { return error_1.ServerError; } });
8
+ var server_1 = require("./lib/server");
9
+ Object.defineProperty(exports, "createServer", { enumerable: true, get: function () { return server_1.createServer; } });
@@ -19,26 +19,26 @@ const types_1 = require("unnbound-logger-sdk/dist/types");
19
19
  * - Custom domain: proxy.domain.com/{shortId} → {shortId}
20
20
  */
21
21
  const getWorkspaceId = (url) => {
22
- url = url.replace(/^https?:\/\//, '');
22
+ const urlWithoutProtocol = url.replace(/^https?:\/\//, '');
23
23
  // Custom domain: extract path after domain (not gotemper.com or legacy unnbound.ai)
24
- if (!url.includes('gotemper.com') && !url.includes('unnbound.ai')) {
25
- return url
24
+ if (!urlWithoutProtocol.includes('gotemper.com') && !urlWithoutProtocol.includes('unnbound.ai')) {
25
+ return urlWithoutProtocol
26
26
  .replace(/^.+?\/(.+)$/, '$1')
27
27
  .replace(/^(sandbox|staging)\//, '')
28
28
  .replace(/^a?sync\//, '')
29
29
  .split('/')[0];
30
30
  }
31
31
  // New path-based format: {env}.workflow.gotemper.com/{sandbox/}{shortId} or workflow.gotemper.com/{sandbox/}{shortId}
32
- if (url.includes('.workflow.') || url.startsWith('workflow.')) {
32
+ if (urlWithoutProtocol.includes('.workflow.') || urlWithoutProtocol.startsWith('workflow.')) {
33
33
  // Extract everything after the domain, then remove sandbox/ and async/ prefixes
34
- const path = url.replace(/^.+?\/(.+)$/, '$1');
34
+ const path = urlWithoutProtocol.replace(/^.+?\/(.+)$/, '$1');
35
35
  return path
36
36
  .replace(/^(sandbox|staging)\//, '')
37
37
  .replace(/^a?sync\//, '')
38
38
  .split('/')[0];
39
39
  }
40
40
  // Legacy hostname format: {shortId}.workspaces.{env}.unnbound.ai
41
- return url.split('.')[0];
41
+ return urlWithoutProtocol.split('.')[0];
42
42
  };
43
43
  const TYPE_PATH_PREFIXES = {
44
44
  sandbox: '/sandbox',
@@ -101,6 +101,7 @@ const getWorkflowDomain = (url) => {
101
101
  // Custom domain - return hostname as-is
102
102
  return hostname;
103
103
  };
104
+ const getWorkflowProtocol = (url) => (url.startsWith('http://') ? 'http' : 'https');
104
105
  /**
105
106
  * Create an HTTP-based enqueuer that posts to the ingress service.
106
107
  * Used for legacy SQS mode or cross-workflow communication.
@@ -117,7 +118,7 @@ const createHttpEnqueuer = () => {
117
118
  const workspaceId = getWorkspaceId(url);
118
119
  const domain = getWorkflowDomain(url);
119
120
  const workflowType = getWorkflowType(url);
120
- const baseURL = `https://${domain}${TYPE_PATH_PREFIXES[workflowType]}/async/${workspaceId}`;
121
+ const baseURL = `${getWorkflowProtocol(url)}://${domain}${TYPE_PATH_PREFIXES[workflowType]}/async/${workspaceId}`;
121
122
  const client = (0, unnbound_logger_sdk_1.traceAxios)(axios_1.default.create({ baseURL }), { getPayload: internal_1.internal });
122
123
  return async (event, metadata) => {
123
124
  unnbound_logger_sdk_1.logger.info({ event, metadata }, 'Enqueuing event via HTTP...');
@@ -4,6 +4,7 @@ import { type Middleware } from './routing/middleware';
4
4
  import type { EventVariables } from './routing/types';
5
5
  type HttpOptions = {};
6
6
  interface QueueOptions {
7
+ enabled?: boolean;
7
8
  maxMessages?: number;
8
9
  visibilityTimeout?: number;
9
10
  }
@@ -14,6 +14,7 @@ class HttpQueueServer {
14
14
  app = new hono_1.Hono();
15
15
  http;
16
16
  queue;
17
+ queueEnabled;
17
18
  dispose;
18
19
  constructor(options = {}) {
19
20
  this.logger = options.logger ?? unnbound_logger_sdk_1.logger;
@@ -33,12 +34,14 @@ class HttpQueueServer {
33
34
  return (0, endpoint_1.respondWith)(c, error.toJson());
34
35
  });
35
36
  this.http = new http_adapter_1.HttpAdapter({ app: this.app, logger: this.logger, ...options.http });
36
- // SQS adapter for async message processing
37
- this.queue = new queue_adapter_1.QueueAdapter({
38
- app: this.app,
39
- logger: this.logger,
40
- options: options.queue,
41
- });
37
+ this.queueEnabled = shouldEnableQueue(options.queue);
38
+ this.queue = this.queueEnabled
39
+ ? new queue_adapter_1.QueueAdapter({
40
+ app: this.app,
41
+ logger: this.logger,
42
+ options: options.queue,
43
+ })
44
+ : null;
42
45
  }
43
46
  get = (...args) => {
44
47
  // @ts-expect-error
@@ -73,17 +76,24 @@ class HttpQueueServer {
73
76
  process.on('SIGINT', createOnSignal('SIGINT'));
74
77
  process.on('SIGTERM', createOnSignal('SIGTERM'));
75
78
  this.logger.debug({}, 'Starting server...');
79
+ const disposables = [];
76
80
  try {
77
- const disposables = await Promise.all([this.http.listen(), this.queue.listen()]);
78
- this.dispose = async () => {
79
- await Promise.all(disposables.map((dispose) => dispose()))
80
- .then(() => void 0)
81
- .catch(console.error);
82
- };
81
+ disposables.push(await this.http.listen());
82
+ if (this.queue && this.queueEnabled) {
83
+ disposables.push(await this.queue.listen());
84
+ }
85
+ else {
86
+ this.logger.info({
87
+ runtimeTarget: process.env.UNNBOUND_RUNTIME_TARGET ?? 'default',
88
+ queueConfigured: Boolean(process.env.UNNBOUND_SQS_URL),
89
+ }, 'Queue listener disabled for this runtime.');
90
+ }
91
+ this.dispose = () => this.stopDisposables(disposables);
83
92
  this.logger.info('Server started.');
84
93
  return this.dispose;
85
94
  }
86
95
  catch (error) {
96
+ await this.stopDisposables(disposables);
87
97
  this.logger.error({ err: error }, 'Server failed to start.');
88
98
  throw error;
89
99
  }
@@ -95,6 +105,20 @@ class HttpQueueServer {
95
105
  await this.dispose();
96
106
  this.logger.debug({}, 'Server stopped.');
97
107
  }
108
+ async stopDisposables(disposables) {
109
+ const results = await Promise.allSettled(disposables.map((dispose) => dispose()));
110
+ for (const result of results) {
111
+ if (result.status === 'rejected') {
112
+ this.logger.error({ err: result.reason }, 'Server disposable failed to stop.');
113
+ }
114
+ }
115
+ }
98
116
  }
99
117
  const createServer = (options = {}) => new HttpQueueServer(options);
100
118
  exports.createServer = createServer;
119
+ function shouldEnableQueue(options) {
120
+ if (options?.enabled !== undefined) {
121
+ return options.enabled;
122
+ }
123
+ return Boolean(process.env.UNNBOUND_SQS_URL);
124
+ }
package/package.json CHANGED
@@ -1,21 +1,9 @@
1
1
  {
2
2
  "name": "unnbound-events",
3
3
  "description": "Unified events SDK to handle HTTP routes and queued messages with a single routing API.",
4
- "version": "2.0.19",
4
+ "version": "2.0.20",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
- "scripts": {
8
- "build": "bun x tsc",
9
- "test": "echo 'No tests'",
10
- "typecheck": "tsc -noEmit",
11
- "lint": "eslint --cache --cache-location ./node_modules/.cache/eslint .",
12
- "lint:fix": "bun run lint --fix",
13
- "format": "biome format --write .",
14
- "format:check": "biome format .",
15
- "prepublishOnly": "bun run build",
16
- "start:example": "bun --watch examples/node-server.ts",
17
- "version:bump": "npm version patch"
18
- },
19
7
  "author": "Unnbound Team",
20
8
  "license": "MIT",
21
9
  "repository": {
@@ -30,15 +18,15 @@
30
18
  "@aws-sdk/client-s3": "^3.0.0",
31
19
  "@aws-sdk/client-sqs": "^3.0.0",
32
20
  "@hono/node-server": "^1.19.11",
33
- "axios": "^1.12.2",
34
- "hono": "^4.12.7",
35
- "ioredis": "^5.4.1",
21
+ "axios": "1.16.0",
22
+ "hono": "^4.12.21",
36
23
  "unnbound-logger-sdk": "^3.0.34"
37
24
  },
38
25
  "devDependencies": {
39
26
  "@types/jest": "^29.5.12",
40
- "@types/node": "^22",
41
- "typescript": "^5.8.3"
27
+ "@types/node": "^24.12.2",
28
+ "typescript": "^5.8.3",
29
+ "vitest": "^4.0.15"
42
30
  },
43
31
  "files": [
44
32
  "dist/**/*",
@@ -48,5 +36,14 @@
48
36
  "engines": {
49
37
  "node": ">=22"
50
38
  },
51
- "sideEffects": false
52
- }
39
+ "sideEffects": false,
40
+ "scripts": {
41
+ "build": "tsc",
42
+ "test": "vitest run",
43
+ "typecheck": "tsgo --noEmit",
44
+ "format": "biome format --write .",
45
+ "format:check": "biome format .",
46
+ "start:example": "tsx watch examples/node-server.ts",
47
+ "version:bump": "npm version patch"
48
+ }
49
+ }