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

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
@@ -168,6 +168,31 @@ var ChronosHandlerError = class extends ChronosError {
168
168
  this.name = "ChronosHandlerError";
169
169
  }
170
170
  };
171
+ /**
172
+ * Thrown by {@link Webhook.verify} when signature verification fails.
173
+ * Covers missing or malformed headers, expired timestamps, and signature
174
+ * mismatches.
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * import { Webhook, ChronosWebhookVerificationError } from '@chronos.sh/sdk';
179
+ *
180
+ * const wh = new Webhook(process.env.CHRONOS_SIGNING_SECRET!);
181
+ * try {
182
+ * const payload = await wh.verify(rawBody, headers);
183
+ * } catch (err) {
184
+ * if (err instanceof ChronosWebhookVerificationError) {
185
+ * console.error('Webhook verification failed:', err.message);
186
+ * }
187
+ * }
188
+ * ```
189
+ */
190
+ var ChronosWebhookVerificationError = class extends ChronosError {
191
+ constructor(message) {
192
+ super(message);
193
+ this.name = "ChronosWebhookVerificationError";
194
+ }
195
+ };
171
196
  //#endregion
172
197
  //#region src/internal/logger.ts
173
198
  const defaultLogger = {
@@ -191,6 +216,11 @@ function validatePollWaitTime(seconds) {
191
216
  function validateRetryDelayMs(ms) {
192
217
  if (!Number.isFinite(ms) || ms < 0) throw new ChronosConfigError("retryDelayMs must be a non-negative number");
193
218
  }
219
+ function validateWorkerId(id) {
220
+ const trimmed = id.trim();
221
+ if (trimmed.length === 0 || trimmed.length > 255) throw new ChronosConfigError("workerId must be 1-255 characters");
222
+ return trimmed;
223
+ }
194
224
  function normalizeHandlerName(name) {
195
225
  const normalized = name.trim();
196
226
  if (!normalized) throw new ChronosError("Handler name is required");
@@ -284,6 +314,26 @@ function isEnvelope(value) {
284
314
  return typeof value.success === "boolean";
285
315
  }
286
316
  //#endregion
317
+ //#region src/internal/id.ts
318
+ function generateWorkerId() {
319
+ const bytes = crypto.getRandomValues(new Uint8Array(8));
320
+ let hex = "";
321
+ for (const b of bytes) hex += b.toString(16).padStart(2, "0");
322
+ return `w_${hex}`;
323
+ }
324
+ //#endregion
325
+ //#region src/internal/runtime.ts
326
+ function detectRuntime() {
327
+ const g = globalThis;
328
+ if (g.Bun != null) return `bun/${g.Bun.version ?? "unknown"}`;
329
+ if (g.Deno != null) return `deno/${g.Deno.version?.deno ?? "unknown"}`;
330
+ if (typeof process !== "undefined" && process.versions?.node) return `node/${process.versions.node}`;
331
+ return "unknown";
332
+ }
333
+ //#endregion
334
+ //#region src/internal/version.ts
335
+ const SDK_VERSION = "0.0.2-canary.fa8e22c";
336
+ //#endregion
287
337
  //#region src/worker.ts
288
338
  /** Default worker long-poll wait time in seconds. Equal to the API maximum. */
289
339
  const DEFAULT_POLL_WAIT_TIME_SECONDS = 20;
@@ -301,14 +351,19 @@ var Worker = class {
301
351
  client;
302
352
  pollWaitTimeSeconds;
303
353
  retryDelayMs;
354
+ workerId;
355
+ runtime;
304
356
  handlers = /* @__PURE__ */ new Map();
305
357
  handlerNames = [];
306
358
  startPromise;
307
359
  pollController;
360
+ startedAt = 0;
308
361
  constructor(client, options) {
309
362
  this.client = client;
310
363
  this.pollWaitTimeSeconds = options.pollWaitTimeSeconds ?? 20;
311
364
  this.retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
365
+ this.workerId = options.workerId !== void 0 ? validateWorkerId(options.workerId) : generateWorkerId();
366
+ this.runtime = detectRuntime();
312
367
  validatePollWaitTime(this.pollWaitTimeSeconds);
313
368
  validateRetryDelayMs(this.retryDelayMs);
314
369
  }
@@ -347,6 +402,7 @@ var Worker = class {
347
402
  start() {
348
403
  if (this.startPromise) throw new ChronosError("Chronos worker is already started");
349
404
  if (this.handlers.size === 0) throw new ChronosError("Register at least one handler before starting Chronos");
405
+ this.startedAt = performance.now();
350
406
  this.pollController = new AbortController();
351
407
  this.startPromise = this.runLoop().finally(() => {
352
408
  this.startPromise = void 0;
@@ -383,7 +439,13 @@ var Worker = class {
383
439
  if (this.handlerNames.length === 0) throw new ChronosError("Cannot claim jobs without registered handlers");
384
440
  return this.client.request("/v1/worker/jobs/claim", {
385
441
  wait_time_seconds: this.pollWaitTimeSeconds,
386
- handlers: this.handlerNames
442
+ handlers: this.handlerNames,
443
+ worker: {
444
+ id: this.workerId,
445
+ sdk_version: SDK_VERSION,
446
+ runtime: this.runtime,
447
+ uptime_ms: Math.round(performance.now() - this.startedAt)
448
+ }
387
449
  }, this.pollController?.signal);
388
450
  }
389
451
  async processJob(job) {
@@ -603,6 +665,124 @@ function sleep(ms, signal) {
603
665
  });
604
666
  }
605
667
  //#endregion
668
+ //#region src/webhook.ts
669
+ const DEFAULT_TIMESTAMP_TOLERANCE_SECONDS = 300;
670
+ const SIGNATURE_PREFIX = "sha256=";
671
+ const REQUIRED_HEADERS = [
672
+ "x-chronos-signature",
673
+ "x-chronos-timestamp",
674
+ "x-chronos-delivery-id"
675
+ ];
676
+ /**
677
+ * Verifies webhook signatures on Chronos push deliveries.
678
+ *
679
+ * @example
680
+ * ```ts
681
+ * import { Webhook } from '@chronos.sh/sdk';
682
+ *
683
+ * const wh = new Webhook(process.env.CHRONOS_SIGNING_SECRET!);
684
+ *
685
+ * // In your request handler:
686
+ * const payload = await wh.verify(rawBody, {
687
+ * 'x-chronos-signature': req.headers['x-chronos-signature'],
688
+ * 'x-chronos-timestamp': req.headers['x-chronos-timestamp'],
689
+ * 'x-chronos-delivery-id': req.headers['x-chronos-delivery-id'],
690
+ * });
691
+ * ```
692
+ */
693
+ var Webhook = class {
694
+ secrets;
695
+ toleranceSeconds;
696
+ keysPromise;
697
+ /**
698
+ * @param secret - Signing secret or array of secrets (for key rotation).
699
+ * @param options - Optional configuration.
700
+ * @throws {ChronosWebhookVerificationError} If no valid secret is provided.
701
+ */
702
+ constructor(secret, options) {
703
+ const secrets = Array.isArray(secret) ? secret : [secret];
704
+ this.secrets = secrets.filter((s) => typeof s === "string" && s.trim().length > 0).map((s) => s.trim());
705
+ if (this.secrets.length === 0) throw new ChronosWebhookVerificationError("At least one non-empty signing secret is required");
706
+ const tolerance = options?.toleranceSeconds ?? 300;
707
+ if (!Number.isFinite(tolerance) || tolerance < 0) throw new ChronosWebhookVerificationError("toleranceSeconds must be a finite non-negative number");
708
+ this.toleranceSeconds = tolerance;
709
+ }
710
+ /**
711
+ * Verifies the webhook signature and returns the parsed request body.
712
+ *
713
+ * @param body - Raw request body as a string. Must be the exact bytes received — not re-serialized.
714
+ * @param headers - Request headers. Keys are matched case-insensitively.
715
+ * @returns The parsed JSON body on successful verification.
716
+ * @throws {ChronosWebhookVerificationError} If any verification step fails.
717
+ */
718
+ async verify(body, headers) {
719
+ const normalized = normalizeHeaders(headers);
720
+ for (const name of REQUIRED_HEADERS) {
721
+ const value = normalized[name];
722
+ if (!value || typeof value !== "string") throw new ChronosWebhookVerificationError(`Missing required header: ${name}`);
723
+ }
724
+ const signature = normalized["x-chronos-signature"];
725
+ const timestamp = normalized["x-chronos-timestamp"];
726
+ const deliveryId = normalized["x-chronos-delivery-id"];
727
+ if (!/^[0-9]+$/.test(timestamp)) throw new ChronosWebhookVerificationError("Invalid timestamp");
728
+ const ts = Number(timestamp);
729
+ if (ts <= 0) throw new ChronosWebhookVerificationError("Invalid timestamp");
730
+ const now = Math.floor(Date.now() / 1e3);
731
+ const isStale = Math.abs(now - ts) > this.toleranceSeconds;
732
+ if (this.toleranceSeconds > 0 && isStale) throw new ChronosWebhookVerificationError("Timestamp outside tolerance");
733
+ if (!signature.startsWith(SIGNATURE_PREFIX)) throw new ChronosWebhookVerificationError(`Invalid signature format: expected ${SIGNATURE_PREFIX}<hex>`);
734
+ const expectedHex = signature.slice(7);
735
+ if (expectedHex.length === 0) throw new ChronosWebhookVerificationError("Invalid signature: expected 32-byte SHA-256 digest");
736
+ let expectedBytes;
737
+ try {
738
+ expectedBytes = hexDecode(expectedHex);
739
+ } catch {
740
+ throw new ChronosWebhookVerificationError("Invalid signature: malformed hex");
741
+ }
742
+ if (expectedBytes.byteLength !== 32) throw new ChronosWebhookVerificationError("Invalid signature: expected 32-byte SHA-256 digest");
743
+ const signedPayload = `${deliveryId}.${timestamp}.${body}`;
744
+ const data = new TextEncoder().encode(signedPayload);
745
+ try {
746
+ const keys = await this.ensureKeys();
747
+ for (const key of keys) if (await globalThis.crypto.subtle.verify("HMAC", key, expectedBytes, data)) try {
748
+ return JSON.parse(body);
749
+ } catch {
750
+ throw new ChronosWebhookVerificationError("Verified payload is not valid JSON");
751
+ }
752
+ } catch (err) {
753
+ if (err instanceof ChronosWebhookVerificationError) throw err;
754
+ throw new ChronosWebhookVerificationError("Signature verification failed");
755
+ }
756
+ throw new ChronosWebhookVerificationError("Signature verification failed");
757
+ }
758
+ ensureKeys() {
759
+ if (!this.keysPromise) {
760
+ const encoder = new TextEncoder();
761
+ this.keysPromise = Promise.all(this.secrets.map((s) => globalThis.crypto.subtle.importKey("raw", encoder.encode(s), {
762
+ name: "HMAC",
763
+ hash: "SHA-256"
764
+ }, false, ["verify"])));
765
+ }
766
+ return this.keysPromise;
767
+ }
768
+ };
769
+ function normalizeHeaders(headers) {
770
+ const result = {};
771
+ if (headers instanceof Headers) headers.forEach((value, key) => {
772
+ result[key.toLowerCase()] = value;
773
+ });
774
+ else for (const key of Object.keys(headers)) result[key.toLowerCase()] = headers[key];
775
+ return result;
776
+ }
777
+ function hexDecode(hex) {
778
+ if (hex.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(hex)) throw new Error("Invalid hex string");
779
+ const length = hex.length / 2;
780
+ const buffer = new ArrayBuffer(length);
781
+ const view = new DataView(buffer);
782
+ for (let i = 0; i < length; i++) view.setUint8(i, Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16));
783
+ return buffer;
784
+ }
785
+ //#endregion
606
786
  //#region src/index.ts
607
787
  /**
608
788
  * The Chronos SDK client. Composes worker and (future) REST resource
@@ -642,7 +822,11 @@ exports.ChronosError = ChronosError;
642
822
  exports.ChronosHandlerError = ChronosHandlerError;
643
823
  exports.ChronosNetworkError = ChronosNetworkError;
644
824
  exports.ChronosRateLimitError = ChronosRateLimitError;
825
+ exports.ChronosWebhookVerificationError = ChronosWebhookVerificationError;
645
826
  exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
646
827
  exports.DEFAULT_POLL_WAIT_TIME_SECONDS = DEFAULT_POLL_WAIT_TIME_SECONDS;
828
+ exports.DEFAULT_TIMESTAMP_TOLERANCE_SECONDS = DEFAULT_TIMESTAMP_TOLERANCE_SECONDS;
829
+ exports.SDK_VERSION = SDK_VERSION;
830
+ exports.Webhook = Webhook;
647
831
 
648
832
  //# sourceMappingURL=index.cjs.map
@@ -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/worker.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","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 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","import type { BaseClient } from './client';\nimport { ChronosApiError, ChronosError, ChronosHandlerError, ChronosNetworkError } from './errors';\nimport {\n normalizeHandlerName,\n validatePollWaitTime,\n validateRetryDelayMs,\n} from './internal/validate';\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 handlers = new Map<string, ChronosHandler<unknown>>();\n private handlerNames: string[] = [];\n private startPromise?: Promise<void>;\n private pollController?: AbortController;\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\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.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 },\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 { 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 type RateLimitInfo,\n} from './errors';\nexport type {\n ChronosContext,\n ChronosHandler,\n ChronosHandlerResult,\n ChronosLogger,\n ChronosOptions,\n ChronosSchedule,\n FetchLike,\n} from './types';\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 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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"}
package/dist/index.d.cts CHANGED
@@ -45,6 +45,12 @@ type ChronosOptions = {
45
45
  * to 1000.
46
46
  */
47
47
  retryDelayMs?: number;
48
+ /**
49
+ * Stable worker identifier. Auto-generated if omitted. Provide a fixed
50
+ * value for long-lived deployments where you want the same worker identity
51
+ * across process restarts.
52
+ */
53
+ workerId?: string;
48
54
  };
49
55
  /** Schedule that produced a job. */
50
56
  type ChronosSchedule = {
@@ -81,6 +87,15 @@ type ChronosContext<TPayload = unknown> = {
81
87
  * record a result on the execution, or `undefined` for no result.
82
88
  */
83
89
  type ChronosHandler<TPayload = unknown> = (ctx: ChronosContext<TPayload>) => ChronosHandlerResult | Promise<ChronosHandlerResult>;
90
+ /** Options for constructing a {@link Webhook} verifier. */
91
+ type WebhookOptions = {
92
+ /**
93
+ * Timestamp tolerance in seconds. Defaults to 300 (5 minutes). Set to 0
94
+ * to disable the timestamp check entirely — this accepts any timestamp
95
+ * (past or future) and removes replay protection.
96
+ */
97
+ toleranceSeconds?: number;
98
+ };
84
99
  //#endregion
85
100
  //#region src/client.d.ts
86
101
  /** Default Chronos API base URL. */
@@ -108,10 +123,13 @@ declare class Worker {
108
123
  private readonly client;
109
124
  private readonly pollWaitTimeSeconds;
110
125
  private readonly retryDelayMs;
126
+ private readonly workerId;
127
+ private readonly runtime;
111
128
  private readonly handlers;
112
129
  private handlerNames;
113
130
  private startPromise?;
114
131
  private pollController?;
132
+ private startedAt;
115
133
  constructor(client: BaseClient, options: ChronosOptions);
116
134
  /**
117
135
  * Register a handler for a named job type. Invoked when the Chronos API
@@ -329,6 +347,72 @@ declare class ChronosHandlerError extends ChronosError {
329
347
  cause: unknown;
330
348
  });
331
349
  }
350
+ /**
351
+ * Thrown by {@link Webhook.verify} when signature verification fails.
352
+ * Covers missing or malformed headers, expired timestamps, and signature
353
+ * mismatches.
354
+ *
355
+ * @example
356
+ * ```ts
357
+ * import { Webhook, ChronosWebhookVerificationError } from '@chronos.sh/sdk';
358
+ *
359
+ * const wh = new Webhook(process.env.CHRONOS_SIGNING_SECRET!);
360
+ * try {
361
+ * const payload = await wh.verify(rawBody, headers);
362
+ * } catch (err) {
363
+ * if (err instanceof ChronosWebhookVerificationError) {
364
+ * console.error('Webhook verification failed:', err.message);
365
+ * }
366
+ * }
367
+ * ```
368
+ */
369
+ declare class ChronosWebhookVerificationError extends ChronosError {
370
+ constructor(message: string);
371
+ }
372
+ //#endregion
373
+ //#region src/internal/version.d.ts
374
+ declare const SDK_VERSION: string;
375
+ //#endregion
376
+ //#region src/webhook.d.ts
377
+ declare const DEFAULT_TIMESTAMP_TOLERANCE_SECONDS = 300;
378
+ /**
379
+ * Verifies webhook signatures on Chronos push deliveries.
380
+ *
381
+ * @example
382
+ * ```ts
383
+ * import { Webhook } from '@chronos.sh/sdk';
384
+ *
385
+ * const wh = new Webhook(process.env.CHRONOS_SIGNING_SECRET!);
386
+ *
387
+ * // In your request handler:
388
+ * const payload = await wh.verify(rawBody, {
389
+ * 'x-chronos-signature': req.headers['x-chronos-signature'],
390
+ * 'x-chronos-timestamp': req.headers['x-chronos-timestamp'],
391
+ * 'x-chronos-delivery-id': req.headers['x-chronos-delivery-id'],
392
+ * });
393
+ * ```
394
+ */
395
+ declare class Webhook {
396
+ private readonly secrets;
397
+ private readonly toleranceSeconds;
398
+ private keysPromise?;
399
+ /**
400
+ * @param secret - Signing secret or array of secrets (for key rotation).
401
+ * @param options - Optional configuration.
402
+ * @throws {ChronosWebhookVerificationError} If no valid secret is provided.
403
+ */
404
+ constructor(secret: string | string[], options?: WebhookOptions);
405
+ /**
406
+ * Verifies the webhook signature and returns the parsed request body.
407
+ *
408
+ * @param body - Raw request body as a string. Must be the exact bytes received — not re-serialized.
409
+ * @param headers - Request headers. Keys are matched case-insensitively.
410
+ * @returns The parsed JSON body on successful verification.
411
+ * @throws {ChronosWebhookVerificationError} If any verification step fails.
412
+ */
413
+ verify(body: string, headers: Record<string, string | undefined> | Headers): Promise<unknown>;
414
+ private ensureKeys;
415
+ }
332
416
  //#endregion
333
417
  //#region src/index.d.ts
334
418
  /**
@@ -359,5 +443,5 @@ declare class Chronos {
359
443
  constructor(options: ChronosOptions);
360
444
  }
361
445
  //#endregion
362
- export { Chronos, ChronosApiError, type ChronosApiErrorOptions, ChronosConfigError, type ChronosContext, ChronosError, type ChronosHandler, ChronosHandlerError, type ChronosHandlerResult, type ChronosLogger, ChronosNetworkError, type ChronosOptions, ChronosRateLimitError, type ChronosSchedule, DEFAULT_BASE_URL, DEFAULT_POLL_WAIT_TIME_SECONDS, type FetchLike, type RateLimitInfo };
446
+ export { Chronos, ChronosApiError, type ChronosApiErrorOptions, ChronosConfigError, type ChronosContext, ChronosError, type ChronosHandler, ChronosHandlerError, type ChronosHandlerResult, type ChronosLogger, ChronosNetworkError, type ChronosOptions, ChronosRateLimitError, type ChronosSchedule, ChronosWebhookVerificationError, DEFAULT_BASE_URL, DEFAULT_POLL_WAIT_TIME_SECONDS, DEFAULT_TIMESTAMP_TOLERANCE_SECONDS, type FetchLike, type RateLimitInfo, SDK_VERSION, Webhook, type WebhookOptions };
363
447
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.ts CHANGED
@@ -45,6 +45,12 @@ type ChronosOptions = {
45
45
  * to 1000.
46
46
  */
47
47
  retryDelayMs?: number;
48
+ /**
49
+ * Stable worker identifier. Auto-generated if omitted. Provide a fixed
50
+ * value for long-lived deployments where you want the same worker identity
51
+ * across process restarts.
52
+ */
53
+ workerId?: string;
48
54
  };
49
55
  /** Schedule that produced a job. */
50
56
  type ChronosSchedule = {
@@ -81,6 +87,15 @@ type ChronosContext<TPayload = unknown> = {
81
87
  * record a result on the execution, or `undefined` for no result.
82
88
  */
83
89
  type ChronosHandler<TPayload = unknown> = (ctx: ChronosContext<TPayload>) => ChronosHandlerResult | Promise<ChronosHandlerResult>;
90
+ /** Options for constructing a {@link Webhook} verifier. */
91
+ type WebhookOptions = {
92
+ /**
93
+ * Timestamp tolerance in seconds. Defaults to 300 (5 minutes). Set to 0
94
+ * to disable the timestamp check entirely — this accepts any timestamp
95
+ * (past or future) and removes replay protection.
96
+ */
97
+ toleranceSeconds?: number;
98
+ };
84
99
  //#endregion
85
100
  //#region src/client.d.ts
86
101
  /** Default Chronos API base URL. */
@@ -108,10 +123,13 @@ declare class Worker {
108
123
  private readonly client;
109
124
  private readonly pollWaitTimeSeconds;
110
125
  private readonly retryDelayMs;
126
+ private readonly workerId;
127
+ private readonly runtime;
111
128
  private readonly handlers;
112
129
  private handlerNames;
113
130
  private startPromise?;
114
131
  private pollController?;
132
+ private startedAt;
115
133
  constructor(client: BaseClient, options: ChronosOptions);
116
134
  /**
117
135
  * Register a handler for a named job type. Invoked when the Chronos API
@@ -329,6 +347,72 @@ declare class ChronosHandlerError extends ChronosError {
329
347
  cause: unknown;
330
348
  });
331
349
  }
350
+ /**
351
+ * Thrown by {@link Webhook.verify} when signature verification fails.
352
+ * Covers missing or malformed headers, expired timestamps, and signature
353
+ * mismatches.
354
+ *
355
+ * @example
356
+ * ```ts
357
+ * import { Webhook, ChronosWebhookVerificationError } from '@chronos.sh/sdk';
358
+ *
359
+ * const wh = new Webhook(process.env.CHRONOS_SIGNING_SECRET!);
360
+ * try {
361
+ * const payload = await wh.verify(rawBody, headers);
362
+ * } catch (err) {
363
+ * if (err instanceof ChronosWebhookVerificationError) {
364
+ * console.error('Webhook verification failed:', err.message);
365
+ * }
366
+ * }
367
+ * ```
368
+ */
369
+ declare class ChronosWebhookVerificationError extends ChronosError {
370
+ constructor(message: string);
371
+ }
372
+ //#endregion
373
+ //#region src/internal/version.d.ts
374
+ declare const SDK_VERSION: string;
375
+ //#endregion
376
+ //#region src/webhook.d.ts
377
+ declare const DEFAULT_TIMESTAMP_TOLERANCE_SECONDS = 300;
378
+ /**
379
+ * Verifies webhook signatures on Chronos push deliveries.
380
+ *
381
+ * @example
382
+ * ```ts
383
+ * import { Webhook } from '@chronos.sh/sdk';
384
+ *
385
+ * const wh = new Webhook(process.env.CHRONOS_SIGNING_SECRET!);
386
+ *
387
+ * // In your request handler:
388
+ * const payload = await wh.verify(rawBody, {
389
+ * 'x-chronos-signature': req.headers['x-chronos-signature'],
390
+ * 'x-chronos-timestamp': req.headers['x-chronos-timestamp'],
391
+ * 'x-chronos-delivery-id': req.headers['x-chronos-delivery-id'],
392
+ * });
393
+ * ```
394
+ */
395
+ declare class Webhook {
396
+ private readonly secrets;
397
+ private readonly toleranceSeconds;
398
+ private keysPromise?;
399
+ /**
400
+ * @param secret - Signing secret or array of secrets (for key rotation).
401
+ * @param options - Optional configuration.
402
+ * @throws {ChronosWebhookVerificationError} If no valid secret is provided.
403
+ */
404
+ constructor(secret: string | string[], options?: WebhookOptions);
405
+ /**
406
+ * Verifies the webhook signature and returns the parsed request body.
407
+ *
408
+ * @param body - Raw request body as a string. Must be the exact bytes received — not re-serialized.
409
+ * @param headers - Request headers. Keys are matched case-insensitively.
410
+ * @returns The parsed JSON body on successful verification.
411
+ * @throws {ChronosWebhookVerificationError} If any verification step fails.
412
+ */
413
+ verify(body: string, headers: Record<string, string | undefined> | Headers): Promise<unknown>;
414
+ private ensureKeys;
415
+ }
332
416
  //#endregion
333
417
  //#region src/index.d.ts
334
418
  /**
@@ -359,5 +443,5 @@ declare class Chronos {
359
443
  constructor(options: ChronosOptions);
360
444
  }
361
445
  //#endregion
362
- export { Chronos, ChronosApiError, type ChronosApiErrorOptions, ChronosConfigError, type ChronosContext, ChronosError, type ChronosHandler, ChronosHandlerError, type ChronosHandlerResult, type ChronosLogger, ChronosNetworkError, type ChronosOptions, ChronosRateLimitError, type ChronosSchedule, DEFAULT_BASE_URL, DEFAULT_POLL_WAIT_TIME_SECONDS, type FetchLike, type RateLimitInfo };
446
+ export { Chronos, ChronosApiError, type ChronosApiErrorOptions, ChronosConfigError, type ChronosContext, ChronosError, type ChronosHandler, ChronosHandlerError, type ChronosHandlerResult, type ChronosLogger, ChronosNetworkError, type ChronosOptions, ChronosRateLimitError, type ChronosSchedule, ChronosWebhookVerificationError, DEFAULT_BASE_URL, DEFAULT_POLL_WAIT_TIME_SECONDS, DEFAULT_TIMESTAMP_TOLERANCE_SECONDS, type FetchLike, type RateLimitInfo, SDK_VERSION, Webhook, type WebhookOptions };
363
447
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -167,6 +167,31 @@ var ChronosHandlerError = class extends ChronosError {
167
167
  this.name = "ChronosHandlerError";
168
168
  }
169
169
  };
170
+ /**
171
+ * Thrown by {@link Webhook.verify} when signature verification fails.
172
+ * Covers missing or malformed headers, expired timestamps, and signature
173
+ * mismatches.
174
+ *
175
+ * @example
176
+ * ```ts
177
+ * import { Webhook, ChronosWebhookVerificationError } from '@chronos.sh/sdk';
178
+ *
179
+ * const wh = new Webhook(process.env.CHRONOS_SIGNING_SECRET!);
180
+ * try {
181
+ * const payload = await wh.verify(rawBody, headers);
182
+ * } catch (err) {
183
+ * if (err instanceof ChronosWebhookVerificationError) {
184
+ * console.error('Webhook verification failed:', err.message);
185
+ * }
186
+ * }
187
+ * ```
188
+ */
189
+ var ChronosWebhookVerificationError = class extends ChronosError {
190
+ constructor(message) {
191
+ super(message);
192
+ this.name = "ChronosWebhookVerificationError";
193
+ }
194
+ };
170
195
  //#endregion
171
196
  //#region src/internal/logger.ts
172
197
  const defaultLogger = {
@@ -190,6 +215,11 @@ function validatePollWaitTime(seconds) {
190
215
  function validateRetryDelayMs(ms) {
191
216
  if (!Number.isFinite(ms) || ms < 0) throw new ChronosConfigError("retryDelayMs must be a non-negative number");
192
217
  }
218
+ function validateWorkerId(id) {
219
+ const trimmed = id.trim();
220
+ if (trimmed.length === 0 || trimmed.length > 255) throw new ChronosConfigError("workerId must be 1-255 characters");
221
+ return trimmed;
222
+ }
193
223
  function normalizeHandlerName(name) {
194
224
  const normalized = name.trim();
195
225
  if (!normalized) throw new ChronosError("Handler name is required");
@@ -283,6 +313,26 @@ function isEnvelope(value) {
283
313
  return typeof value.success === "boolean";
284
314
  }
285
315
  //#endregion
316
+ //#region src/internal/id.ts
317
+ function generateWorkerId() {
318
+ const bytes = crypto.getRandomValues(new Uint8Array(8));
319
+ let hex = "";
320
+ for (const b of bytes) hex += b.toString(16).padStart(2, "0");
321
+ return `w_${hex}`;
322
+ }
323
+ //#endregion
324
+ //#region src/internal/runtime.ts
325
+ function detectRuntime() {
326
+ const g = globalThis;
327
+ if (g.Bun != null) return `bun/${g.Bun.version ?? "unknown"}`;
328
+ if (g.Deno != null) return `deno/${g.Deno.version?.deno ?? "unknown"}`;
329
+ if (typeof process !== "undefined" && process.versions?.node) return `node/${process.versions.node}`;
330
+ return "unknown";
331
+ }
332
+ //#endregion
333
+ //#region src/internal/version.ts
334
+ const SDK_VERSION = "0.0.2-canary.fa8e22c";
335
+ //#endregion
286
336
  //#region src/worker.ts
287
337
  /** Default worker long-poll wait time in seconds. Equal to the API maximum. */
288
338
  const DEFAULT_POLL_WAIT_TIME_SECONDS = 20;
@@ -300,14 +350,19 @@ var Worker = class {
300
350
  client;
301
351
  pollWaitTimeSeconds;
302
352
  retryDelayMs;
353
+ workerId;
354
+ runtime;
303
355
  handlers = /* @__PURE__ */ new Map();
304
356
  handlerNames = [];
305
357
  startPromise;
306
358
  pollController;
359
+ startedAt = 0;
307
360
  constructor(client, options) {
308
361
  this.client = client;
309
362
  this.pollWaitTimeSeconds = options.pollWaitTimeSeconds ?? 20;
310
363
  this.retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
364
+ this.workerId = options.workerId !== void 0 ? validateWorkerId(options.workerId) : generateWorkerId();
365
+ this.runtime = detectRuntime();
311
366
  validatePollWaitTime(this.pollWaitTimeSeconds);
312
367
  validateRetryDelayMs(this.retryDelayMs);
313
368
  }
@@ -346,6 +401,7 @@ var Worker = class {
346
401
  start() {
347
402
  if (this.startPromise) throw new ChronosError("Chronos worker is already started");
348
403
  if (this.handlers.size === 0) throw new ChronosError("Register at least one handler before starting Chronos");
404
+ this.startedAt = performance.now();
349
405
  this.pollController = new AbortController();
350
406
  this.startPromise = this.runLoop().finally(() => {
351
407
  this.startPromise = void 0;
@@ -382,7 +438,13 @@ var Worker = class {
382
438
  if (this.handlerNames.length === 0) throw new ChronosError("Cannot claim jobs without registered handlers");
383
439
  return this.client.request("/v1/worker/jobs/claim", {
384
440
  wait_time_seconds: this.pollWaitTimeSeconds,
385
- handlers: this.handlerNames
441
+ handlers: this.handlerNames,
442
+ worker: {
443
+ id: this.workerId,
444
+ sdk_version: SDK_VERSION,
445
+ runtime: this.runtime,
446
+ uptime_ms: Math.round(performance.now() - this.startedAt)
447
+ }
386
448
  }, this.pollController?.signal);
387
449
  }
388
450
  async processJob(job) {
@@ -602,6 +664,124 @@ function sleep(ms, signal) {
602
664
  });
603
665
  }
604
666
  //#endregion
667
+ //#region src/webhook.ts
668
+ const DEFAULT_TIMESTAMP_TOLERANCE_SECONDS = 300;
669
+ const SIGNATURE_PREFIX = "sha256=";
670
+ const REQUIRED_HEADERS = [
671
+ "x-chronos-signature",
672
+ "x-chronos-timestamp",
673
+ "x-chronos-delivery-id"
674
+ ];
675
+ /**
676
+ * Verifies webhook signatures on Chronos push deliveries.
677
+ *
678
+ * @example
679
+ * ```ts
680
+ * import { Webhook } from '@chronos.sh/sdk';
681
+ *
682
+ * const wh = new Webhook(process.env.CHRONOS_SIGNING_SECRET!);
683
+ *
684
+ * // In your request handler:
685
+ * const payload = await wh.verify(rawBody, {
686
+ * 'x-chronos-signature': req.headers['x-chronos-signature'],
687
+ * 'x-chronos-timestamp': req.headers['x-chronos-timestamp'],
688
+ * 'x-chronos-delivery-id': req.headers['x-chronos-delivery-id'],
689
+ * });
690
+ * ```
691
+ */
692
+ var Webhook = class {
693
+ secrets;
694
+ toleranceSeconds;
695
+ keysPromise;
696
+ /**
697
+ * @param secret - Signing secret or array of secrets (for key rotation).
698
+ * @param options - Optional configuration.
699
+ * @throws {ChronosWebhookVerificationError} If no valid secret is provided.
700
+ */
701
+ constructor(secret, options) {
702
+ const secrets = Array.isArray(secret) ? secret : [secret];
703
+ this.secrets = secrets.filter((s) => typeof s === "string" && s.trim().length > 0).map((s) => s.trim());
704
+ if (this.secrets.length === 0) throw new ChronosWebhookVerificationError("At least one non-empty signing secret is required");
705
+ const tolerance = options?.toleranceSeconds ?? 300;
706
+ if (!Number.isFinite(tolerance) || tolerance < 0) throw new ChronosWebhookVerificationError("toleranceSeconds must be a finite non-negative number");
707
+ this.toleranceSeconds = tolerance;
708
+ }
709
+ /**
710
+ * Verifies the webhook signature and returns the parsed request body.
711
+ *
712
+ * @param body - Raw request body as a string. Must be the exact bytes received — not re-serialized.
713
+ * @param headers - Request headers. Keys are matched case-insensitively.
714
+ * @returns The parsed JSON body on successful verification.
715
+ * @throws {ChronosWebhookVerificationError} If any verification step fails.
716
+ */
717
+ async verify(body, headers) {
718
+ const normalized = normalizeHeaders(headers);
719
+ for (const name of REQUIRED_HEADERS) {
720
+ const value = normalized[name];
721
+ if (!value || typeof value !== "string") throw new ChronosWebhookVerificationError(`Missing required header: ${name}`);
722
+ }
723
+ const signature = normalized["x-chronos-signature"];
724
+ const timestamp = normalized["x-chronos-timestamp"];
725
+ const deliveryId = normalized["x-chronos-delivery-id"];
726
+ if (!/^[0-9]+$/.test(timestamp)) throw new ChronosWebhookVerificationError("Invalid timestamp");
727
+ const ts = Number(timestamp);
728
+ if (ts <= 0) throw new ChronosWebhookVerificationError("Invalid timestamp");
729
+ const now = Math.floor(Date.now() / 1e3);
730
+ const isStale = Math.abs(now - ts) > this.toleranceSeconds;
731
+ if (this.toleranceSeconds > 0 && isStale) throw new ChronosWebhookVerificationError("Timestamp outside tolerance");
732
+ if (!signature.startsWith(SIGNATURE_PREFIX)) throw new ChronosWebhookVerificationError(`Invalid signature format: expected ${SIGNATURE_PREFIX}<hex>`);
733
+ const expectedHex = signature.slice(7);
734
+ if (expectedHex.length === 0) throw new ChronosWebhookVerificationError("Invalid signature: expected 32-byte SHA-256 digest");
735
+ let expectedBytes;
736
+ try {
737
+ expectedBytes = hexDecode(expectedHex);
738
+ } catch {
739
+ throw new ChronosWebhookVerificationError("Invalid signature: malformed hex");
740
+ }
741
+ if (expectedBytes.byteLength !== 32) throw new ChronosWebhookVerificationError("Invalid signature: expected 32-byte SHA-256 digest");
742
+ const signedPayload = `${deliveryId}.${timestamp}.${body}`;
743
+ const data = new TextEncoder().encode(signedPayload);
744
+ try {
745
+ const keys = await this.ensureKeys();
746
+ for (const key of keys) if (await globalThis.crypto.subtle.verify("HMAC", key, expectedBytes, data)) try {
747
+ return JSON.parse(body);
748
+ } catch {
749
+ throw new ChronosWebhookVerificationError("Verified payload is not valid JSON");
750
+ }
751
+ } catch (err) {
752
+ if (err instanceof ChronosWebhookVerificationError) throw err;
753
+ throw new ChronosWebhookVerificationError("Signature verification failed");
754
+ }
755
+ throw new ChronosWebhookVerificationError("Signature verification failed");
756
+ }
757
+ ensureKeys() {
758
+ if (!this.keysPromise) {
759
+ const encoder = new TextEncoder();
760
+ this.keysPromise = Promise.all(this.secrets.map((s) => globalThis.crypto.subtle.importKey("raw", encoder.encode(s), {
761
+ name: "HMAC",
762
+ hash: "SHA-256"
763
+ }, false, ["verify"])));
764
+ }
765
+ return this.keysPromise;
766
+ }
767
+ };
768
+ function normalizeHeaders(headers) {
769
+ const result = {};
770
+ if (headers instanceof Headers) headers.forEach((value, key) => {
771
+ result[key.toLowerCase()] = value;
772
+ });
773
+ else for (const key of Object.keys(headers)) result[key.toLowerCase()] = headers[key];
774
+ return result;
775
+ }
776
+ function hexDecode(hex) {
777
+ if (hex.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(hex)) throw new Error("Invalid hex string");
778
+ const length = hex.length / 2;
779
+ const buffer = new ArrayBuffer(length);
780
+ const view = new DataView(buffer);
781
+ for (let i = 0; i < length; i++) view.setUint8(i, Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16));
782
+ return buffer;
783
+ }
784
+ //#endregion
605
785
  //#region src/index.ts
606
786
  /**
607
787
  * The Chronos SDK client. Composes worker and (future) REST resource
@@ -634,6 +814,6 @@ var Chronos = class {
634
814
  }
635
815
  };
636
816
  //#endregion
637
- export { Chronos, ChronosApiError, ChronosConfigError, ChronosError, ChronosHandlerError, ChronosNetworkError, ChronosRateLimitError, DEFAULT_BASE_URL, DEFAULT_POLL_WAIT_TIME_SECONDS };
817
+ export { Chronos, ChronosApiError, ChronosConfigError, ChronosError, ChronosHandlerError, ChronosNetworkError, ChronosRateLimitError, ChronosWebhookVerificationError, DEFAULT_BASE_URL, DEFAULT_POLL_WAIT_TIME_SECONDS, DEFAULT_TIMESTAMP_TOLERANCE_SECONDS, SDK_VERSION, Webhook };
638
818
 
639
819
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/errors.ts","../src/internal/logger.ts","../src/internal/validate.ts","../src/client.ts","../src/worker.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","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 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","import type { BaseClient } from './client';\nimport { ChronosApiError, ChronosError, ChronosHandlerError, ChronosNetworkError } from './errors';\nimport {\n normalizeHandlerName,\n validatePollWaitTime,\n validateRetryDelayMs,\n} from './internal/validate';\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 handlers = new Map<string, ChronosHandler<unknown>>();\n private handlerNames: string[] = [];\n private startPromise?: Promise<void>;\n private pollController?: AbortController;\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\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.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 },\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 { 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 type RateLimitInfo,\n} from './errors';\nexport type {\n ChronosContext,\n ChronosHandler,\n ChronosHandlerResult,\n ChronosLogger,\n ChronosOptions,\n ChronosSchedule,\n FetchLike,\n} from './types';\nexport { DEFAULT_BASE_URL, DEFAULT_POLL_WAIT_TIME_SECONDS };\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chronos.sh/sdk",
3
- "version": "0.0.2-canary.b86e7e1",
3
+ "version": "0.0.2-canary.fa8e22c",
4
4
  "description": "Chronos JavaScript/TypeScript SDK",
5
5
  "license": "MIT",
6
6
  "keywords": [