@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.cjs CHANGED
@@ -58,7 +58,7 @@ var ChronosConfigError = class extends ChronosError {
58
58
  * `success: false` envelope. Carries HTTP `status`, the application
59
59
  * `code`, parsed `body`, the API's `X-Request-Id`, and — when the
60
60
  * relevant headers are present — rate-limit metadata
61
- * (`retryAfterSeconds` on 429, `rateLimit` on other errors).
61
+ * (`retryAfterSeconds` on 429 and 5xx, `rateLimit` on non-429 errors).
62
62
  *
63
63
  * `instanceof ChronosApiError` means the server replied;
64
64
  * network/transport failures throw {@link ChronosNetworkError} instead.
@@ -85,7 +85,7 @@ var ChronosApiError = class extends ChronosError {
85
85
  body;
86
86
  /** Value of the `X-Request-Id` response header, when present. */
87
87
  requestId;
88
- /** Seconds to wait before retrying, from the `Retry-After` response header. Present on 429 responses. */
88
+ /** Seconds to wait before retrying, from the `Retry-After` response header. Present on 429 and 5xx responses that include the header. */
89
89
  retryAfterSeconds;
90
90
  /** Rate-limit budget from `X-RateLimit-*` headers. Present on non-429 responses. */
91
91
  rateLimit;
@@ -124,12 +124,14 @@ var ChronosRateLimitError = class extends ChronosApiError {
124
124
  }
125
125
  };
126
126
  /**
127
- * Thrown when the underlying `fetch` rejects before the server replies
128
- * DNS failure, TCP reset, connection refused, etc. The original error is
129
- * available on `.cause`.
127
+ * Thrown when a request fails at the transport layer DNS failure, TCP
128
+ * reset, connection refused, or a connection dropped mid-response. The
129
+ * original error is available on `.cause`.
130
130
  *
131
- * Abort signals propagate unwrapped — `instanceof ChronosNetworkError`
132
- * always means a real transport failure, not a graceful shutdown.
131
+ * Caller-supplied abort signals propagate unwrapped — `instanceof
132
+ * ChronosNetworkError` always means a transport failure, not a graceful
133
+ * shutdown. The SDK's own transport timeout throws the
134
+ * {@link ChronosTimeoutError} subclass.
133
135
  *
134
136
  * @example
135
137
  * ```ts
@@ -149,6 +151,29 @@ var ChronosNetworkError = class extends ChronosError {
149
151
  }
150
152
  };
151
153
  /**
154
+ * Thrown when an HTTP request exceeds the configured `timeoutMs` before the
155
+ * response completes. Subclass of {@link ChronosNetworkError}, so generic
156
+ * transport-failure handling catches it; the underlying rejection is on
157
+ * `.cause`.
158
+ *
159
+ * @example
160
+ * ```ts
161
+ * import { ChronosTimeoutError } from '@chronos.sh/sdk';
162
+ *
163
+ * function handleSdkError(err: unknown) {
164
+ * if (err instanceof ChronosTimeoutError) {
165
+ * console.warn('Chronos request timed out', err.message);
166
+ * }
167
+ * }
168
+ * ```
169
+ */
170
+ var ChronosTimeoutError = class extends ChronosNetworkError {
171
+ constructor(message, options) {
172
+ super(message, options);
173
+ this.name = "ChronosTimeoutError";
174
+ }
175
+ };
176
+ /**
152
177
  * Wraps an exception thrown by a user-supplied {@link ChronosHandler}. The
153
178
  * original error is on `.cause`; `.message` is copied from the original so
154
179
  * the SDK reports it to the API as the failure reason.
@@ -216,6 +241,10 @@ function validatePollWaitTime(seconds) {
216
241
  function validateRetryDelayMs(ms) {
217
242
  if (!Number.isFinite(ms) || ms < 0) throw new ChronosConfigError("retryDelayMs must be a non-negative number");
218
243
  }
244
+ const MAX_TIMEOUT_MS = 2147483647;
245
+ function validateTimeoutMs(ms) {
246
+ if (!Number.isInteger(ms) || ms <= 0 || ms > 2147483647) throw new ChronosConfigError(`timeoutMs must be a positive integer no greater than ${MAX_TIMEOUT_MS}`);
247
+ }
219
248
  function validateWorkerId(id) {
220
249
  const trimmed = id.trim();
221
250
  if (trimmed.length === 0 || trimmed.length > 255) throw new ChronosConfigError("workerId must be 1-255 characters");
@@ -231,64 +260,92 @@ function normalizeHandlerName(name) {
231
260
  //#region src/client.ts
232
261
  /** Default Chronos API base URL. */
233
262
  const DEFAULT_BASE_URL = "https://api.chronos.sh";
263
+ /** Default per-request transport timeout in milliseconds. */
264
+ const DEFAULT_TIMEOUT_MS = 3e4;
234
265
  var BaseClient = class {
235
266
  apiKey;
236
267
  baseUrl;
237
268
  fetch;
238
269
  logger;
270
+ timeoutMs;
239
271
  headers;
240
272
  constructor(options) {
241
273
  this.apiKey = validateApiKey(options.apiKey);
242
274
  this.baseUrl = normalizeBaseUrl(options.baseUrl ?? "https://api.chronos.sh");
243
275
  this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
244
276
  this.logger = options.logger ?? defaultLogger;
277
+ this.timeoutMs = options.timeoutMs ?? 3e4;
278
+ validateTimeoutMs(this.timeoutMs);
245
279
  this.headers = {
246
280
  "content-type": "application/json",
247
281
  authorization: `Bearer ${this.apiKey}`
248
282
  };
249
283
  }
250
- async request(path, body, signal) {
251
- let response;
284
+ async request(path, body, options = {}) {
285
+ const { signal } = options;
286
+ const timeoutMs = options.timeoutMs ?? this.timeoutMs;
287
+ const controller = new AbortController();
288
+ let timedOut = false;
289
+ const timer = setTimeout(() => {
290
+ if (controller.signal.aborted) return;
291
+ timedOut = true;
292
+ controller.abort(new DOMException(`Chronos API request timed out after ${timeoutMs}ms`, "TimeoutError"));
293
+ }, timeoutMs);
294
+ const onCallerAbort = () => controller.abort(signal?.reason);
295
+ if (signal?.aborted) onCallerAbort();
296
+ else signal?.addEventListener("abort", onCallerAbort, { once: true });
252
297
  try {
253
- response = await this.fetch(`${this.baseUrl}${path}`, {
254
- method: "POST",
255
- headers: this.headers,
256
- body: JSON.stringify(body),
257
- signal
258
- });
259
- } catch (err) {
260
- if (signal?.aborted) throw err;
261
- throw new ChronosNetworkError(`Chronos API request failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
262
- }
263
- const payload = await parseJsonResponse(response);
264
- const envelope = isEnvelope(payload) ? payload : void 0;
265
- if (!response.ok || envelope && !envelope.success) {
266
- const is429 = response.status === 429;
267
- throw new (is429 ? ChronosRateLimitError : ChronosApiError)(apiErrorMessage(response, envelope), {
268
- status: response.status,
269
- code: typeof envelope?.code === "string" ? envelope.code : void 0,
270
- body: payload,
271
- requestId: response.headers.get("x-request-id") ?? void 0,
272
- retryAfterSeconds: is429 ? parseIntHeader(response.headers, "retry-after") : void 0,
273
- rateLimit: is429 ? void 0 : parseRateLimitHeaders(response.headers)
274
- });
298
+ let response;
299
+ try {
300
+ response = await this.fetch(`${this.baseUrl}${path}`, {
301
+ method: "POST",
302
+ headers: this.headers,
303
+ body: JSON.stringify(body),
304
+ signal: controller.signal
305
+ });
306
+ } catch (err) {
307
+ if (signal?.aborted && !timedOut) throw err;
308
+ if (timedOut) throw timeoutError(timeoutMs, err);
309
+ throw new ChronosNetworkError(`Chronos API request failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
310
+ }
311
+ let payload;
312
+ try {
313
+ payload = await response.json();
314
+ } catch (err) {
315
+ if (signal?.aborted && !timedOut) throw err;
316
+ if (timedOut) throw timeoutError(timeoutMs, err);
317
+ if (err instanceof SyntaxError) payload = null;
318
+ else throw new ChronosNetworkError(`Chronos API response failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
319
+ }
320
+ const envelope = isEnvelope(payload) ? payload : void 0;
321
+ if (!response.ok || envelope && !envelope.success) {
322
+ const is429 = response.status === 429;
323
+ const retryAfterApplies = is429 || response.status >= 500;
324
+ throw new (is429 ? ChronosRateLimitError : ChronosApiError)(apiErrorMessage(response, envelope), {
325
+ status: response.status,
326
+ code: typeof envelope?.code === "string" ? envelope.code : void 0,
327
+ body: payload,
328
+ requestId: response.headers.get("x-request-id") ?? void 0,
329
+ retryAfterSeconds: retryAfterApplies ? parseIntHeader(response.headers, "retry-after") : void 0,
330
+ rateLimit: is429 ? void 0 : parseRateLimitHeaders(response.headers)
331
+ });
332
+ }
333
+ if (!envelope) throw new ChronosError("Chronos API returned an invalid response");
334
+ return envelope.data;
335
+ } finally {
336
+ clearTimeout(timer);
337
+ signal?.removeEventListener("abort", onCallerAbort);
275
338
  }
276
- if (!envelope) throw new ChronosError("Chronos API returned an invalid response");
277
- return envelope.data;
278
339
  }
279
340
  };
341
+ function timeoutError(timeoutMs, cause) {
342
+ return new ChronosTimeoutError(`Chronos API request timed out after ${timeoutMs}ms`, { cause });
343
+ }
280
344
  function normalizeBaseUrl(baseUrl) {
281
345
  const normalized = baseUrl.trim().replace(/\/+$/, "");
282
346
  if (!normalized) throw new ChronosConfigError("Chronos baseUrl is required");
283
347
  return normalized;
284
348
  }
285
- async function parseJsonResponse(response) {
286
- try {
287
- return await response.json();
288
- } catch {
289
- return null;
290
- }
291
- }
292
349
  function apiErrorMessage(response, envelope) {
293
350
  if (typeof envelope?.message === "string" && envelope.message.trim()) return envelope.message;
294
351
  return response.ok ? "Chronos API returned an invalid response" : `Chronos API request failed with status ${response.status}`;
@@ -316,7 +373,7 @@ function isEnvelope(value) {
316
373
  //#endregion
317
374
  //#region src/internal/id.ts
318
375
  function generateWorkerId() {
319
- const bytes = crypto.getRandomValues(new Uint8Array(8));
376
+ const bytes = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(8));
320
377
  let hex = "";
321
378
  for (const b of bytes) hex += b.toString(16).padStart(2, "0");
322
379
  return `w_${hex}`;
@@ -332,12 +389,13 @@ function detectRuntime() {
332
389
  }
333
390
  //#endregion
334
391
  //#region src/internal/version.ts
335
- const SDK_VERSION = "0.0.2-canary.fa8e22c";
392
+ const SDK_VERSION = "0.1.0";
336
393
  //#endregion
337
394
  //#region src/worker.ts
338
395
  /** Default worker long-poll wait time in seconds. Equal to the API maximum. */
339
396
  const DEFAULT_POLL_WAIT_TIME_SECONDS = 20;
340
397
  const DEFAULT_RETRY_DELAY_MS = 1e3;
398
+ const MAX_RETRY_AFTER_MS = 6e4;
341
399
  const RESULT_REPORT_MAX_ATTEMPTS = 3;
342
400
  const MAX_REPORTED_ERROR_LENGTH = 4096;
343
401
  const DEFAULT_HANDLER_ERROR = "Chronos handler failed";
@@ -415,8 +473,11 @@ var Worker = class {
415
473
  * in-flight handler that has not exceeded its timeout, along with the
416
474
  * subsequent result-report, is allowed to complete. Timed-out handlers
417
475
  * are abandoned (their {@link ChronosContext.signal} fires, but the SDK
418
- * does not wait for them). Returns the same promise as the active
419
- * {@link Worker.start}, or a resolved promise if the worker isn't running.
476
+ * does not wait for them). Every HTTP request is bounded by the `timeoutMs`
477
+ * transport timeout and retry waits are skipped once stop is requested, so
478
+ * the returned promise resolves in bounded time. Returns the same promise
479
+ * as the active {@link Worker.start}, or a resolved promise if the worker
480
+ * isn't running.
420
481
  */
421
482
  stop() {
422
483
  this.pollController?.abort();
@@ -437,7 +498,7 @@ var Worker = class {
437
498
  }
438
499
  async claimJob() {
439
500
  if (this.handlerNames.length === 0) throw new ChronosError("Cannot claim jobs without registered handlers");
440
- return this.client.request("/v1/worker/jobs/claim", {
501
+ return this.client.request("/v1/workers/jobs/claim", {
441
502
  wait_time_seconds: this.pollWaitTimeSeconds,
442
503
  handlers: this.handlerNames,
443
504
  worker: {
@@ -446,7 +507,10 @@ var Worker = class {
446
507
  runtime: this.runtime,
447
508
  uptime_ms: Math.round(performance.now() - this.startedAt)
448
509
  }
449
- }, this.pollController?.signal);
510
+ }, {
511
+ signal: this.pollController?.signal,
512
+ timeoutMs: Math.min(this.pollWaitTimeSeconds * 1e3 + this.client.timeoutMs, MAX_TIMEOUT_MS)
513
+ });
450
514
  }
451
515
  async processJob(job) {
452
516
  const handler = this.handlers.get(job.handler);
@@ -541,7 +605,7 @@ var Worker = class {
541
605
  async reportResultWithRetry(executionId, body) {
542
606
  let lastErr;
543
607
  for (let attempt = 1; attempt <= RESULT_REPORT_MAX_ATTEMPTS; attempt++) try {
544
- await this.client.request(`/v1/worker/executions/${encodeURIComponent(executionId)}/result`, body);
608
+ await this.client.request(`/v1/workers/executions/${encodeURIComponent(executionId)}/result`, body);
545
609
  return;
546
610
  } catch (err) {
547
611
  const apiErr = err instanceof ChronosApiError ? err : void 0;
@@ -563,7 +627,7 @@ var Worker = class {
563
627
  attempt,
564
628
  maxAttempts: RESULT_REPORT_MAX_ATTEMPTS
565
629
  });
566
- await sleep(retryDelayFor(err, this.retryDelayMs));
630
+ await sleep(retryDelayFor(err, this.retryDelayMs), this.pollController?.signal);
567
631
  }
568
632
  throw lastErr;
569
633
  }
@@ -639,10 +703,10 @@ function truncate(value, maxLength) {
639
703
  return value.length <= maxLength ? value : value.slice(0, maxLength);
640
704
  }
641
705
  function retryDelayFor(err, fallbackMs) {
642
- if (err instanceof ChronosApiError && err.retryAfterSeconds !== void 0) return err.retryAfterSeconds * 1e3;
706
+ if (err instanceof ChronosApiError && err.retryAfterSeconds !== void 0) return Math.max(Math.min(err.retryAfterSeconds * 1e3, MAX_RETRY_AFTER_MS), fallbackMs);
643
707
  return fallbackMs;
644
708
  }
645
- const NON_TERMINAL_4XX = new Set([
709
+ const NON_TERMINAL_4XX = /* @__PURE__ */ new Set([
646
710
  408,
647
711
  409,
648
712
  429
@@ -822,9 +886,11 @@ exports.ChronosError = ChronosError;
822
886
  exports.ChronosHandlerError = ChronosHandlerError;
823
887
  exports.ChronosNetworkError = ChronosNetworkError;
824
888
  exports.ChronosRateLimitError = ChronosRateLimitError;
889
+ exports.ChronosTimeoutError = ChronosTimeoutError;
825
890
  exports.ChronosWebhookVerificationError = ChronosWebhookVerificationError;
826
891
  exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
827
892
  exports.DEFAULT_POLL_WAIT_TIME_SECONDS = DEFAULT_POLL_WAIT_TIME_SECONDS;
893
+ exports.DEFAULT_TIMEOUT_MS = DEFAULT_TIMEOUT_MS;
828
894
  exports.DEFAULT_TIMESTAMP_TOLERANCE_SECONDS = DEFAULT_TIMESTAMP_TOLERANCE_SECONDS;
829
895
  exports.SDK_VERSION = SDK_VERSION;
830
896
  exports.Webhook = Webhook;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../src/errors.ts","../src/internal/logger.ts","../src/internal/validate.ts","../src/client.ts","../src/internal/id.ts","../src/internal/runtime.ts","../src/internal/version.ts","../src/worker.ts","../src/webhook.ts","../src/index.ts"],"sourcesContent":["/**\n * Base class for all errors thrown by the Chronos SDK. Catch this in a\n * single `catch` to handle any SDK failure generically; use the subclasses\n * to branch on cause.\n *\n * @example\n * ```ts\n * import { Chronos, ChronosError } from '@chronos.sh/sdk';\n *\n * const chronos = new Chronos({ apiKey: 'chrns_...' });\n * try {\n * await chronos.worker.start();\n * } catch (err) {\n * if (err instanceof ChronosError) {\n * console.error('Chronos failed:', err.message);\n * }\n * }\n * ```\n */\nexport class ChronosError extends Error {\n /** Original error/value that caused this SDK error, when one is available. */\n declare readonly cause?: unknown;\n\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = 'ChronosError';\n\n if (options && !('cause' in this)) {\n Object.defineProperty(this, 'cause', {\n value: options.cause,\n configurable: true,\n writable: true,\n });\n }\n }\n}\n\n/**\n * Thrown when SDK options fail validation at `new Chronos({ ... })`. Covers\n * `apiKey`, `baseUrl`, `pollWaitTimeSeconds`, and `retryDelayMs`.\n *\n * @example\n * ```ts\n * import { Chronos, ChronosConfigError } from '@chronos.sh/sdk';\n *\n * try {\n * const chronos = new Chronos({ apiKey: '' });\n * } catch (err) {\n * if (err instanceof ChronosConfigError) {\n * console.error('Invalid Chronos config:', err.message);\n * }\n * }\n * ```\n */\nexport class ChronosConfigError extends ChronosError {\n constructor(message: string) {\n super(message);\n this.name = 'ChronosConfigError';\n }\n}\n\n/** Rate-limit budget from `X-RateLimit-*` response headers. Present on non-429 responses. */\nexport type RateLimitInfo = {\n /** Maximum requests allowed per window. */\n limit: number;\n /** Requests remaining in the current window. */\n remaining: number;\n /** Seconds until the current window resets. */\n reset: number;\n};\n\n/** Options for constructing a {@link ChronosApiError}. */\nexport type ChronosApiErrorOptions = {\n /** HTTP status code returned by the Chronos API. */\n status: number;\n /** Application-level error code from the response envelope, when present. */\n code?: string;\n /** Full parsed response payload (envelope + data, or whatever the server returned). */\n body?: unknown;\n /** Value of the `X-Request-Id` response header, when present. Pair with server logs. */\n requestId?: string;\n /** Seconds to wait before retrying, from the `Retry-After` response header. Present on 429 responses. */\n retryAfterSeconds?: number;\n /** Rate-limit budget from `X-RateLimit-*` headers. Present on non-429 responses. */\n rateLimit?: RateLimitInfo;\n};\n\n/**\n * Thrown when the Chronos API responds with a non-2xx status or a\n * `success: false` envelope. Carries HTTP `status`, the application\n * `code`, parsed `body`, the API's `X-Request-Id`, and — when the\n * relevant headers are present — rate-limit metadata\n * (`retryAfterSeconds` on 429, `rateLimit` on other errors).\n *\n * `instanceof ChronosApiError` means the server replied;\n * network/transport failures throw {@link ChronosNetworkError} instead.\n * For 429 specifically, see {@link ChronosRateLimitError}.\n *\n * @example\n * ```ts\n * import { ChronosApiError } from '@chronos.sh/sdk';\n *\n * function handleSdkError(err: unknown) {\n * if (err instanceof ChronosApiError) {\n * if (err.status === 401) return refreshAuth();\n * console.error('API error', { status: err.status, requestId: err.requestId });\n * }\n * }\n * ```\n */\nexport class ChronosApiError extends ChronosError {\n /** HTTP status code returned by the Chronos API. */\n readonly status: number;\n /** Application-level error code from the response envelope, when present. */\n readonly code?: string;\n /** Full parsed response payload (envelope + data, or whatever the server returned). */\n readonly body?: unknown;\n /** Value of the `X-Request-Id` response header, when present. */\n readonly requestId?: string;\n /** Seconds to wait before retrying, from the `Retry-After` response header. Present on 429 responses. */\n readonly retryAfterSeconds?: number;\n /** Rate-limit budget from `X-RateLimit-*` headers. Present on non-429 responses. */\n readonly rateLimit?: RateLimitInfo;\n\n constructor(message: string, options: ChronosApiErrorOptions) {\n super(message);\n this.name = 'ChronosApiError';\n this.status = options.status;\n this.code = options.code;\n this.body = options.body;\n this.requestId = options.requestId;\n this.retryAfterSeconds = options.retryAfterSeconds;\n this.rateLimit = options.rateLimit;\n }\n}\n\n/**\n * Thrown when the Chronos API responds with `429 Too Many Requests`.\n * Subclass of {@link ChronosApiError} — `instanceof ChronosRateLimitError`\n * narrows to a rate-limited response. `retryAfterSeconds` is set when the\n * response includes a `Retry-After` header; otherwise it is `undefined`.\n *\n * @example\n * ```ts\n * import { ChronosRateLimitError } from '@chronos.sh/sdk';\n *\n * function handleSdkError(err: unknown) {\n * if (err instanceof ChronosRateLimitError && err.retryAfterSeconds) {\n * console.warn(`Rate limited — retry after ${err.retryAfterSeconds}s`);\n * }\n * }\n * ```\n */\nexport class ChronosRateLimitError extends ChronosApiError {\n constructor(message: string, options: ChronosApiErrorOptions) {\n super(message, options);\n this.name = 'ChronosRateLimitError';\n }\n}\n\n/**\n * Thrown when the underlying `fetch` rejects before the server replies —\n * DNS failure, TCP reset, connection refused, etc. The original error is\n * available on `.cause`.\n *\n * Abort signals propagate unwrapped — `instanceof ChronosNetworkError`\n * always means a real transport failure, not a graceful shutdown.\n *\n * @example\n * ```ts\n * import { ChronosNetworkError } from '@chronos.sh/sdk';\n *\n * function handleSdkError(err: unknown) {\n * if (err instanceof ChronosNetworkError) {\n * console.warn('Transport blip', { cause: err.cause });\n * }\n * }\n * ```\n */\nexport class ChronosNetworkError extends ChronosError {\n /** Original error/value rejected by the underlying `fetch`. */\n declare readonly cause: unknown;\n\n constructor(message: string, options: { cause: unknown }) {\n super(message, options);\n this.name = 'ChronosNetworkError';\n }\n}\n\n/**\n * Wraps an exception thrown by a user-supplied {@link ChronosHandler}. The\n * original error is on `.cause`; `.message` is copied from the original so\n * the SDK reports it to the API as the failure reason.\n *\n * @example\n * ```ts\n * import { ChronosHandlerError } from '@chronos.sh/sdk';\n *\n * if (err instanceof ChronosHandlerError) {\n * console.error('Handler threw', err.cause);\n * }\n * ```\n */\nexport class ChronosHandlerError extends ChronosError {\n /** Original error/value thrown by the user-supplied handler. */\n declare readonly cause: unknown;\n\n constructor(message: string, options: { cause: unknown }) {\n super(message, options);\n this.name = 'ChronosHandlerError';\n }\n}\n\n/**\n * Thrown by {@link Webhook.verify} when signature verification fails.\n * Covers missing or malformed headers, expired timestamps, and signature\n * mismatches.\n *\n * @example\n * ```ts\n * import { Webhook, ChronosWebhookVerificationError } from '@chronos.sh/sdk';\n *\n * const wh = new Webhook(process.env.CHRONOS_SIGNING_SECRET!);\n * try {\n * const payload = await wh.verify(rawBody, headers);\n * } catch (err) {\n * if (err instanceof ChronosWebhookVerificationError) {\n * console.error('Webhook verification failed:', err.message);\n * }\n * }\n * ```\n */\nexport class ChronosWebhookVerificationError extends ChronosError {\n constructor(message: string) {\n super(message);\n this.name = 'ChronosWebhookVerificationError';\n }\n}\n","import type { ChronosLogger } from '../types';\n\nexport const defaultLogger: ChronosLogger = {\n debug: (message, meta) => logToConsole(console.debug, message, meta),\n info: (message, meta) => logToConsole(console.info, message, meta),\n warn: (message, meta) => logToConsole(console.warn, message, meta),\n error: (message, meta) => logToConsole(console.error, message, meta),\n};\n\nfunction logToConsole(\n method: (message?: unknown, ...optionalParams: unknown[]) => void,\n message: string,\n meta?: Record<string, unknown>,\n): void {\n if (meta) {\n method(message, meta);\n } else {\n method(message);\n }\n}\n","import { ChronosConfigError, ChronosError } from '../errors';\n\nexport const MAX_HANDLER_NAME_LENGTH = 255;\nexport const MAX_POLL_WAIT_TIME_SECONDS = 20;\n\nexport function validateApiKey(apiKey: string | undefined): string {\n const trimmed = apiKey?.trim();\n if (!trimmed) {\n throw new ChronosConfigError('Chronos apiKey is required');\n }\n return trimmed;\n}\n\nexport function validatePollWaitTime(seconds: number): void {\n if (!Number.isInteger(seconds) || seconds < 0 || seconds > MAX_POLL_WAIT_TIME_SECONDS) {\n throw new ChronosConfigError(\n `pollWaitTimeSeconds must be an integer between 0 and ${MAX_POLL_WAIT_TIME_SECONDS}`,\n );\n }\n}\n\nexport function validateRetryDelayMs(ms: number): void {\n if (!Number.isFinite(ms) || ms < 0) {\n throw new ChronosConfigError('retryDelayMs must be a non-negative number');\n }\n}\n\nexport function validateWorkerId(id: string): string {\n const trimmed = id.trim();\n if (trimmed.length === 0 || trimmed.length > 255) {\n throw new ChronosConfigError('workerId must be 1-255 characters');\n }\n return trimmed;\n}\n\nexport function normalizeHandlerName(name: string): string {\n const normalized = name.trim();\n\n if (!normalized) {\n throw new ChronosError('Handler name is required');\n }\n\n if (normalized.length > MAX_HANDLER_NAME_LENGTH) {\n throw new ChronosError(`Handler name must be ${MAX_HANDLER_NAME_LENGTH} characters or fewer`);\n }\n\n return normalized;\n}\n","import {\n ChronosApiError,\n ChronosConfigError,\n ChronosError,\n ChronosNetworkError,\n ChronosRateLimitError,\n type RateLimitInfo,\n} from './errors';\nimport { defaultLogger } from './internal/logger';\nimport { validateApiKey } from './internal/validate';\nimport type { ChronosLogger, ChronosOptions, FetchLike } from './types';\n\n/** Default Chronos API base URL. */\nexport const DEFAULT_BASE_URL = 'https://api.chronos.sh';\n\ntype ApiResponse<T> = {\n success: boolean;\n message?: string;\n code?: string;\n data: T;\n};\n\nexport class BaseClient {\n readonly apiKey: string;\n readonly baseUrl: string;\n readonly fetch: FetchLike;\n readonly logger: ChronosLogger;\n private readonly headers: Record<string, string>;\n\n constructor(options: ChronosOptions) {\n this.apiKey = validateApiKey(options.apiKey);\n this.baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL);\n this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis);\n this.logger = options.logger ?? defaultLogger;\n this.headers = {\n 'content-type': 'application/json',\n authorization: `Bearer ${this.apiKey}`,\n };\n }\n\n async request<T>(path: string, body: Record<string, unknown>, signal?: AbortSignal): Promise<T> {\n let response: Response;\n try {\n response = await this.fetch(`${this.baseUrl}${path}`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(body),\n signal,\n });\n } catch (err) {\n if (signal?.aborted) {\n throw err;\n }\n const message = err instanceof Error ? err.message : String(err);\n throw new ChronosNetworkError(`Chronos API request failed: ${message}`, { cause: err });\n }\n\n const payload = await parseJsonResponse<ApiResponse<T>>(response);\n const envelope = isEnvelope(payload) ? (payload as ApiResponse<T>) : undefined;\n\n if (!response.ok || (envelope && !envelope.success)) {\n const is429 = response.status === 429;\n const ErrorClass = is429 ? ChronosRateLimitError : ChronosApiError;\n throw new ErrorClass(apiErrorMessage(response, envelope), {\n status: response.status,\n code: typeof envelope?.code === 'string' ? envelope.code : undefined,\n body: payload,\n requestId: response.headers.get('x-request-id') ?? undefined,\n retryAfterSeconds: is429 ? parseIntHeader(response.headers, 'retry-after') : undefined,\n rateLimit: is429 ? undefined : parseRateLimitHeaders(response.headers),\n });\n }\n\n if (!envelope) {\n // Plain ChronosError, not ChronosApiError — a malformed 200 body is\n // an infrastructure anomaly that should be retried, not a definitive\n // API rejection.\n throw new ChronosError('Chronos API returned an invalid response');\n }\n\n return envelope.data;\n }\n}\n\nfunction normalizeBaseUrl(baseUrl: string): string {\n const normalized = baseUrl.trim().replace(/\\/+$/, '');\n if (!normalized) {\n throw new ChronosConfigError('Chronos baseUrl is required');\n }\n return normalized;\n}\n\nasync function parseJsonResponse<T>(response: Response): Promise<T | null> {\n try {\n return (await response.json()) as T;\n } catch {\n return null;\n }\n}\n\nfunction apiErrorMessage(\n response: Response,\n envelope: Record<string, unknown> | undefined,\n): string {\n if (typeof envelope?.message === 'string' && envelope.message.trim()) {\n return envelope.message;\n }\n\n return response.ok\n ? 'Chronos API returned an invalid response'\n : `Chronos API request failed with status ${response.status}`;\n}\n\nfunction parseIntHeader(headers: Headers, name: string): number | undefined {\n const raw = headers.get(name);\n if (raw === null || !/^\\d+$/.test(raw)) {\n return undefined;\n }\n return Number.parseInt(raw, 10);\n}\n\nfunction parseRateLimitHeaders(headers: Headers): RateLimitInfo | undefined {\n const limit = parseIntHeader(headers, 'x-ratelimit-limit');\n const remaining = parseIntHeader(headers, 'x-ratelimit-remaining');\n const reset = parseIntHeader(headers, 'x-ratelimit-reset');\n\n if (limit === undefined || remaining === undefined || reset === undefined) {\n return undefined;\n }\n return { limit, remaining, reset };\n}\n\n// Malformed bodies (arrays, primitives, missing `success`) are routed to the\n// invalid-response path, not classified as API errors.\nfunction isEnvelope(value: unknown): value is Record<string, unknown> & { success: boolean } {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return false;\n }\n return typeof (value as Record<string, unknown>).success === 'boolean';\n}\n","export function generateWorkerId(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(8));\n let hex = '';\n for (const b of bytes) {\n hex += b.toString(16).padStart(2, '0');\n }\n return `w_${hex}`;\n}\n","export function detectRuntime(): string {\n const g = globalThis as unknown as Record<string, Record<string, unknown> | undefined>;\n\n if (g.Bun != null) {\n return `bun/${(g.Bun.version as string | undefined) ?? 'unknown'}`;\n }\n\n if (g.Deno != null) {\n const version = g.Deno.version as Record<string, string> | undefined;\n return `deno/${version?.deno ?? 'unknown'}`;\n }\n\n if (typeof process !== 'undefined' && process.versions?.node) {\n return `node/${process.versions.node}`;\n }\n\n return 'unknown';\n}\n","declare const __SDK_VERSION__: string;\n\nexport const SDK_VERSION: string = __SDK_VERSION__;\n","import type { BaseClient } from './client';\nimport { ChronosApiError, ChronosError, ChronosHandlerError, ChronosNetworkError } from './errors';\nimport { generateWorkerId } from './internal/id';\nimport { detectRuntime } from './internal/runtime';\nimport {\n normalizeHandlerName,\n validatePollWaitTime,\n validateRetryDelayMs,\n validateWorkerId,\n} from './internal/validate';\nimport { SDK_VERSION } from './internal/version';\nimport type {\n ChronosContext,\n ChronosHandler,\n ChronosHandlerResult,\n ChronosOptions,\n ChronosSchedule,\n} from './types';\n\n/** Default worker long-poll wait time in seconds. Equal to the API maximum. */\nexport const DEFAULT_POLL_WAIT_TIME_SECONDS = 20;\nconst DEFAULT_RETRY_DELAY_MS = 1_000;\nconst RESULT_REPORT_MAX_ATTEMPTS = 3;\nconst MAX_REPORTED_ERROR_LENGTH = 4_096;\nconst DEFAULT_HANDLER_ERROR = 'Chronos handler failed';\n\ntype ResultStatus = 'completed' | 'failed' | 'timeout';\n\ntype HandlerOutcome = { timedOut: false; result: ChronosHandlerResult } | { timedOut: true };\n\ntype ClaimedJob = {\n job_id: string;\n execution_id: string;\n handler: string;\n scheduled_for: string;\n attempt: number;\n timeout: number;\n payload: unknown;\n schedule: ChronosSchedule | null;\n};\n\n/**\n * Long-poll worker. Claims jobs from the Chronos API, dispatches them to\n * registered handlers, and reports results.\n *\n * Construct via `new Chronos({ apiKey }).worker` rather than directly.\n */\nexport class Worker {\n private readonly client: BaseClient;\n private readonly pollWaitTimeSeconds: number;\n private readonly retryDelayMs: number;\n private readonly workerId: string;\n private readonly runtime: string;\n private readonly handlers = new Map<string, ChronosHandler<unknown>>();\n private handlerNames: string[] = [];\n private startPromise?: Promise<void>;\n private pollController?: AbortController;\n private startedAt = 0;\n\n constructor(client: BaseClient, options: ChronosOptions) {\n this.client = client;\n this.pollWaitTimeSeconds = options.pollWaitTimeSeconds ?? DEFAULT_POLL_WAIT_TIME_SECONDS;\n this.retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;\n this.workerId =\n options.workerId !== undefined ? validateWorkerId(options.workerId) : generateWorkerId();\n this.runtime = detectRuntime();\n\n validatePollWaitTime(this.pollWaitTimeSeconds);\n validateRetryDelayMs(this.retryDelayMs);\n }\n\n /**\n * Register a handler for a named job type. Invoked when the Chronos API\n * claims a job whose `handler` field matches `name`. Names are trimmed and\n * must be 1–255 characters.\n *\n * @param name - Handler name. Must match the schedule's `handler` on the API side.\n * @param handler - Async function invoked with the job context. Return a\n * plain object to record a result, or `undefined` for none.\n * @returns The Worker, for chaining.\n * @throws {ChronosError} If the name is invalid, already registered, or `handler` is not a function.\n *\n * @example\n * ```ts\n * chronos.worker\n * .handle('send-email', async (ctx) => ({ sent: true }))\n * .handle('cleanup', async () => undefined);\n * ```\n */\n handle<TPayload = unknown>(name: string, handler: ChronosHandler<TPayload>): this {\n const normalizedName = normalizeHandlerName(name);\n\n if (this.handlers.has(normalizedName)) {\n throw new ChronosError(`Handler \"${normalizedName}\" is already registered`);\n }\n\n if (typeof handler !== 'function') {\n throw new ChronosError(`Handler \"${normalizedName}\" must be a function`);\n }\n\n this.handlers.set(normalizedName, handler as ChronosHandler<unknown>);\n this.handlerNames.push(normalizedName);\n return this;\n }\n\n /**\n * Begin long-polling for jobs. The returned promise resolves when\n * {@link Worker.stop} is called and any in-flight job completes.\n *\n * @throws {ChronosError} Synchronously, if no handlers are registered or the worker is already running.\n */\n start(): Promise<void> {\n if (this.startPromise) {\n throw new ChronosError('Chronos worker is already started');\n }\n\n if (this.handlers.size === 0) {\n throw new ChronosError('Register at least one handler before starting Chronos');\n }\n\n this.startedAt = performance.now();\n this.pollController = new AbortController();\n this.startPromise = this.runLoop().finally(() => {\n this.startPromise = undefined;\n this.pollController = undefined;\n });\n\n return this.startPromise;\n }\n\n /**\n * Request graceful shutdown. The poll loop is aborted immediately; any\n * in-flight handler that has not exceeded its timeout, along with the\n * subsequent result-report, is allowed to complete. Timed-out handlers\n * are abandoned (their {@link ChronosContext.signal} fires, but the SDK\n * does not wait for them). Returns the same promise as the active\n * {@link Worker.start}, or a resolved promise if the worker isn't running.\n */\n stop(): Promise<void> {\n this.pollController?.abort();\n return this.startPromise ?? Promise.resolve();\n }\n\n private get isStopped(): boolean {\n return this.pollController?.signal.aborted ?? true;\n }\n\n private async runLoop(): Promise<void> {\n while (!this.isStopped) {\n try {\n const job = await this.claimJob();\n if (job) {\n await this.processJob(job);\n }\n } catch (err) {\n if (this.isStopped) {\n break;\n }\n\n this.client.logger.error('Chronos poll loop error', { err: errorToLogValue(err) });\n await sleep(retryDelayFor(err, this.retryDelayMs), this.pollController?.signal);\n }\n }\n }\n\n private async claimJob(): Promise<ClaimedJob | null> {\n if (this.handlerNames.length === 0) {\n throw new ChronosError('Cannot claim jobs without registered handlers');\n }\n\n return this.client.request<ClaimedJob | null>(\n '/v1/worker/jobs/claim',\n {\n wait_time_seconds: this.pollWaitTimeSeconds,\n handlers: this.handlerNames,\n worker: {\n id: this.workerId,\n sdk_version: SDK_VERSION,\n runtime: this.runtime,\n uptime_ms: Math.round(performance.now() - this.startedAt),\n },\n },\n this.pollController?.signal,\n );\n }\n\n private async processJob(job: ClaimedJob): Promise<void> {\n const handler = this.handlers.get(job.handler);\n if (!handler) {\n return this.handleUnregisteredJob(job);\n }\n\n const controller = new AbortController();\n let outcome: HandlerOutcome;\n try {\n outcome = await this.runHandlerWithTimeout(\n handler,\n createContext(job, controller.signal),\n job.timeout * 1_000,\n controller,\n );\n } catch (err) {\n const message = errorMessage(err);\n const handlerErr = new ChronosHandlerError(message, { cause: err });\n this.client.logger.error('Chronos handler failed', {\n ...jobLogMeta(job),\n err: errorToLogValue(handlerErr),\n });\n await this.safeReportFailed(job, message);\n return;\n }\n\n if (outcome.timedOut) {\n this.client.logger.error('Chronos handler timed out', {\n ...jobLogMeta(job),\n timeout: job.timeout,\n });\n try {\n await this.reportTimeout(job.execution_id);\n } catch (err) {\n this.logResultReportFailure(err, job, 'timeout');\n }\n return;\n }\n\n let result: Record<string, unknown> | undefined;\n try {\n result = normalizeHandlerResult(outcome.result);\n } catch (err) {\n this.client.logger.error('Chronos handler returned invalid result', {\n ...jobLogMeta(job),\n err: errorToLogValue(err),\n });\n await this.safeReportFailed(job, errorMessage(err));\n return;\n }\n\n try {\n await this.reportCompleted(job.execution_id, result);\n } catch (err) {\n this.logResultReportFailure(err, job, 'completed');\n }\n }\n\n private runHandlerWithTimeout(\n handler: ChronosHandler<unknown>,\n ctx: ChronosContext<unknown>,\n timeoutMs: number,\n controller: AbortController,\n ): Promise<HandlerOutcome> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const handlerPromise = Promise.resolve(handler(ctx)).then(\n (result): HandlerOutcome => ({ timedOut: false, result }),\n );\n handlerPromise.catch(() => {});\n\n const timeoutPromise = new Promise<HandlerOutcome>((resolve) => {\n timer = setTimeout(() => {\n controller.abort();\n resolve({ timedOut: true });\n }, timeoutMs);\n });\n\n return Promise.race([handlerPromise, timeoutPromise]).finally(() => {\n clearTimeout(timer);\n });\n }\n\n private async handleUnregisteredJob(job: ClaimedJob): Promise<void> {\n const message = `Chronos SDK received job for unregistered handler \"${job.handler}\"`;\n this.client.logger.error(message, jobLogMeta(job));\n await this.safeReportFailed(job, message);\n }\n\n private async safeReportFailed(job: ClaimedJob, message: string): Promise<void> {\n try {\n await this.reportFailed(job.execution_id, message);\n } catch (err) {\n this.logResultReportFailure(err, job, 'failed');\n }\n }\n\n private async reportCompleted(\n executionId: string,\n result: Record<string, unknown> | undefined,\n ): Promise<void> {\n await this.reportResultWithRetry(executionId, {\n status: 'completed',\n ...(result === undefined ? {} : { result }),\n });\n }\n\n private async reportTimeout(executionId: string): Promise<void> {\n await this.reportResultWithRetry(executionId, { status: 'timeout' });\n }\n\n private async reportFailed(executionId: string, error: string): Promise<void> {\n await this.reportResultWithRetry(executionId, {\n status: 'failed',\n error: truncate(error, MAX_REPORTED_ERROR_LENGTH),\n });\n }\n\n private async reportResultWithRetry(\n executionId: string,\n body: Record<string, unknown>,\n ): Promise<void> {\n let lastErr: unknown;\n\n for (let attempt = 1; attempt <= RESULT_REPORT_MAX_ATTEMPTS; attempt++) {\n try {\n await this.client.request(\n `/v1/worker/executions/${encodeURIComponent(executionId)}/result`,\n body,\n );\n return;\n } catch (err) {\n const apiErr = err instanceof ChronosApiError ? err : undefined;\n\n if (apiErr?.status === 409) {\n this.client.logger.warn('Chronos result discarded; execution already terminal', {\n executionId,\n status: body.status,\n code: apiErr.code,\n });\n return;\n }\n\n if (apiErr && isTerminalReportError(apiErr.status)) {\n throw apiErr;\n }\n\n lastErr = err;\n if (attempt === RESULT_REPORT_MAX_ATTEMPTS) {\n break;\n }\n\n this.client.logger.warn('Chronos result report failed; retrying', {\n err: errorToLogValue(err),\n executionId,\n status: body.status,\n attempt,\n maxAttempts: RESULT_REPORT_MAX_ATTEMPTS,\n });\n await sleep(retryDelayFor(err, this.retryDelayMs));\n }\n }\n\n throw lastErr;\n }\n\n private logResultReportFailure(err: unknown, job: ClaimedJob, status: ResultStatus): void {\n const isTerminal = err instanceof ChronosApiError && isTerminalReportError(err.status);\n this.client.logger.error(\n isTerminal\n ? 'Chronos result report rejected by API'\n : 'Chronos result report failed after retries',\n { ...jobLogMeta(job), err: errorToLogValue(err), status },\n );\n }\n}\n\nfunction createContext(job: ClaimedJob, signal: AbortSignal): ChronosContext<unknown> {\n return {\n jobId: job.job_id,\n executionId: job.execution_id,\n handler: job.handler,\n payload: job.payload,\n scheduledFor: new Date(job.scheduled_for),\n attempt: job.attempt,\n timeout: job.timeout,\n signal,\n schedule: job.schedule,\n };\n}\n\nfunction jobLogMeta(job: ClaimedJob): Record<string, unknown> {\n return {\n jobId: job.job_id,\n executionId: job.execution_id,\n handler: job.handler,\n };\n}\n\nfunction normalizeHandlerResult(result: ChronosHandlerResult): Record<string, unknown> | undefined {\n if (result === undefined) {\n return undefined;\n }\n\n if (!isPlainObject(result)) {\n throw new ChronosError('Chronos handler result must be a plain object or undefined');\n }\n\n try {\n JSON.stringify(result);\n } catch (err) {\n const reason = err instanceof Error ? err.message : 'value is not JSON-encodable';\n throw new ChronosError(`Chronos handler result is not JSON-encodable: ${reason}`);\n }\n\n return result;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return false;\n }\n\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\nfunction errorMessage(err: unknown): string {\n const message = err instanceof Error ? err.message : String(err);\n return message.trim() || DEFAULT_HANDLER_ERROR;\n}\n\nfunction errorToLogValue(err: unknown): unknown {\n if (!(err instanceof Error)) {\n return err;\n }\n\n const base = { name: err.name, message: err.message, stack: err.stack };\n\n if (err instanceof ChronosApiError) {\n return {\n ...base,\n status: err.status,\n code: err.code,\n requestId: err.requestId,\n ...(err.retryAfterSeconds !== undefined && { retryAfterSeconds: err.retryAfterSeconds }),\n ...(err.rateLimit !== undefined && { rateLimit: err.rateLimit }),\n };\n }\n if (err instanceof ChronosNetworkError || err instanceof ChronosHandlerError) {\n return { ...base, cause: errorToLogValue(err.cause) };\n }\n return base;\n}\n\nfunction truncate(value: string, maxLength: number): string {\n return value.length <= maxLength ? value : value.slice(0, maxLength);\n}\n\nfunction retryDelayFor(err: unknown, fallbackMs: number): number {\n if (err instanceof ChronosApiError && err.retryAfterSeconds !== undefined) {\n return err.retryAfterSeconds * 1_000;\n }\n return fallbackMs;\n}\n\n// 409 is handled by the caller before reaching here; 408/429 are transient.\nconst NON_TERMINAL_4XX = new Set([408, 409, 429]);\n\n// 200 reaches here only via an explicit `{ success: false }` envelope from BaseClient.\nfunction isTerminalReportError(status: number): boolean {\n if (status === 200) {\n return true;\n }\n if (NON_TERMINAL_4XX.has(status)) {\n return false;\n }\n return status >= 400 && status < 500;\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) {\n return Promise.resolve();\n }\n\n return new Promise((resolve) => {\n const timeout: ReturnType<typeof setTimeout> = setTimeout(done, ms);\n\n function done() {\n clearTimeout(timeout);\n signal?.removeEventListener('abort', done);\n resolve();\n }\n\n signal?.addEventListener('abort', done, { once: true });\n });\n}\n","import { ChronosWebhookVerificationError } from './errors';\nimport type { WebhookOptions } from './types';\n\nexport const DEFAULT_TIMESTAMP_TOLERANCE_SECONDS = 300;\n\nconst SIGNATURE_PREFIX = 'sha256=';\nconst REQUIRED_HEADERS = [\n 'x-chronos-signature',\n 'x-chronos-timestamp',\n 'x-chronos-delivery-id',\n] as const;\n\n/**\n * Verifies webhook signatures on Chronos push deliveries.\n *\n * @example\n * ```ts\n * import { Webhook } from '@chronos.sh/sdk';\n *\n * const wh = new Webhook(process.env.CHRONOS_SIGNING_SECRET!);\n *\n * // In your request handler:\n * const payload = await wh.verify(rawBody, {\n * 'x-chronos-signature': req.headers['x-chronos-signature'],\n * 'x-chronos-timestamp': req.headers['x-chronos-timestamp'],\n * 'x-chronos-delivery-id': req.headers['x-chronos-delivery-id'],\n * });\n * ```\n */\nexport class Webhook {\n private readonly secrets: string[];\n private readonly toleranceSeconds: number;\n private keysPromise?: Promise<CryptoKey[]>;\n\n /**\n * @param secret - Signing secret or array of secrets (for key rotation).\n * @param options - Optional configuration.\n * @throws {ChronosWebhookVerificationError} If no valid secret is provided.\n */\n constructor(secret: string | string[], options?: WebhookOptions) {\n const secrets = Array.isArray(secret) ? secret : [secret];\n this.secrets = secrets\n .filter((s) => typeof s === 'string' && s.trim().length > 0)\n .map((s) => s.trim());\n\n if (this.secrets.length === 0) {\n throw new ChronosWebhookVerificationError(\n 'At least one non-empty signing secret is required',\n );\n }\n\n const tolerance = options?.toleranceSeconds ?? DEFAULT_TIMESTAMP_TOLERANCE_SECONDS;\n if (!Number.isFinite(tolerance) || tolerance < 0) {\n throw new ChronosWebhookVerificationError(\n 'toleranceSeconds must be a finite non-negative number',\n );\n }\n this.toleranceSeconds = tolerance;\n }\n\n /**\n * Verifies the webhook signature and returns the parsed request body.\n *\n * @param body - Raw request body as a string. Must be the exact bytes received — not re-serialized.\n * @param headers - Request headers. Keys are matched case-insensitively.\n * @returns The parsed JSON body on successful verification.\n * @throws {ChronosWebhookVerificationError} If any verification step fails.\n */\n async verify(\n body: string,\n headers: Record<string, string | undefined> | Headers,\n ): Promise<unknown> {\n const normalized = normalizeHeaders(headers);\n\n for (const name of REQUIRED_HEADERS) {\n const value = normalized[name];\n if (!value || typeof value !== 'string') {\n throw new ChronosWebhookVerificationError(`Missing required header: ${name}`);\n }\n }\n\n const signature = normalized['x-chronos-signature'] as string;\n const timestamp = normalized['x-chronos-timestamp'] as string;\n const deliveryId = normalized['x-chronos-delivery-id'] as string;\n\n if (!/^[0-9]+$/.test(timestamp)) {\n throw new ChronosWebhookVerificationError('Invalid timestamp');\n }\n\n const ts = Number(timestamp);\n if (ts <= 0) {\n throw new ChronosWebhookVerificationError('Invalid timestamp');\n }\n\n const now = Math.floor(Date.now() / 1000);\n const isStale = Math.abs(now - ts) > this.toleranceSeconds;\n if (this.toleranceSeconds > 0 && isStale) {\n throw new ChronosWebhookVerificationError('Timestamp outside tolerance');\n }\n\n if (!signature.startsWith(SIGNATURE_PREFIX)) {\n throw new ChronosWebhookVerificationError(\n `Invalid signature format: expected ${SIGNATURE_PREFIX}<hex>`,\n );\n }\n\n const expectedHex = signature.slice(SIGNATURE_PREFIX.length);\n if (expectedHex.length === 0) {\n throw new ChronosWebhookVerificationError(\n 'Invalid signature: expected 32-byte SHA-256 digest',\n );\n }\n\n let expectedBytes: ArrayBuffer;\n try {\n expectedBytes = hexDecode(expectedHex);\n } catch {\n throw new ChronosWebhookVerificationError('Invalid signature: malformed hex');\n }\n\n if (expectedBytes.byteLength !== 32) {\n throw new ChronosWebhookVerificationError(\n 'Invalid signature: expected 32-byte SHA-256 digest',\n );\n }\n\n const signedPayload = `${deliveryId}.${timestamp}.${body}`;\n const data = new TextEncoder().encode(signedPayload);\n\n try {\n const keys = await this.ensureKeys();\n for (const key of keys) {\n const valid = await globalThis.crypto.subtle.verify('HMAC', key, expectedBytes, data);\n if (valid) {\n try {\n return JSON.parse(body);\n } catch {\n throw new ChronosWebhookVerificationError('Verified payload is not valid JSON');\n }\n }\n }\n } catch (err) {\n if (err instanceof ChronosWebhookVerificationError) {\n throw err;\n }\n throw new ChronosWebhookVerificationError('Signature verification failed');\n }\n\n throw new ChronosWebhookVerificationError('Signature verification failed');\n }\n\n private ensureKeys(): Promise<CryptoKey[]> {\n if (!this.keysPromise) {\n const encoder = new TextEncoder();\n this.keysPromise = Promise.all(\n this.secrets.map((s) =>\n globalThis.crypto.subtle.importKey(\n 'raw',\n encoder.encode(s),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['verify'],\n ),\n ),\n );\n }\n return this.keysPromise;\n }\n}\n\nfunction normalizeHeaders(\n headers: Record<string, string | undefined> | Headers,\n): Record<string, string | undefined> {\n const result: Record<string, string | undefined> = {};\n if (headers instanceof Headers) {\n headers.forEach((value, key) => {\n result[key.toLowerCase()] = value;\n });\n } else {\n for (const key of Object.keys(headers)) {\n result[key.toLowerCase()] = headers[key];\n }\n }\n return result;\n}\n\nfunction hexDecode(hex: string): ArrayBuffer {\n if (hex.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(hex)) {\n throw new Error('Invalid hex string');\n }\n const length = hex.length / 2;\n const buffer = new ArrayBuffer(length);\n const view = new DataView(buffer);\n for (let i = 0; i < length; i++) {\n view.setUint8(i, Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16));\n }\n return buffer;\n}\n","import { BaseClient, DEFAULT_BASE_URL } from './client';\nimport type { ChronosOptions } from './types';\nimport { DEFAULT_POLL_WAIT_TIME_SECONDS, Worker } from './worker';\n\n/**\n * The Chronos SDK client. Composes worker and (future) REST resource\n * subclients from a single instance.\n *\n * @example\n * ```ts\n * import { Chronos } from '@chronos.sh/sdk';\n *\n * const chronos = new Chronos({ apiKey: process.env.CHRONOS_API_KEY! });\n *\n * chronos.worker.handle<{ to: string }>('send-email', async (ctx) => {\n * await sendEmail(ctx.payload.to);\n * return { sent: true };\n * });\n *\n * await chronos.worker.start();\n * ```\n */\nexport class Chronos {\n /** Long-poll worker for executing pull-mode jobs. */\n readonly worker: Worker;\n\n /**\n * @param options - Client configuration. Only `apiKey` is required.\n * @throws {ChronosConfigError} If `apiKey` is missing or any option fails validation.\n */\n constructor(options: ChronosOptions) {\n const client = new BaseClient(options);\n this.worker = new Worker(client, options);\n }\n}\n\nexport {\n ChronosApiError,\n type ChronosApiErrorOptions,\n ChronosConfigError,\n ChronosError,\n ChronosHandlerError,\n ChronosNetworkError,\n ChronosRateLimitError,\n ChronosWebhookVerificationError,\n type RateLimitInfo,\n} from './errors';\nexport { SDK_VERSION } from './internal/version';\nexport type {\n ChronosContext,\n ChronosHandler,\n ChronosHandlerResult,\n ChronosLogger,\n ChronosOptions,\n ChronosSchedule,\n FetchLike,\n WebhookOptions,\n} from './types';\nexport { DEFAULT_TIMESTAMP_TOLERANCE_SECONDS, Webhook } from './webhook';\nexport { DEFAULT_BASE_URL, DEFAULT_POLL_WAIT_TIME_SECONDS };\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/errors.ts","../src/internal/logger.ts","../src/internal/validate.ts","../src/client.ts","../src/internal/id.ts","../src/internal/runtime.ts","../src/internal/version.ts","../src/worker.ts","../src/webhook.ts","../src/index.ts"],"sourcesContent":["/**\n * Base class for all errors thrown by the Chronos SDK. Catch this in a\n * single `catch` to handle any SDK failure generically; use the subclasses\n * to branch on cause.\n *\n * @example\n * ```ts\n * import { Chronos, ChronosError } from '@chronos.sh/sdk';\n *\n * const chronos = new Chronos({ apiKey: 'chrns_...' });\n * try {\n * await chronos.worker.start();\n * } catch (err) {\n * if (err instanceof ChronosError) {\n * console.error('Chronos failed:', err.message);\n * }\n * }\n * ```\n */\nexport class ChronosError extends Error {\n /** Original error/value that caused this SDK error, when one is available. */\n declare readonly cause?: unknown;\n\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = 'ChronosError';\n\n if (options && !('cause' in this)) {\n Object.defineProperty(this, 'cause', {\n value: options.cause,\n configurable: true,\n writable: true,\n });\n }\n }\n}\n\n/**\n * Thrown when SDK options fail validation at `new Chronos({ ... })`. Covers\n * `apiKey`, `baseUrl`, `pollWaitTimeSeconds`, and `retryDelayMs`.\n *\n * @example\n * ```ts\n * import { Chronos, ChronosConfigError } from '@chronos.sh/sdk';\n *\n * try {\n * const chronos = new Chronos({ apiKey: '' });\n * } catch (err) {\n * if (err instanceof ChronosConfigError) {\n * console.error('Invalid Chronos config:', err.message);\n * }\n * }\n * ```\n */\nexport class ChronosConfigError extends ChronosError {\n constructor(message: string) {\n super(message);\n this.name = 'ChronosConfigError';\n }\n}\n\n/** Rate-limit budget from `X-RateLimit-*` response headers. Present on non-429 responses. */\nexport type RateLimitInfo = {\n /** Maximum requests allowed per window. */\n limit: number;\n /** Requests remaining in the current window. */\n remaining: number;\n /** Seconds until the current window resets. */\n reset: number;\n};\n\n/** Options for constructing a {@link ChronosApiError}. */\nexport type ChronosApiErrorOptions = {\n /** HTTP status code returned by the Chronos API. */\n status: number;\n /** Application-level error code from the response envelope, when present. */\n code?: string;\n /** Full parsed response payload (envelope + data, or whatever the server returned). */\n body?: unknown;\n /** Value of the `X-Request-Id` response header, when present. Pair with server logs. */\n requestId?: string;\n /** Seconds to wait before retrying, from the `Retry-After` response header. Present on 429 and 5xx responses that include the header. */\n retryAfterSeconds?: number;\n /** Rate-limit budget from `X-RateLimit-*` headers. Present on non-429 responses. */\n rateLimit?: RateLimitInfo;\n};\n\n/**\n * Thrown when the Chronos API responds with a non-2xx status or a\n * `success: false` envelope. Carries HTTP `status`, the application\n * `code`, parsed `body`, the API's `X-Request-Id`, and — when the\n * relevant headers are present — rate-limit metadata\n * (`retryAfterSeconds` on 429 and 5xx, `rateLimit` on non-429 errors).\n *\n * `instanceof ChronosApiError` means the server replied;\n * network/transport failures throw {@link ChronosNetworkError} instead.\n * For 429 specifically, see {@link ChronosRateLimitError}.\n *\n * @example\n * ```ts\n * import { ChronosApiError } from '@chronos.sh/sdk';\n *\n * function handleSdkError(err: unknown) {\n * if (err instanceof ChronosApiError) {\n * if (err.status === 401) return refreshAuth();\n * console.error('API error', { status: err.status, requestId: err.requestId });\n * }\n * }\n * ```\n */\nexport class ChronosApiError extends ChronosError {\n /** HTTP status code returned by the Chronos API. */\n readonly status: number;\n /** Application-level error code from the response envelope, when present. */\n readonly code?: string;\n /** Full parsed response payload (envelope + data, or whatever the server returned). */\n readonly body?: unknown;\n /** Value of the `X-Request-Id` response header, when present. */\n readonly requestId?: string;\n /** Seconds to wait before retrying, from the `Retry-After` response header. Present on 429 and 5xx responses that include the header. */\n readonly retryAfterSeconds?: number;\n /** Rate-limit budget from `X-RateLimit-*` headers. Present on non-429 responses. */\n readonly rateLimit?: RateLimitInfo;\n\n constructor(message: string, options: ChronosApiErrorOptions) {\n super(message);\n this.name = 'ChronosApiError';\n this.status = options.status;\n this.code = options.code;\n this.body = options.body;\n this.requestId = options.requestId;\n this.retryAfterSeconds = options.retryAfterSeconds;\n this.rateLimit = options.rateLimit;\n }\n}\n\n/**\n * Thrown when the Chronos API responds with `429 Too Many Requests`.\n * Subclass of {@link ChronosApiError} — `instanceof ChronosRateLimitError`\n * narrows to a rate-limited response. `retryAfterSeconds` is set when the\n * response includes a `Retry-After` header; otherwise it is `undefined`.\n *\n * @example\n * ```ts\n * import { ChronosRateLimitError } from '@chronos.sh/sdk';\n *\n * function handleSdkError(err: unknown) {\n * if (err instanceof ChronosRateLimitError && err.retryAfterSeconds) {\n * console.warn(`Rate limited — retry after ${err.retryAfterSeconds}s`);\n * }\n * }\n * ```\n */\nexport class ChronosRateLimitError extends ChronosApiError {\n constructor(message: string, options: ChronosApiErrorOptions) {\n super(message, options);\n this.name = 'ChronosRateLimitError';\n }\n}\n\n/**\n * Thrown when a request fails at the transport layer — DNS failure, TCP\n * reset, connection refused, or a connection dropped mid-response. The\n * original error is available on `.cause`.\n *\n * Caller-supplied abort signals propagate unwrapped — `instanceof\n * ChronosNetworkError` always means a transport failure, not a graceful\n * shutdown. The SDK's own transport timeout throws the\n * {@link ChronosTimeoutError} subclass.\n *\n * @example\n * ```ts\n * import { ChronosNetworkError } from '@chronos.sh/sdk';\n *\n * function handleSdkError(err: unknown) {\n * if (err instanceof ChronosNetworkError) {\n * console.warn('Transport blip', { cause: err.cause });\n * }\n * }\n * ```\n */\nexport class ChronosNetworkError extends ChronosError {\n /** Original error/value rejected by the underlying `fetch`. */\n declare readonly cause: unknown;\n\n constructor(message: string, options: { cause: unknown }) {\n super(message, options);\n this.name = 'ChronosNetworkError';\n }\n}\n\n/**\n * Thrown when an HTTP request exceeds the configured `timeoutMs` before the\n * response completes. Subclass of {@link ChronosNetworkError}, so generic\n * transport-failure handling catches it; the underlying rejection is on\n * `.cause`.\n *\n * @example\n * ```ts\n * import { ChronosTimeoutError } from '@chronos.sh/sdk';\n *\n * function handleSdkError(err: unknown) {\n * if (err instanceof ChronosTimeoutError) {\n * console.warn('Chronos request timed out', err.message);\n * }\n * }\n * ```\n */\nexport class ChronosTimeoutError extends ChronosNetworkError {\n constructor(message: string, options: { cause: unknown }) {\n super(message, options);\n this.name = 'ChronosTimeoutError';\n }\n}\n\n/**\n * Wraps an exception thrown by a user-supplied {@link ChronosHandler}. The\n * original error is on `.cause`; `.message` is copied from the original so\n * the SDK reports it to the API as the failure reason.\n *\n * @example\n * ```ts\n * import { ChronosHandlerError } from '@chronos.sh/sdk';\n *\n * if (err instanceof ChronosHandlerError) {\n * console.error('Handler threw', err.cause);\n * }\n * ```\n */\nexport class ChronosHandlerError extends ChronosError {\n /** Original error/value thrown by the user-supplied handler. */\n declare readonly cause: unknown;\n\n constructor(message: string, options: { cause: unknown }) {\n super(message, options);\n this.name = 'ChronosHandlerError';\n }\n}\n\n/**\n * Thrown by {@link Webhook.verify} when signature verification fails.\n * Covers missing or malformed headers, expired timestamps, and signature\n * mismatches.\n *\n * @example\n * ```ts\n * import { Webhook, ChronosWebhookVerificationError } from '@chronos.sh/sdk';\n *\n * const wh = new Webhook(process.env.CHRONOS_SIGNING_SECRET!);\n * try {\n * const payload = await wh.verify(rawBody, headers);\n * } catch (err) {\n * if (err instanceof ChronosWebhookVerificationError) {\n * console.error('Webhook verification failed:', err.message);\n * }\n * }\n * ```\n */\nexport class ChronosWebhookVerificationError extends ChronosError {\n constructor(message: string) {\n super(message);\n this.name = 'ChronosWebhookVerificationError';\n }\n}\n","import type { ChronosLogger } from '../types';\n\nexport const defaultLogger: ChronosLogger = {\n debug: (message, meta) => logToConsole(console.debug, message, meta),\n info: (message, meta) => logToConsole(console.info, message, meta),\n warn: (message, meta) => logToConsole(console.warn, message, meta),\n error: (message, meta) => logToConsole(console.error, message, meta),\n};\n\nfunction logToConsole(\n method: (message?: unknown, ...optionalParams: unknown[]) => void,\n message: string,\n meta?: Record<string, unknown>,\n): void {\n if (meta) {\n method(message, meta);\n } else {\n method(message);\n }\n}\n","import { ChronosConfigError, ChronosError } from '../errors';\n\nexport const MAX_HANDLER_NAME_LENGTH = 255;\nexport const MAX_POLL_WAIT_TIME_SECONDS = 20;\n\nexport function validateApiKey(apiKey: string | undefined): string {\n const trimmed = apiKey?.trim();\n if (!trimmed) {\n throw new ChronosConfigError('Chronos apiKey is required');\n }\n return trimmed;\n}\n\nexport function validatePollWaitTime(seconds: number): void {\n if (!Number.isInteger(seconds) || seconds < 0 || seconds > MAX_POLL_WAIT_TIME_SECONDS) {\n throw new ChronosConfigError(\n `pollWaitTimeSeconds must be an integer between 0 and ${MAX_POLL_WAIT_TIME_SECONDS}`,\n );\n }\n}\n\nexport function validateRetryDelayMs(ms: number): void {\n if (!Number.isFinite(ms) || ms < 0) {\n throw new ChronosConfigError('retryDelayMs must be a non-negative number');\n }\n}\n\n// setTimeout silently clamps delays above 2^31-1 to 1ms.\nexport const MAX_TIMEOUT_MS = 2_147_483_647;\n\nexport function validateTimeoutMs(ms: number): void {\n if (!Number.isInteger(ms) || ms <= 0 || ms > MAX_TIMEOUT_MS) {\n throw new ChronosConfigError(\n `timeoutMs must be a positive integer no greater than ${MAX_TIMEOUT_MS}`,\n );\n }\n}\n\nexport function validateWorkerId(id: string): string {\n const trimmed = id.trim();\n if (trimmed.length === 0 || trimmed.length > 255) {\n throw new ChronosConfigError('workerId must be 1-255 characters');\n }\n return trimmed;\n}\n\nexport function normalizeHandlerName(name: string): string {\n const normalized = name.trim();\n\n if (!normalized) {\n throw new ChronosError('Handler name is required');\n }\n\n if (normalized.length > MAX_HANDLER_NAME_LENGTH) {\n throw new ChronosError(`Handler name must be ${MAX_HANDLER_NAME_LENGTH} characters or fewer`);\n }\n\n return normalized;\n}\n","import {\n ChronosApiError,\n ChronosConfigError,\n ChronosError,\n ChronosNetworkError,\n ChronosRateLimitError,\n ChronosTimeoutError,\n type RateLimitInfo,\n} from './errors';\nimport { defaultLogger } from './internal/logger';\nimport { validateApiKey, validateTimeoutMs } from './internal/validate';\nimport type { ChronosLogger, ChronosOptions, FetchLike } from './types';\n\n/** Default Chronos API base URL. */\nexport const DEFAULT_BASE_URL = 'https://api.chronos.sh';\n\n/** Default per-request transport timeout in milliseconds. */\nexport const DEFAULT_TIMEOUT_MS = 30_000;\n\ntype ApiResponse<T> = {\n success: boolean;\n message?: string;\n code?: string;\n data: T;\n};\n\ntype RequestOptions = {\n signal?: AbortSignal;\n timeoutMs?: number;\n};\n\nexport class BaseClient {\n readonly apiKey: string;\n readonly baseUrl: string;\n readonly fetch: FetchLike;\n readonly logger: ChronosLogger;\n readonly timeoutMs: number;\n private readonly headers: Record<string, string>;\n\n constructor(options: ChronosOptions) {\n this.apiKey = validateApiKey(options.apiKey);\n this.baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL);\n this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis);\n this.logger = options.logger ?? defaultLogger;\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n validateTimeoutMs(this.timeoutMs);\n this.headers = {\n 'content-type': 'application/json',\n authorization: `Bearer ${this.apiKey}`,\n };\n }\n\n async request<T>(\n path: string,\n body: Record<string, unknown>,\n options: RequestOptions = {},\n ): Promise<T> {\n const { signal } = options;\n const timeoutMs = options.timeoutMs ?? this.timeoutMs;\n\n // Not AbortSignal.any: Node pins each composite onto long-lived source\n // signals (like the poll signal) forever, leaking per claim.\n const controller = new AbortController();\n let timedOut = false;\n const timer = setTimeout(() => {\n if (controller.signal.aborted) {\n return;\n }\n timedOut = true;\n controller.abort(\n new DOMException(`Chronos API request timed out after ${timeoutMs}ms`, 'TimeoutError'),\n );\n }, timeoutMs);\n const onCallerAbort = () => controller.abort(signal?.reason);\n if (signal?.aborted) {\n onCallerAbort();\n } else {\n signal?.addEventListener('abort', onCallerAbort, { once: true });\n }\n\n try {\n let response: Response;\n try {\n response = await this.fetch(`${this.baseUrl}${path}`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n } catch (err) {\n if (signal?.aborted && !timedOut) {\n throw err;\n }\n if (timedOut) {\n throw timeoutError(timeoutMs, err);\n }\n const message = err instanceof Error ? err.message : String(err);\n throw new ChronosNetworkError(`Chronos API request failed: ${message}`, { cause: err });\n }\n\n // A half-open connection can stall after headers arrive, so the\n // timeout must cover the body read too.\n let payload: ApiResponse<T> | null;\n try {\n payload = (await response.json()) as ApiResponse<T>;\n } catch (err) {\n // Aborted-state check, not reason identity: some runtimes wrap the\n // abort reason during body reads.\n if (signal?.aborted && !timedOut) {\n throw err;\n }\n if (timedOut) {\n throw timeoutError(timeoutMs, err);\n }\n if (err instanceof SyntaxError) {\n payload = null;\n } else {\n const message = err instanceof Error ? err.message : String(err);\n throw new ChronosNetworkError(`Chronos API response failed: ${message}`, { cause: err });\n }\n }\n\n const envelope = isEnvelope(payload) ? (payload as ApiResponse<T>) : undefined;\n\n if (!response.ok || (envelope && !envelope.success)) {\n const is429 = response.status === 429;\n const retryAfterApplies = is429 || response.status >= 500;\n const ErrorClass = is429 ? ChronosRateLimitError : ChronosApiError;\n throw new ErrorClass(apiErrorMessage(response, envelope), {\n status: response.status,\n code: typeof envelope?.code === 'string' ? envelope.code : undefined,\n body: payload,\n requestId: response.headers.get('x-request-id') ?? undefined,\n retryAfterSeconds: retryAfterApplies\n ? parseIntHeader(response.headers, 'retry-after')\n : undefined,\n rateLimit: is429 ? undefined : parseRateLimitHeaders(response.headers),\n });\n }\n\n if (!envelope) {\n // Plain ChronosError, not ChronosApiError — a malformed 200 body is\n // an infrastructure anomaly that should be retried, not a definitive\n // API rejection.\n throw new ChronosError('Chronos API returned an invalid response');\n }\n\n return envelope.data;\n } finally {\n clearTimeout(timer);\n signal?.removeEventListener('abort', onCallerAbort);\n }\n }\n}\n\nfunction timeoutError(timeoutMs: number, cause: unknown): ChronosTimeoutError {\n return new ChronosTimeoutError(`Chronos API request timed out after ${timeoutMs}ms`, { cause });\n}\n\nfunction normalizeBaseUrl(baseUrl: string): string {\n const normalized = baseUrl.trim().replace(/\\/+$/, '');\n if (!normalized) {\n throw new ChronosConfigError('Chronos baseUrl is required');\n }\n return normalized;\n}\n\nfunction apiErrorMessage(\n response: Response,\n envelope: Record<string, unknown> | undefined,\n): string {\n if (typeof envelope?.message === 'string' && envelope.message.trim()) {\n return envelope.message;\n }\n\n return response.ok\n ? 'Chronos API returned an invalid response'\n : `Chronos API request failed with status ${response.status}`;\n}\n\nfunction parseIntHeader(headers: Headers, name: string): number | undefined {\n const raw = headers.get(name);\n if (raw === null || !/^\\d+$/.test(raw)) {\n return undefined;\n }\n return Number.parseInt(raw, 10);\n}\n\nfunction parseRateLimitHeaders(headers: Headers): RateLimitInfo | undefined {\n const limit = parseIntHeader(headers, 'x-ratelimit-limit');\n const remaining = parseIntHeader(headers, 'x-ratelimit-remaining');\n const reset = parseIntHeader(headers, 'x-ratelimit-reset');\n\n if (limit === undefined || remaining === undefined || reset === undefined) {\n return undefined;\n }\n return { limit, remaining, reset };\n}\n\n// Malformed bodies (arrays, primitives, missing `success`) are routed to the\n// invalid-response path, not classified as API errors.\nfunction isEnvelope(value: unknown): value is Record<string, unknown> & { success: boolean } {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return false;\n }\n return typeof (value as Record<string, unknown>).success === 'boolean';\n}\n","export function generateWorkerId(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(8));\n let hex = '';\n for (const b of bytes) {\n hex += b.toString(16).padStart(2, '0');\n }\n return `w_${hex}`;\n}\n","export function detectRuntime(): string {\n const g = globalThis as unknown as Record<string, Record<string, unknown> | undefined>;\n\n if (g.Bun != null) {\n return `bun/${(g.Bun.version as string | undefined) ?? 'unknown'}`;\n }\n\n if (g.Deno != null) {\n const version = g.Deno.version as Record<string, string> | undefined;\n return `deno/${version?.deno ?? 'unknown'}`;\n }\n\n if (typeof process !== 'undefined' && process.versions?.node) {\n return `node/${process.versions.node}`;\n }\n\n return 'unknown';\n}\n","declare const __SDK_VERSION__: string;\n\nexport const SDK_VERSION: string = __SDK_VERSION__;\n","import type { BaseClient } from './client';\nimport { ChronosApiError, ChronosError, ChronosHandlerError, ChronosNetworkError } from './errors';\nimport { generateWorkerId } from './internal/id';\nimport { detectRuntime } from './internal/runtime';\nimport {\n MAX_TIMEOUT_MS,\n normalizeHandlerName,\n validatePollWaitTime,\n validateRetryDelayMs,\n validateWorkerId,\n} from './internal/validate';\nimport { SDK_VERSION } from './internal/version';\nimport type {\n ChronosContext,\n ChronosHandler,\n ChronosHandlerResult,\n ChronosOptions,\n ChronosSchedule,\n} from './types';\n\n/** Default worker long-poll wait time in seconds. Equal to the API maximum. */\nexport const DEFAULT_POLL_WAIT_TIME_SECONDS = 20;\nconst DEFAULT_RETRY_DELAY_MS = 1_000;\n// Retry-After on 5xx often comes from proxies/CDNs with browser-oriented\n// values (e.g. 3600); uncapped it would park the worker for hours.\nconst MAX_RETRY_AFTER_MS = 60_000;\nconst RESULT_REPORT_MAX_ATTEMPTS = 3;\nconst MAX_REPORTED_ERROR_LENGTH = 4_096;\nconst DEFAULT_HANDLER_ERROR = 'Chronos handler failed';\n\ntype ResultStatus = 'completed' | 'failed' | 'timeout';\n\ntype HandlerOutcome = { timedOut: false; result: ChronosHandlerResult } | { timedOut: true };\n\ntype ClaimedJob = {\n job_id: string;\n execution_id: string;\n handler: string;\n scheduled_for: string;\n attempt: number;\n timeout: number;\n payload: unknown;\n schedule: ChronosSchedule | null;\n};\n\n/**\n * Long-poll worker. Claims jobs from the Chronos API, dispatches them to\n * registered handlers, and reports results.\n *\n * Construct via `new Chronos({ apiKey }).worker` rather than directly.\n */\nexport class Worker {\n private readonly client: BaseClient;\n private readonly pollWaitTimeSeconds: number;\n private readonly retryDelayMs: number;\n private readonly workerId: string;\n private readonly runtime: string;\n private readonly handlers = new Map<string, ChronosHandler<unknown>>();\n private handlerNames: string[] = [];\n private startPromise?: Promise<void>;\n private pollController?: AbortController;\n private startedAt = 0;\n\n constructor(client: BaseClient, options: ChronosOptions) {\n this.client = client;\n this.pollWaitTimeSeconds = options.pollWaitTimeSeconds ?? DEFAULT_POLL_WAIT_TIME_SECONDS;\n this.retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;\n this.workerId =\n options.workerId !== undefined ? validateWorkerId(options.workerId) : generateWorkerId();\n this.runtime = detectRuntime();\n\n validatePollWaitTime(this.pollWaitTimeSeconds);\n validateRetryDelayMs(this.retryDelayMs);\n }\n\n /**\n * Register a handler for a named job type. Invoked when the Chronos API\n * claims a job whose `handler` field matches `name`. Names are trimmed and\n * must be 1–255 characters.\n *\n * @param name - Handler name. Must match the schedule's `handler` on the API side.\n * @param handler - Async function invoked with the job context. Return a\n * plain object to record a result, or `undefined` for none.\n * @returns The Worker, for chaining.\n * @throws {ChronosError} If the name is invalid, already registered, or `handler` is not a function.\n *\n * @example\n * ```ts\n * chronos.worker\n * .handle('send-email', async (ctx) => ({ sent: true }))\n * .handle('cleanup', async () => undefined);\n * ```\n */\n handle<TPayload = unknown>(name: string, handler: ChronosHandler<TPayload>): this {\n const normalizedName = normalizeHandlerName(name);\n\n if (this.handlers.has(normalizedName)) {\n throw new ChronosError(`Handler \"${normalizedName}\" is already registered`);\n }\n\n if (typeof handler !== 'function') {\n throw new ChronosError(`Handler \"${normalizedName}\" must be a function`);\n }\n\n this.handlers.set(normalizedName, handler as ChronosHandler<unknown>);\n this.handlerNames.push(normalizedName);\n return this;\n }\n\n /**\n * Begin long-polling for jobs. The returned promise resolves when\n * {@link Worker.stop} is called and any in-flight job completes.\n *\n * @throws {ChronosError} Synchronously, if no handlers are registered or the worker is already running.\n */\n start(): Promise<void> {\n if (this.startPromise) {\n throw new ChronosError('Chronos worker is already started');\n }\n\n if (this.handlers.size === 0) {\n throw new ChronosError('Register at least one handler before starting Chronos');\n }\n\n this.startedAt = performance.now();\n this.pollController = new AbortController();\n this.startPromise = this.runLoop().finally(() => {\n this.startPromise = undefined;\n this.pollController = undefined;\n });\n\n return this.startPromise;\n }\n\n /**\n * Request graceful shutdown. The poll loop is aborted immediately; any\n * in-flight handler that has not exceeded its timeout, along with the\n * subsequent result-report, is allowed to complete. Timed-out handlers\n * are abandoned (their {@link ChronosContext.signal} fires, but the SDK\n * does not wait for them). Every HTTP request is bounded by the `timeoutMs`\n * transport timeout and retry waits are skipped once stop is requested, so\n * the returned promise resolves in bounded time. Returns the same promise\n * as the active {@link Worker.start}, or a resolved promise if the worker\n * isn't running.\n */\n stop(): Promise<void> {\n this.pollController?.abort();\n return this.startPromise ?? Promise.resolve();\n }\n\n private get isStopped(): boolean {\n return this.pollController?.signal.aborted ?? true;\n }\n\n private async runLoop(): Promise<void> {\n while (!this.isStopped) {\n try {\n const job = await this.claimJob();\n if (job) {\n await this.processJob(job);\n }\n } catch (err) {\n if (this.isStopped) {\n break;\n }\n\n this.client.logger.error('Chronos poll loop error', { err: errorToLogValue(err) });\n await sleep(retryDelayFor(err, this.retryDelayMs), this.pollController?.signal);\n }\n }\n }\n\n private async claimJob(): Promise<ClaimedJob | null> {\n if (this.handlerNames.length === 0) {\n throw new ChronosError('Cannot claim jobs without registered handlers');\n }\n\n return this.client.request<ClaimedJob | null>(\n '/v1/workers/jobs/claim',\n {\n wait_time_seconds: this.pollWaitTimeSeconds,\n handlers: this.handlerNames,\n worker: {\n id: this.workerId,\n sdk_version: SDK_VERSION,\n runtime: this.runtime,\n uptime_ms: Math.round(performance.now() - this.startedAt),\n },\n },\n {\n signal: this.pollController?.signal,\n // The intentional long-poll wait must not count against the\n // transport bound.\n timeoutMs: Math.min(\n this.pollWaitTimeSeconds * 1_000 + this.client.timeoutMs,\n MAX_TIMEOUT_MS,\n ),\n },\n );\n }\n\n private async processJob(job: ClaimedJob): Promise<void> {\n const handler = this.handlers.get(job.handler);\n if (!handler) {\n return this.handleUnregisteredJob(job);\n }\n\n const controller = new AbortController();\n let outcome: HandlerOutcome;\n try {\n outcome = await this.runHandlerWithTimeout(\n handler,\n createContext(job, controller.signal),\n job.timeout * 1_000,\n controller,\n );\n } catch (err) {\n const message = errorMessage(err);\n const handlerErr = new ChronosHandlerError(message, { cause: err });\n this.client.logger.error('Chronos handler failed', {\n ...jobLogMeta(job),\n err: errorToLogValue(handlerErr),\n });\n await this.safeReportFailed(job, message);\n return;\n }\n\n if (outcome.timedOut) {\n this.client.logger.error('Chronos handler timed out', {\n ...jobLogMeta(job),\n timeout: job.timeout,\n });\n try {\n await this.reportTimeout(job.execution_id);\n } catch (err) {\n this.logResultReportFailure(err, job, 'timeout');\n }\n return;\n }\n\n let result: Record<string, unknown> | undefined;\n try {\n result = normalizeHandlerResult(outcome.result);\n } catch (err) {\n this.client.logger.error('Chronos handler returned invalid result', {\n ...jobLogMeta(job),\n err: errorToLogValue(err),\n });\n await this.safeReportFailed(job, errorMessage(err));\n return;\n }\n\n try {\n await this.reportCompleted(job.execution_id, result);\n } catch (err) {\n this.logResultReportFailure(err, job, 'completed');\n }\n }\n\n private runHandlerWithTimeout(\n handler: ChronosHandler<unknown>,\n ctx: ChronosContext<unknown>,\n timeoutMs: number,\n controller: AbortController,\n ): Promise<HandlerOutcome> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const handlerPromise = Promise.resolve(handler(ctx)).then(\n (result): HandlerOutcome => ({ timedOut: false, result }),\n );\n handlerPromise.catch(() => {});\n\n const timeoutPromise = new Promise<HandlerOutcome>((resolve) => {\n timer = setTimeout(() => {\n controller.abort();\n resolve({ timedOut: true });\n }, timeoutMs);\n });\n\n return Promise.race([handlerPromise, timeoutPromise]).finally(() => {\n clearTimeout(timer);\n });\n }\n\n private async handleUnregisteredJob(job: ClaimedJob): Promise<void> {\n const message = `Chronos SDK received job for unregistered handler \"${job.handler}\"`;\n this.client.logger.error(message, jobLogMeta(job));\n await this.safeReportFailed(job, message);\n }\n\n private async safeReportFailed(job: ClaimedJob, message: string): Promise<void> {\n try {\n await this.reportFailed(job.execution_id, message);\n } catch (err) {\n this.logResultReportFailure(err, job, 'failed');\n }\n }\n\n private async reportCompleted(\n executionId: string,\n result: Record<string, unknown> | undefined,\n ): Promise<void> {\n await this.reportResultWithRetry(executionId, {\n status: 'completed',\n ...(result === undefined ? {} : { result }),\n });\n }\n\n private async reportTimeout(executionId: string): Promise<void> {\n await this.reportResultWithRetry(executionId, { status: 'timeout' });\n }\n\n private async reportFailed(executionId: string, error: string): Promise<void> {\n await this.reportResultWithRetry(executionId, {\n status: 'failed',\n error: truncate(error, MAX_REPORTED_ERROR_LENGTH),\n });\n }\n\n private async reportResultWithRetry(\n executionId: string,\n body: Record<string, unknown>,\n ): Promise<void> {\n let lastErr: unknown;\n\n for (let attempt = 1; attempt <= RESULT_REPORT_MAX_ATTEMPTS; attempt++) {\n try {\n await this.client.request(\n `/v1/workers/executions/${encodeURIComponent(executionId)}/result`,\n body,\n );\n return;\n } catch (err) {\n const apiErr = err instanceof ChronosApiError ? err : undefined;\n\n if (apiErr?.status === 409) {\n this.client.logger.warn('Chronos result discarded; execution already terminal', {\n executionId,\n status: body.status,\n code: apiErr.code,\n });\n return;\n }\n\n if (apiErr && isTerminalReportError(apiErr.status)) {\n throw apiErr;\n }\n\n lastErr = err;\n if (attempt === RESULT_REPORT_MAX_ATTEMPTS) {\n break;\n }\n\n this.client.logger.warn('Chronos result report failed; retrying', {\n err: errorToLogValue(err),\n executionId,\n status: body.status,\n attempt,\n maxAttempts: RESULT_REPORT_MAX_ATTEMPTS,\n });\n // The signal skips the wait after stop(), never the retry itself:\n // the report is still allowed to complete.\n await sleep(retryDelayFor(err, this.retryDelayMs), this.pollController?.signal);\n }\n }\n\n throw lastErr;\n }\n\n private logResultReportFailure(err: unknown, job: ClaimedJob, status: ResultStatus): void {\n const isTerminal = err instanceof ChronosApiError && isTerminalReportError(err.status);\n this.client.logger.error(\n isTerminal\n ? 'Chronos result report rejected by API'\n : 'Chronos result report failed after retries',\n { ...jobLogMeta(job), err: errorToLogValue(err), status },\n );\n }\n}\n\nfunction createContext(job: ClaimedJob, signal: AbortSignal): ChronosContext<unknown> {\n return {\n jobId: job.job_id,\n executionId: job.execution_id,\n handler: job.handler,\n payload: job.payload,\n scheduledFor: new Date(job.scheduled_for),\n attempt: job.attempt,\n timeout: job.timeout,\n signal,\n schedule: job.schedule,\n };\n}\n\nfunction jobLogMeta(job: ClaimedJob): Record<string, unknown> {\n return {\n jobId: job.job_id,\n executionId: job.execution_id,\n handler: job.handler,\n };\n}\n\nfunction normalizeHandlerResult(result: ChronosHandlerResult): Record<string, unknown> | undefined {\n if (result === undefined) {\n return undefined;\n }\n\n if (!isPlainObject(result)) {\n throw new ChronosError('Chronos handler result must be a plain object or undefined');\n }\n\n try {\n JSON.stringify(result);\n } catch (err) {\n const reason = err instanceof Error ? err.message : 'value is not JSON-encodable';\n throw new ChronosError(`Chronos handler result is not JSON-encodable: ${reason}`);\n }\n\n return result;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return false;\n }\n\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\nfunction errorMessage(err: unknown): string {\n const message = err instanceof Error ? err.message : String(err);\n return message.trim() || DEFAULT_HANDLER_ERROR;\n}\n\nfunction errorToLogValue(err: unknown): unknown {\n if (!(err instanceof Error)) {\n return err;\n }\n\n const base = { name: err.name, message: err.message, stack: err.stack };\n\n if (err instanceof ChronosApiError) {\n return {\n ...base,\n status: err.status,\n code: err.code,\n requestId: err.requestId,\n ...(err.retryAfterSeconds !== undefined && { retryAfterSeconds: err.retryAfterSeconds }),\n ...(err.rateLimit !== undefined && { rateLimit: err.rateLimit }),\n };\n }\n if (err instanceof ChronosNetworkError || err instanceof ChronosHandlerError) {\n return { ...base, cause: errorToLogValue(err.cause) };\n }\n return base;\n}\n\nfunction truncate(value: string, maxLength: number): string {\n return value.length <= maxLength ? value : value.slice(0, maxLength);\n}\n\n// Floored at fallbackMs: a proxy's Retry-After: 0 would otherwise hot-loop\n// against a down origin.\nfunction retryDelayFor(err: unknown, fallbackMs: number): number {\n if (err instanceof ChronosApiError && err.retryAfterSeconds !== undefined) {\n return Math.max(Math.min(err.retryAfterSeconds * 1_000, MAX_RETRY_AFTER_MS), fallbackMs);\n }\n return fallbackMs;\n}\n\n// 409 is handled by the caller before reaching here; 408/429 are transient.\nconst NON_TERMINAL_4XX = new Set([408, 409, 429]);\n\n// 200 reaches here only via an explicit `{ success: false }` envelope from BaseClient.\nfunction isTerminalReportError(status: number): boolean {\n if (status === 200) {\n return true;\n }\n if (NON_TERMINAL_4XX.has(status)) {\n return false;\n }\n return status >= 400 && status < 500;\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) {\n return Promise.resolve();\n }\n\n return new Promise((resolve) => {\n const timeout: ReturnType<typeof setTimeout> = setTimeout(done, ms);\n\n function done() {\n clearTimeout(timeout);\n signal?.removeEventListener('abort', done);\n resolve();\n }\n\n signal?.addEventListener('abort', done, { once: true });\n });\n}\n","import { ChronosWebhookVerificationError } from './errors';\nimport type { WebhookOptions } from './types';\n\nexport const DEFAULT_TIMESTAMP_TOLERANCE_SECONDS = 300;\n\nconst SIGNATURE_PREFIX = 'sha256=';\nconst REQUIRED_HEADERS = [\n 'x-chronos-signature',\n 'x-chronos-timestamp',\n 'x-chronos-delivery-id',\n] as const;\n\n/**\n * Verifies webhook signatures on Chronos push deliveries.\n *\n * @example\n * ```ts\n * import { Webhook } from '@chronos.sh/sdk';\n *\n * const wh = new Webhook(process.env.CHRONOS_SIGNING_SECRET!);\n *\n * // In your request handler:\n * const payload = await wh.verify(rawBody, {\n * 'x-chronos-signature': req.headers['x-chronos-signature'],\n * 'x-chronos-timestamp': req.headers['x-chronos-timestamp'],\n * 'x-chronos-delivery-id': req.headers['x-chronos-delivery-id'],\n * });\n * ```\n */\nexport class Webhook {\n private readonly secrets: string[];\n private readonly toleranceSeconds: number;\n private keysPromise?: Promise<CryptoKey[]>;\n\n /**\n * @param secret - Signing secret or array of secrets (for key rotation).\n * @param options - Optional configuration.\n * @throws {ChronosWebhookVerificationError} If no valid secret is provided.\n */\n constructor(secret: string | string[], options?: WebhookOptions) {\n const secrets = Array.isArray(secret) ? secret : [secret];\n this.secrets = secrets\n .filter((s) => typeof s === 'string' && s.trim().length > 0)\n .map((s) => s.trim());\n\n if (this.secrets.length === 0) {\n throw new ChronosWebhookVerificationError(\n 'At least one non-empty signing secret is required',\n );\n }\n\n const tolerance = options?.toleranceSeconds ?? DEFAULT_TIMESTAMP_TOLERANCE_SECONDS;\n if (!Number.isFinite(tolerance) || tolerance < 0) {\n throw new ChronosWebhookVerificationError(\n 'toleranceSeconds must be a finite non-negative number',\n );\n }\n this.toleranceSeconds = tolerance;\n }\n\n /**\n * Verifies the webhook signature and returns the parsed request body.\n *\n * @param body - Raw request body as a string. Must be the exact bytes received — not re-serialized.\n * @param headers - Request headers. Keys are matched case-insensitively.\n * @returns The parsed JSON body on successful verification.\n * @throws {ChronosWebhookVerificationError} If any verification step fails.\n */\n async verify(\n body: string,\n headers: Record<string, string | undefined> | Headers,\n ): Promise<unknown> {\n const normalized = normalizeHeaders(headers);\n\n for (const name of REQUIRED_HEADERS) {\n const value = normalized[name];\n if (!value || typeof value !== 'string') {\n throw new ChronosWebhookVerificationError(`Missing required header: ${name}`);\n }\n }\n\n const signature = normalized['x-chronos-signature'] as string;\n const timestamp = normalized['x-chronos-timestamp'] as string;\n const deliveryId = normalized['x-chronos-delivery-id'] as string;\n\n if (!/^[0-9]+$/.test(timestamp)) {\n throw new ChronosWebhookVerificationError('Invalid timestamp');\n }\n\n const ts = Number(timestamp);\n if (ts <= 0) {\n throw new ChronosWebhookVerificationError('Invalid timestamp');\n }\n\n const now = Math.floor(Date.now() / 1000);\n const isStale = Math.abs(now - ts) > this.toleranceSeconds;\n if (this.toleranceSeconds > 0 && isStale) {\n throw new ChronosWebhookVerificationError('Timestamp outside tolerance');\n }\n\n if (!signature.startsWith(SIGNATURE_PREFIX)) {\n throw new ChronosWebhookVerificationError(\n `Invalid signature format: expected ${SIGNATURE_PREFIX}<hex>`,\n );\n }\n\n const expectedHex = signature.slice(SIGNATURE_PREFIX.length);\n if (expectedHex.length === 0) {\n throw new ChronosWebhookVerificationError(\n 'Invalid signature: expected 32-byte SHA-256 digest',\n );\n }\n\n let expectedBytes: ArrayBuffer;\n try {\n expectedBytes = hexDecode(expectedHex);\n } catch {\n throw new ChronosWebhookVerificationError('Invalid signature: malformed hex');\n }\n\n if (expectedBytes.byteLength !== 32) {\n throw new ChronosWebhookVerificationError(\n 'Invalid signature: expected 32-byte SHA-256 digest',\n );\n }\n\n const signedPayload = `${deliveryId}.${timestamp}.${body}`;\n const data = new TextEncoder().encode(signedPayload);\n\n try {\n const keys = await this.ensureKeys();\n for (const key of keys) {\n const valid = await globalThis.crypto.subtle.verify('HMAC', key, expectedBytes, data);\n if (valid) {\n try {\n return JSON.parse(body);\n } catch {\n throw new ChronosWebhookVerificationError('Verified payload is not valid JSON');\n }\n }\n }\n } catch (err) {\n if (err instanceof ChronosWebhookVerificationError) {\n throw err;\n }\n throw new ChronosWebhookVerificationError('Signature verification failed');\n }\n\n throw new ChronosWebhookVerificationError('Signature verification failed');\n }\n\n private ensureKeys(): Promise<CryptoKey[]> {\n if (!this.keysPromise) {\n const encoder = new TextEncoder();\n this.keysPromise = Promise.all(\n this.secrets.map((s) =>\n globalThis.crypto.subtle.importKey(\n 'raw',\n encoder.encode(s),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['verify'],\n ),\n ),\n );\n }\n return this.keysPromise;\n }\n}\n\nfunction normalizeHeaders(\n headers: Record<string, string | undefined> | Headers,\n): Record<string, string | undefined> {\n const result: Record<string, string | undefined> = {};\n if (headers instanceof Headers) {\n headers.forEach((value, key) => {\n result[key.toLowerCase()] = value;\n });\n } else {\n for (const key of Object.keys(headers)) {\n result[key.toLowerCase()] = headers[key];\n }\n }\n return result;\n}\n\nfunction hexDecode(hex: string): ArrayBuffer {\n if (hex.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(hex)) {\n throw new Error('Invalid hex string');\n }\n const length = hex.length / 2;\n const buffer = new ArrayBuffer(length);\n const view = new DataView(buffer);\n for (let i = 0; i < length; i++) {\n view.setUint8(i, Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16));\n }\n return buffer;\n}\n","import { BaseClient, DEFAULT_BASE_URL, DEFAULT_TIMEOUT_MS } from './client';\nimport type { ChronosOptions } from './types';\nimport { DEFAULT_POLL_WAIT_TIME_SECONDS, Worker } from './worker';\n\n/**\n * The Chronos SDK client. Composes worker and (future) REST resource\n * subclients from a single instance.\n *\n * @example\n * ```ts\n * import { Chronos } from '@chronos.sh/sdk';\n *\n * const chronos = new Chronos({ apiKey: process.env.CHRONOS_API_KEY! });\n *\n * chronos.worker.handle<{ to: string }>('send-email', async (ctx) => {\n * await sendEmail(ctx.payload.to);\n * return { sent: true };\n * });\n *\n * await chronos.worker.start();\n * ```\n */\nexport class Chronos {\n /** Long-poll worker for executing pull-mode jobs. */\n readonly worker: Worker;\n\n /**\n * @param options - Client configuration. Only `apiKey` is required.\n * @throws {ChronosConfigError} If `apiKey` is missing or any option fails validation.\n */\n constructor(options: ChronosOptions) {\n const client = new BaseClient(options);\n this.worker = new Worker(client, options);\n }\n}\n\nexport {\n ChronosApiError,\n type ChronosApiErrorOptions,\n ChronosConfigError,\n ChronosError,\n ChronosHandlerError,\n ChronosNetworkError,\n ChronosRateLimitError,\n ChronosTimeoutError,\n ChronosWebhookVerificationError,\n type RateLimitInfo,\n} from './errors';\nexport { SDK_VERSION } from './internal/version';\nexport type {\n ChronosContext,\n ChronosHandler,\n ChronosHandlerResult,\n ChronosLogger,\n ChronosOptions,\n ChronosSchedule,\n FetchLike,\n WebhookOptions,\n} from './types';\nexport { DEFAULT_TIMESTAMP_TOLERANCE_SECONDS, Webhook } from './webhook';\nexport { DEFAULT_BASE_URL, DEFAULT_POLL_WAIT_TIME_SECONDS, DEFAULT_TIMEOUT_MS };\n"],"mappings":"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"}