@chronos.sh/sdk 0.0.2-canary.fa8e22c → 0.1.0

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.cts CHANGED
@@ -6,8 +6,10 @@
6
6
  type ChronosHandlerResult = Record<string, unknown> | void;
7
7
  /**
8
8
  * Custom fetch implementation. Compatible with `globalThis.fetch`. Pass via
9
- * {@link ChronosOptions.fetch} to inject middleware (logging, tracing, custom
10
- * timeouts) or run in environments without a global `fetch`.
9
+ * {@link ChronosOptions.fetch} to inject middleware (logging, tracing) or run
10
+ * in environments without a global `fetch`. Implementations must forward
11
+ * `init.signal` to the underlying transport — dropping it disables the SDK's
12
+ * transport timeout and graceful abort.
11
13
  */
12
14
  type FetchLike = (input: string | URL, init?: RequestInit) => Promise<Response>;
13
15
  /**
@@ -39,6 +41,15 @@ type ChronosOptions = {
39
41
  * 20 inclusive. Defaults to 20 (the API maximum).
40
42
  */
41
43
  pollWaitTimeSeconds?: number;
44
+ /**
45
+ * Per-request transport timeout in milliseconds, applied to every HTTP
46
+ * request the SDK makes. Requests exceeding it fail with
47
+ * `ChronosTimeoutError`. Long-poll claim requests get
48
+ * `pollWaitTimeSeconds * 1000` of extra headroom on top, so the
49
+ * intentional poll wait never counts against this bound. Must be a
50
+ * positive integer no greater than 2147483647. Defaults to 30000.
51
+ */
52
+ timeoutMs?: number;
42
53
  /**
43
54
  * Worker retry delay in milliseconds. Used between failed result-report
44
55
  * attempts and between poll-loop iterations after a claim error. Defaults
@@ -100,14 +111,21 @@ type WebhookOptions = {
100
111
  //#region src/client.d.ts
101
112
  /** Default Chronos API base URL. */
102
113
  declare const DEFAULT_BASE_URL = "https://api.chronos.sh";
114
+ /** Default per-request transport timeout in milliseconds. */
115
+ declare const DEFAULT_TIMEOUT_MS = 30000;
116
+ type RequestOptions = {
117
+ signal?: AbortSignal;
118
+ timeoutMs?: number;
119
+ };
103
120
  declare class BaseClient {
104
121
  readonly apiKey: string;
105
122
  readonly baseUrl: string;
106
123
  readonly fetch: FetchLike;
107
124
  readonly logger: ChronosLogger;
125
+ readonly timeoutMs: number;
108
126
  private readonly headers;
109
127
  constructor(options: ChronosOptions);
110
- request<T>(path: string, body: Record<string, unknown>, signal?: AbortSignal): Promise<T>;
128
+ request<T>(path: string, body: Record<string, unknown>, options?: RequestOptions): Promise<T>;
111
129
  }
112
130
  //#endregion
113
131
  //#region src/worker.d.ts
@@ -162,8 +180,11 @@ declare class Worker {
162
180
  * in-flight handler that has not exceeded its timeout, along with the
163
181
  * subsequent result-report, is allowed to complete. Timed-out handlers
164
182
  * are abandoned (their {@link ChronosContext.signal} fires, but the SDK
165
- * does not wait for them). Returns the same promise as the active
166
- * {@link Worker.start}, or a resolved promise if the worker isn't running.
183
+ * does not wait for them). Every HTTP request is bounded by the `timeoutMs`
184
+ * transport timeout and retry waits are skipped once stop is requested, so
185
+ * the returned promise resolves in bounded time. Returns the same promise
186
+ * as the active {@link Worker.start}, or a resolved promise if the worker
187
+ * isn't running.
167
188
  */
168
189
  stop(): Promise<void>;
169
190
  private get isStopped();
@@ -238,7 +259,7 @@ type ChronosApiErrorOptions = {
238
259
  /** HTTP status code returned by the Chronos API. */status: number; /** Application-level error code from the response envelope, when present. */
239
260
  code?: string; /** Full parsed response payload (envelope + data, or whatever the server returned). */
240
261
  body?: unknown; /** Value of the `X-Request-Id` response header, when present. Pair with server logs. */
241
- requestId?: string; /** Seconds to wait before retrying, from the `Retry-After` response header. Present on 429 responses. */
262
+ requestId?: string; /** Seconds to wait before retrying, from the `Retry-After` response header. Present on 429 and 5xx responses that include the header. */
242
263
  retryAfterSeconds?: number; /** Rate-limit budget from `X-RateLimit-*` headers. Present on non-429 responses. */
243
264
  rateLimit?: RateLimitInfo;
244
265
  };
@@ -247,7 +268,7 @@ type ChronosApiErrorOptions = {
247
268
  * `success: false` envelope. Carries HTTP `status`, the application
248
269
  * `code`, parsed `body`, the API's `X-Request-Id`, and — when the
249
270
  * relevant headers are present — rate-limit metadata
250
- * (`retryAfterSeconds` on 429, `rateLimit` on other errors).
271
+ * (`retryAfterSeconds` on 429 and 5xx, `rateLimit` on non-429 errors).
251
272
  *
252
273
  * `instanceof ChronosApiError` means the server replied;
253
274
  * network/transport failures throw {@link ChronosNetworkError} instead.
@@ -274,7 +295,7 @@ declare class ChronosApiError extends ChronosError {
274
295
  readonly body?: unknown;
275
296
  /** Value of the `X-Request-Id` response header, when present. */
276
297
  readonly requestId?: string;
277
- /** Seconds to wait before retrying, from the `Retry-After` response header. Present on 429 responses. */
298
+ /** Seconds to wait before retrying, from the `Retry-After` response header. Present on 429 and 5xx responses that include the header. */
278
299
  readonly retryAfterSeconds?: number;
279
300
  /** Rate-limit budget from `X-RateLimit-*` headers. Present on non-429 responses. */
280
301
  readonly rateLimit?: RateLimitInfo;
@@ -301,12 +322,14 @@ declare class ChronosRateLimitError extends ChronosApiError {
301
322
  constructor(message: string, options: ChronosApiErrorOptions);
302
323
  }
303
324
  /**
304
- * Thrown when the underlying `fetch` rejects before the server replies
305
- * DNS failure, TCP reset, connection refused, etc. The original error is
306
- * available on `.cause`.
325
+ * Thrown when a request fails at the transport layer DNS failure, TCP
326
+ * reset, connection refused, or a connection dropped mid-response. The
327
+ * original error is available on `.cause`.
307
328
  *
308
- * Abort signals propagate unwrapped — `instanceof ChronosNetworkError`
309
- * always means a real transport failure, not a graceful shutdown.
329
+ * Caller-supplied abort signals propagate unwrapped — `instanceof
330
+ * ChronosNetworkError` always means a transport failure, not a graceful
331
+ * shutdown. The SDK's own transport timeout throws the
332
+ * {@link ChronosTimeoutError} subclass.
310
333
  *
311
334
  * @example
312
335
  * ```ts
@@ -326,6 +349,28 @@ declare class ChronosNetworkError extends ChronosError {
326
349
  cause: unknown;
327
350
  });
328
351
  }
352
+ /**
353
+ * Thrown when an HTTP request exceeds the configured `timeoutMs` before the
354
+ * response completes. Subclass of {@link ChronosNetworkError}, so generic
355
+ * transport-failure handling catches it; the underlying rejection is on
356
+ * `.cause`.
357
+ *
358
+ * @example
359
+ * ```ts
360
+ * import { ChronosTimeoutError } from '@chronos.sh/sdk';
361
+ *
362
+ * function handleSdkError(err: unknown) {
363
+ * if (err instanceof ChronosTimeoutError) {
364
+ * console.warn('Chronos request timed out', err.message);
365
+ * }
366
+ * }
367
+ * ```
368
+ */
369
+ declare class ChronosTimeoutError extends ChronosNetworkError {
370
+ constructor(message: string, options: {
371
+ cause: unknown;
372
+ });
373
+ }
329
374
  /**
330
375
  * Wraps an exception thrown by a user-supplied {@link ChronosHandler}. The
331
376
  * original error is on `.cause`; `.message` is copied from the original so
@@ -443,5 +488,5 @@ declare class Chronos {
443
488
  constructor(options: ChronosOptions);
444
489
  }
445
490
  //#endregion
446
- export { Chronos, ChronosApiError, type ChronosApiErrorOptions, ChronosConfigError, type ChronosContext, ChronosError, type ChronosHandler, ChronosHandlerError, type ChronosHandlerResult, type ChronosLogger, ChronosNetworkError, type ChronosOptions, ChronosRateLimitError, type ChronosSchedule, ChronosWebhookVerificationError, DEFAULT_BASE_URL, DEFAULT_POLL_WAIT_TIME_SECONDS, DEFAULT_TIMESTAMP_TOLERANCE_SECONDS, type FetchLike, type RateLimitInfo, SDK_VERSION, Webhook, type WebhookOptions };
491
+ export { Chronos, ChronosApiError, type ChronosApiErrorOptions, ChronosConfigError, type ChronosContext, ChronosError, type ChronosHandler, ChronosHandlerError, type ChronosHandlerResult, type ChronosLogger, ChronosNetworkError, type ChronosOptions, ChronosRateLimitError, type ChronosSchedule, ChronosTimeoutError, ChronosWebhookVerificationError, DEFAULT_BASE_URL, DEFAULT_POLL_WAIT_TIME_SECONDS, DEFAULT_TIMEOUT_MS, DEFAULT_TIMESTAMP_TOLERANCE_SECONDS, type FetchLike, type RateLimitInfo, SDK_VERSION, Webhook, type WebhookOptions };
447
492
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.ts CHANGED
@@ -6,8 +6,10 @@
6
6
  type ChronosHandlerResult = Record<string, unknown> | void;
7
7
  /**
8
8
  * Custom fetch implementation. Compatible with `globalThis.fetch`. Pass via
9
- * {@link ChronosOptions.fetch} to inject middleware (logging, tracing, custom
10
- * timeouts) or run in environments without a global `fetch`.
9
+ * {@link ChronosOptions.fetch} to inject middleware (logging, tracing) or run
10
+ * in environments without a global `fetch`. Implementations must forward
11
+ * `init.signal` to the underlying transport — dropping it disables the SDK's
12
+ * transport timeout and graceful abort.
11
13
  */
12
14
  type FetchLike = (input: string | URL, init?: RequestInit) => Promise<Response>;
13
15
  /**
@@ -39,6 +41,15 @@ type ChronosOptions = {
39
41
  * 20 inclusive. Defaults to 20 (the API maximum).
40
42
  */
41
43
  pollWaitTimeSeconds?: number;
44
+ /**
45
+ * Per-request transport timeout in milliseconds, applied to every HTTP
46
+ * request the SDK makes. Requests exceeding it fail with
47
+ * `ChronosTimeoutError`. Long-poll claim requests get
48
+ * `pollWaitTimeSeconds * 1000` of extra headroom on top, so the
49
+ * intentional poll wait never counts against this bound. Must be a
50
+ * positive integer no greater than 2147483647. Defaults to 30000.
51
+ */
52
+ timeoutMs?: number;
42
53
  /**
43
54
  * Worker retry delay in milliseconds. Used between failed result-report
44
55
  * attempts and between poll-loop iterations after a claim error. Defaults
@@ -100,14 +111,21 @@ type WebhookOptions = {
100
111
  //#region src/client.d.ts
101
112
  /** Default Chronos API base URL. */
102
113
  declare const DEFAULT_BASE_URL = "https://api.chronos.sh";
114
+ /** Default per-request transport timeout in milliseconds. */
115
+ declare const DEFAULT_TIMEOUT_MS = 30000;
116
+ type RequestOptions = {
117
+ signal?: AbortSignal;
118
+ timeoutMs?: number;
119
+ };
103
120
  declare class BaseClient {
104
121
  readonly apiKey: string;
105
122
  readonly baseUrl: string;
106
123
  readonly fetch: FetchLike;
107
124
  readonly logger: ChronosLogger;
125
+ readonly timeoutMs: number;
108
126
  private readonly headers;
109
127
  constructor(options: ChronosOptions);
110
- request<T>(path: string, body: Record<string, unknown>, signal?: AbortSignal): Promise<T>;
128
+ request<T>(path: string, body: Record<string, unknown>, options?: RequestOptions): Promise<T>;
111
129
  }
112
130
  //#endregion
113
131
  //#region src/worker.d.ts
@@ -162,8 +180,11 @@ declare class Worker {
162
180
  * in-flight handler that has not exceeded its timeout, along with the
163
181
  * subsequent result-report, is allowed to complete. Timed-out handlers
164
182
  * are abandoned (their {@link ChronosContext.signal} fires, but the SDK
165
- * does not wait for them). Returns the same promise as the active
166
- * {@link Worker.start}, or a resolved promise if the worker isn't running.
183
+ * does not wait for them). Every HTTP request is bounded by the `timeoutMs`
184
+ * transport timeout and retry waits are skipped once stop is requested, so
185
+ * the returned promise resolves in bounded time. Returns the same promise
186
+ * as the active {@link Worker.start}, or a resolved promise if the worker
187
+ * isn't running.
167
188
  */
168
189
  stop(): Promise<void>;
169
190
  private get isStopped();
@@ -238,7 +259,7 @@ type ChronosApiErrorOptions = {
238
259
  /** HTTP status code returned by the Chronos API. */status: number; /** Application-level error code from the response envelope, when present. */
239
260
  code?: string; /** Full parsed response payload (envelope + data, or whatever the server returned). */
240
261
  body?: unknown; /** Value of the `X-Request-Id` response header, when present. Pair with server logs. */
241
- requestId?: string; /** Seconds to wait before retrying, from the `Retry-After` response header. Present on 429 responses. */
262
+ requestId?: string; /** Seconds to wait before retrying, from the `Retry-After` response header. Present on 429 and 5xx responses that include the header. */
242
263
  retryAfterSeconds?: number; /** Rate-limit budget from `X-RateLimit-*` headers. Present on non-429 responses. */
243
264
  rateLimit?: RateLimitInfo;
244
265
  };
@@ -247,7 +268,7 @@ type ChronosApiErrorOptions = {
247
268
  * `success: false` envelope. Carries HTTP `status`, the application
248
269
  * `code`, parsed `body`, the API's `X-Request-Id`, and — when the
249
270
  * relevant headers are present — rate-limit metadata
250
- * (`retryAfterSeconds` on 429, `rateLimit` on other errors).
271
+ * (`retryAfterSeconds` on 429 and 5xx, `rateLimit` on non-429 errors).
251
272
  *
252
273
  * `instanceof ChronosApiError` means the server replied;
253
274
  * network/transport failures throw {@link ChronosNetworkError} instead.
@@ -274,7 +295,7 @@ declare class ChronosApiError extends ChronosError {
274
295
  readonly body?: unknown;
275
296
  /** Value of the `X-Request-Id` response header, when present. */
276
297
  readonly requestId?: string;
277
- /** Seconds to wait before retrying, from the `Retry-After` response header. Present on 429 responses. */
298
+ /** Seconds to wait before retrying, from the `Retry-After` response header. Present on 429 and 5xx responses that include the header. */
278
299
  readonly retryAfterSeconds?: number;
279
300
  /** Rate-limit budget from `X-RateLimit-*` headers. Present on non-429 responses. */
280
301
  readonly rateLimit?: RateLimitInfo;
@@ -301,12 +322,14 @@ declare class ChronosRateLimitError extends ChronosApiError {
301
322
  constructor(message: string, options: ChronosApiErrorOptions);
302
323
  }
303
324
  /**
304
- * Thrown when the underlying `fetch` rejects before the server replies
305
- * DNS failure, TCP reset, connection refused, etc. The original error is
306
- * available on `.cause`.
325
+ * Thrown when a request fails at the transport layer DNS failure, TCP
326
+ * reset, connection refused, or a connection dropped mid-response. The
327
+ * original error is available on `.cause`.
307
328
  *
308
- * Abort signals propagate unwrapped — `instanceof ChronosNetworkError`
309
- * always means a real transport failure, not a graceful shutdown.
329
+ * Caller-supplied abort signals propagate unwrapped — `instanceof
330
+ * ChronosNetworkError` always means a transport failure, not a graceful
331
+ * shutdown. The SDK's own transport timeout throws the
332
+ * {@link ChronosTimeoutError} subclass.
310
333
  *
311
334
  * @example
312
335
  * ```ts
@@ -326,6 +349,28 @@ declare class ChronosNetworkError extends ChronosError {
326
349
  cause: unknown;
327
350
  });
328
351
  }
352
+ /**
353
+ * Thrown when an HTTP request exceeds the configured `timeoutMs` before the
354
+ * response completes. Subclass of {@link ChronosNetworkError}, so generic
355
+ * transport-failure handling catches it; the underlying rejection is on
356
+ * `.cause`.
357
+ *
358
+ * @example
359
+ * ```ts
360
+ * import { ChronosTimeoutError } from '@chronos.sh/sdk';
361
+ *
362
+ * function handleSdkError(err: unknown) {
363
+ * if (err instanceof ChronosTimeoutError) {
364
+ * console.warn('Chronos request timed out', err.message);
365
+ * }
366
+ * }
367
+ * ```
368
+ */
369
+ declare class ChronosTimeoutError extends ChronosNetworkError {
370
+ constructor(message: string, options: {
371
+ cause: unknown;
372
+ });
373
+ }
329
374
  /**
330
375
  * Wraps an exception thrown by a user-supplied {@link ChronosHandler}. The
331
376
  * original error is on `.cause`; `.message` is copied from the original so
@@ -443,5 +488,5 @@ declare class Chronos {
443
488
  constructor(options: ChronosOptions);
444
489
  }
445
490
  //#endregion
446
- export { Chronos, ChronosApiError, type ChronosApiErrorOptions, ChronosConfigError, type ChronosContext, ChronosError, type ChronosHandler, ChronosHandlerError, type ChronosHandlerResult, type ChronosLogger, ChronosNetworkError, type ChronosOptions, ChronosRateLimitError, type ChronosSchedule, ChronosWebhookVerificationError, DEFAULT_BASE_URL, DEFAULT_POLL_WAIT_TIME_SECONDS, DEFAULT_TIMESTAMP_TOLERANCE_SECONDS, type FetchLike, type RateLimitInfo, SDK_VERSION, Webhook, type WebhookOptions };
491
+ export { Chronos, ChronosApiError, type ChronosApiErrorOptions, ChronosConfigError, type ChronosContext, ChronosError, type ChronosHandler, ChronosHandlerError, type ChronosHandlerResult, type ChronosLogger, ChronosNetworkError, type ChronosOptions, ChronosRateLimitError, type ChronosSchedule, ChronosTimeoutError, ChronosWebhookVerificationError, DEFAULT_BASE_URL, DEFAULT_POLL_WAIT_TIME_SECONDS, DEFAULT_TIMEOUT_MS, DEFAULT_TIMESTAMP_TOLERANCE_SECONDS, type FetchLike, type RateLimitInfo, SDK_VERSION, Webhook, type WebhookOptions };
447
492
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -57,7 +57,7 @@ var ChronosConfigError = class extends ChronosError {
57
57
  * `success: false` envelope. Carries HTTP `status`, the application
58
58
  * `code`, parsed `body`, the API's `X-Request-Id`, and — when the
59
59
  * relevant headers are present — rate-limit metadata
60
- * (`retryAfterSeconds` on 429, `rateLimit` on other errors).
60
+ * (`retryAfterSeconds` on 429 and 5xx, `rateLimit` on non-429 errors).
61
61
  *
62
62
  * `instanceof ChronosApiError` means the server replied;
63
63
  * network/transport failures throw {@link ChronosNetworkError} instead.
@@ -84,7 +84,7 @@ var ChronosApiError = class extends ChronosError {
84
84
  body;
85
85
  /** Value of the `X-Request-Id` response header, when present. */
86
86
  requestId;
87
- /** Seconds to wait before retrying, from the `Retry-After` response header. Present on 429 responses. */
87
+ /** Seconds to wait before retrying, from the `Retry-After` response header. Present on 429 and 5xx responses that include the header. */
88
88
  retryAfterSeconds;
89
89
  /** Rate-limit budget from `X-RateLimit-*` headers. Present on non-429 responses. */
90
90
  rateLimit;
@@ -123,12 +123,14 @@ var ChronosRateLimitError = class extends ChronosApiError {
123
123
  }
124
124
  };
125
125
  /**
126
- * Thrown when the underlying `fetch` rejects before the server replies
127
- * DNS failure, TCP reset, connection refused, etc. The original error is
128
- * available on `.cause`.
126
+ * Thrown when a request fails at the transport layer DNS failure, TCP
127
+ * reset, connection refused, or a connection dropped mid-response. The
128
+ * original error is available on `.cause`.
129
129
  *
130
- * Abort signals propagate unwrapped — `instanceof ChronosNetworkError`
131
- * always means a real transport failure, not a graceful shutdown.
130
+ * Caller-supplied abort signals propagate unwrapped — `instanceof
131
+ * ChronosNetworkError` always means a transport failure, not a graceful
132
+ * shutdown. The SDK's own transport timeout throws the
133
+ * {@link ChronosTimeoutError} subclass.
132
134
  *
133
135
  * @example
134
136
  * ```ts
@@ -148,6 +150,29 @@ var ChronosNetworkError = class extends ChronosError {
148
150
  }
149
151
  };
150
152
  /**
153
+ * Thrown when an HTTP request exceeds the configured `timeoutMs` before the
154
+ * response completes. Subclass of {@link ChronosNetworkError}, so generic
155
+ * transport-failure handling catches it; the underlying rejection is on
156
+ * `.cause`.
157
+ *
158
+ * @example
159
+ * ```ts
160
+ * import { ChronosTimeoutError } from '@chronos.sh/sdk';
161
+ *
162
+ * function handleSdkError(err: unknown) {
163
+ * if (err instanceof ChronosTimeoutError) {
164
+ * console.warn('Chronos request timed out', err.message);
165
+ * }
166
+ * }
167
+ * ```
168
+ */
169
+ var ChronosTimeoutError = class extends ChronosNetworkError {
170
+ constructor(message, options) {
171
+ super(message, options);
172
+ this.name = "ChronosTimeoutError";
173
+ }
174
+ };
175
+ /**
151
176
  * Wraps an exception thrown by a user-supplied {@link ChronosHandler}. The
152
177
  * original error is on `.cause`; `.message` is copied from the original so
153
178
  * the SDK reports it to the API as the failure reason.
@@ -215,6 +240,10 @@ function validatePollWaitTime(seconds) {
215
240
  function validateRetryDelayMs(ms) {
216
241
  if (!Number.isFinite(ms) || ms < 0) throw new ChronosConfigError("retryDelayMs must be a non-negative number");
217
242
  }
243
+ const MAX_TIMEOUT_MS = 2147483647;
244
+ function validateTimeoutMs(ms) {
245
+ if (!Number.isInteger(ms) || ms <= 0 || ms > 2147483647) throw new ChronosConfigError(`timeoutMs must be a positive integer no greater than ${MAX_TIMEOUT_MS}`);
246
+ }
218
247
  function validateWorkerId(id) {
219
248
  const trimmed = id.trim();
220
249
  if (trimmed.length === 0 || trimmed.length > 255) throw new ChronosConfigError("workerId must be 1-255 characters");
@@ -230,64 +259,92 @@ function normalizeHandlerName(name) {
230
259
  //#region src/client.ts
231
260
  /** Default Chronos API base URL. */
232
261
  const DEFAULT_BASE_URL = "https://api.chronos.sh";
262
+ /** Default per-request transport timeout in milliseconds. */
263
+ const DEFAULT_TIMEOUT_MS = 3e4;
233
264
  var BaseClient = class {
234
265
  apiKey;
235
266
  baseUrl;
236
267
  fetch;
237
268
  logger;
269
+ timeoutMs;
238
270
  headers;
239
271
  constructor(options) {
240
272
  this.apiKey = validateApiKey(options.apiKey);
241
273
  this.baseUrl = normalizeBaseUrl(options.baseUrl ?? "https://api.chronos.sh");
242
274
  this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
243
275
  this.logger = options.logger ?? defaultLogger;
276
+ this.timeoutMs = options.timeoutMs ?? 3e4;
277
+ validateTimeoutMs(this.timeoutMs);
244
278
  this.headers = {
245
279
  "content-type": "application/json",
246
280
  authorization: `Bearer ${this.apiKey}`
247
281
  };
248
282
  }
249
- async request(path, body, signal) {
250
- let response;
283
+ async request(path, body, options = {}) {
284
+ const { signal } = options;
285
+ const timeoutMs = options.timeoutMs ?? this.timeoutMs;
286
+ const controller = new AbortController();
287
+ let timedOut = false;
288
+ const timer = setTimeout(() => {
289
+ if (controller.signal.aborted) return;
290
+ timedOut = true;
291
+ controller.abort(new DOMException(`Chronos API request timed out after ${timeoutMs}ms`, "TimeoutError"));
292
+ }, timeoutMs);
293
+ const onCallerAbort = () => controller.abort(signal?.reason);
294
+ if (signal?.aborted) onCallerAbort();
295
+ else signal?.addEventListener("abort", onCallerAbort, { once: true });
251
296
  try {
252
- response = await this.fetch(`${this.baseUrl}${path}`, {
253
- method: "POST",
254
- headers: this.headers,
255
- body: JSON.stringify(body),
256
- signal
257
- });
258
- } catch (err) {
259
- if (signal?.aborted) throw err;
260
- throw new ChronosNetworkError(`Chronos API request failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
261
- }
262
- const payload = await parseJsonResponse(response);
263
- const envelope = isEnvelope(payload) ? payload : void 0;
264
- if (!response.ok || envelope && !envelope.success) {
265
- const is429 = response.status === 429;
266
- throw new (is429 ? ChronosRateLimitError : ChronosApiError)(apiErrorMessage(response, envelope), {
267
- status: response.status,
268
- code: typeof envelope?.code === "string" ? envelope.code : void 0,
269
- body: payload,
270
- requestId: response.headers.get("x-request-id") ?? void 0,
271
- retryAfterSeconds: is429 ? parseIntHeader(response.headers, "retry-after") : void 0,
272
- rateLimit: is429 ? void 0 : parseRateLimitHeaders(response.headers)
273
- });
297
+ let response;
298
+ try {
299
+ response = await this.fetch(`${this.baseUrl}${path}`, {
300
+ method: "POST",
301
+ headers: this.headers,
302
+ body: JSON.stringify(body),
303
+ signal: controller.signal
304
+ });
305
+ } catch (err) {
306
+ if (signal?.aborted && !timedOut) throw err;
307
+ if (timedOut) throw timeoutError(timeoutMs, err);
308
+ throw new ChronosNetworkError(`Chronos API request failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
309
+ }
310
+ let payload;
311
+ try {
312
+ payload = await response.json();
313
+ } catch (err) {
314
+ if (signal?.aborted && !timedOut) throw err;
315
+ if (timedOut) throw timeoutError(timeoutMs, err);
316
+ if (err instanceof SyntaxError) payload = null;
317
+ else throw new ChronosNetworkError(`Chronos API response failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
318
+ }
319
+ const envelope = isEnvelope(payload) ? payload : void 0;
320
+ if (!response.ok || envelope && !envelope.success) {
321
+ const is429 = response.status === 429;
322
+ const retryAfterApplies = is429 || response.status >= 500;
323
+ throw new (is429 ? ChronosRateLimitError : ChronosApiError)(apiErrorMessage(response, envelope), {
324
+ status: response.status,
325
+ code: typeof envelope?.code === "string" ? envelope.code : void 0,
326
+ body: payload,
327
+ requestId: response.headers.get("x-request-id") ?? void 0,
328
+ retryAfterSeconds: retryAfterApplies ? parseIntHeader(response.headers, "retry-after") : void 0,
329
+ rateLimit: is429 ? void 0 : parseRateLimitHeaders(response.headers)
330
+ });
331
+ }
332
+ if (!envelope) throw new ChronosError("Chronos API returned an invalid response");
333
+ return envelope.data;
334
+ } finally {
335
+ clearTimeout(timer);
336
+ signal?.removeEventListener("abort", onCallerAbort);
274
337
  }
275
- if (!envelope) throw new ChronosError("Chronos API returned an invalid response");
276
- return envelope.data;
277
338
  }
278
339
  };
340
+ function timeoutError(timeoutMs, cause) {
341
+ return new ChronosTimeoutError(`Chronos API request timed out after ${timeoutMs}ms`, { cause });
342
+ }
279
343
  function normalizeBaseUrl(baseUrl) {
280
344
  const normalized = baseUrl.trim().replace(/\/+$/, "");
281
345
  if (!normalized) throw new ChronosConfigError("Chronos baseUrl is required");
282
346
  return normalized;
283
347
  }
284
- async function parseJsonResponse(response) {
285
- try {
286
- return await response.json();
287
- } catch {
288
- return null;
289
- }
290
- }
291
348
  function apiErrorMessage(response, envelope) {
292
349
  if (typeof envelope?.message === "string" && envelope.message.trim()) return envelope.message;
293
350
  return response.ok ? "Chronos API returned an invalid response" : `Chronos API request failed with status ${response.status}`;
@@ -315,7 +372,7 @@ function isEnvelope(value) {
315
372
  //#endregion
316
373
  //#region src/internal/id.ts
317
374
  function generateWorkerId() {
318
- const bytes = crypto.getRandomValues(new Uint8Array(8));
375
+ const bytes = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(8));
319
376
  let hex = "";
320
377
  for (const b of bytes) hex += b.toString(16).padStart(2, "0");
321
378
  return `w_${hex}`;
@@ -331,12 +388,13 @@ function detectRuntime() {
331
388
  }
332
389
  //#endregion
333
390
  //#region src/internal/version.ts
334
- const SDK_VERSION = "0.0.2-canary.fa8e22c";
391
+ const SDK_VERSION = "0.1.0";
335
392
  //#endregion
336
393
  //#region src/worker.ts
337
394
  /** Default worker long-poll wait time in seconds. Equal to the API maximum. */
338
395
  const DEFAULT_POLL_WAIT_TIME_SECONDS = 20;
339
396
  const DEFAULT_RETRY_DELAY_MS = 1e3;
397
+ const MAX_RETRY_AFTER_MS = 6e4;
340
398
  const RESULT_REPORT_MAX_ATTEMPTS = 3;
341
399
  const MAX_REPORTED_ERROR_LENGTH = 4096;
342
400
  const DEFAULT_HANDLER_ERROR = "Chronos handler failed";
@@ -414,8 +472,11 @@ var Worker = class {
414
472
  * in-flight handler that has not exceeded its timeout, along with the
415
473
  * subsequent result-report, is allowed to complete. Timed-out handlers
416
474
  * are abandoned (their {@link ChronosContext.signal} fires, but the SDK
417
- * does not wait for them). Returns the same promise as the active
418
- * {@link Worker.start}, or a resolved promise if the worker isn't running.
475
+ * does not wait for them). Every HTTP request is bounded by the `timeoutMs`
476
+ * transport timeout and retry waits are skipped once stop is requested, so
477
+ * the returned promise resolves in bounded time. Returns the same promise
478
+ * as the active {@link Worker.start}, or a resolved promise if the worker
479
+ * isn't running.
419
480
  */
420
481
  stop() {
421
482
  this.pollController?.abort();
@@ -436,7 +497,7 @@ var Worker = class {
436
497
  }
437
498
  async claimJob() {
438
499
  if (this.handlerNames.length === 0) throw new ChronosError("Cannot claim jobs without registered handlers");
439
- return this.client.request("/v1/worker/jobs/claim", {
500
+ return this.client.request("/v1/workers/jobs/claim", {
440
501
  wait_time_seconds: this.pollWaitTimeSeconds,
441
502
  handlers: this.handlerNames,
442
503
  worker: {
@@ -445,7 +506,10 @@ var Worker = class {
445
506
  runtime: this.runtime,
446
507
  uptime_ms: Math.round(performance.now() - this.startedAt)
447
508
  }
448
- }, this.pollController?.signal);
509
+ }, {
510
+ signal: this.pollController?.signal,
511
+ timeoutMs: Math.min(this.pollWaitTimeSeconds * 1e3 + this.client.timeoutMs, MAX_TIMEOUT_MS)
512
+ });
449
513
  }
450
514
  async processJob(job) {
451
515
  const handler = this.handlers.get(job.handler);
@@ -540,7 +604,7 @@ var Worker = class {
540
604
  async reportResultWithRetry(executionId, body) {
541
605
  let lastErr;
542
606
  for (let attempt = 1; attempt <= RESULT_REPORT_MAX_ATTEMPTS; attempt++) try {
543
- await this.client.request(`/v1/worker/executions/${encodeURIComponent(executionId)}/result`, body);
607
+ await this.client.request(`/v1/workers/executions/${encodeURIComponent(executionId)}/result`, body);
544
608
  return;
545
609
  } catch (err) {
546
610
  const apiErr = err instanceof ChronosApiError ? err : void 0;
@@ -562,7 +626,7 @@ var Worker = class {
562
626
  attempt,
563
627
  maxAttempts: RESULT_REPORT_MAX_ATTEMPTS
564
628
  });
565
- await sleep(retryDelayFor(err, this.retryDelayMs));
629
+ await sleep(retryDelayFor(err, this.retryDelayMs), this.pollController?.signal);
566
630
  }
567
631
  throw lastErr;
568
632
  }
@@ -638,10 +702,10 @@ function truncate(value, maxLength) {
638
702
  return value.length <= maxLength ? value : value.slice(0, maxLength);
639
703
  }
640
704
  function retryDelayFor(err, fallbackMs) {
641
- if (err instanceof ChronosApiError && err.retryAfterSeconds !== void 0) return err.retryAfterSeconds * 1e3;
705
+ if (err instanceof ChronosApiError && err.retryAfterSeconds !== void 0) return Math.max(Math.min(err.retryAfterSeconds * 1e3, MAX_RETRY_AFTER_MS), fallbackMs);
642
706
  return fallbackMs;
643
707
  }
644
- const NON_TERMINAL_4XX = new Set([
708
+ const NON_TERMINAL_4XX = /* @__PURE__ */ new Set([
645
709
  408,
646
710
  409,
647
711
  429
@@ -814,6 +878,6 @@ var Chronos = class {
814
878
  }
815
879
  };
816
880
  //#endregion
817
- export { Chronos, ChronosApiError, ChronosConfigError, ChronosError, ChronosHandlerError, ChronosNetworkError, ChronosRateLimitError, ChronosWebhookVerificationError, DEFAULT_BASE_URL, DEFAULT_POLL_WAIT_TIME_SECONDS, DEFAULT_TIMESTAMP_TOLERANCE_SECONDS, SDK_VERSION, Webhook };
881
+ export { Chronos, ChronosApiError, ChronosConfigError, ChronosError, ChronosHandlerError, ChronosNetworkError, ChronosRateLimitError, ChronosTimeoutError, ChronosWebhookVerificationError, DEFAULT_BASE_URL, DEFAULT_POLL_WAIT_TIME_SECONDS, DEFAULT_TIMEOUT_MS, DEFAULT_TIMESTAMP_TOLERANCE_SECONDS, SDK_VERSION, Webhook };
818
882
 
819
883
  //# sourceMappingURL=index.js.map