@calmlens/sdk 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,6 +10,8 @@ CalmLens provides content moderation and classification for text, images, docume
10
10
  npm install @calmlens/sdk
11
11
  ```
12
12
 
13
+ Requires Node.js 22 or later (the minimum its dependencies support), or another runtime with `fetch`, Web Crypto and `AbortSignal.any`, such as Bun or Deno.
14
+
13
15
  Every public SDK operation accepts one options object. This keeps call sites stable as optional fields are added.
14
16
 
15
17
  ## Quick Start
@@ -69,6 +71,12 @@ new CalmLensClient(options: CalmLensClientOptions)
69
71
  - `projectId` (string, required): Your CalmLens project ID
70
72
  - `baseUrl` (string, optional): API base URL (defaults to `https://api.calmlens.com/v1`)
71
73
  - `cdnBaseUrl` (string, optional): CDN base URL (defaults to `https://cdn.calmlens.com/v1`)
74
+ - `timeoutMs` (positive integer, optional): Deadline for each request, including reading the response body (defaults to `120000`)
75
+ - `signal` (AbortSignal, optional): Cancels every request made by this client
76
+ - `uploadFetch`, `cdnFetch` (optional): `fetch` implementations for content uploads and CDN requests
77
+ - `requestBackend` (optional): An `api-def` request backend for the JSON API, for example `new FetchRequestBackend(customFetch)`
78
+
79
+ Options are validated before anything else is set up: a missing `apiKey` or `projectId`, a `timeoutMs` that is not a positive integer, or a `baseUrl`/`cdnBaseUrl` that is not an absolute `http(s)` URL throws a `CalmLensError`.
72
80
 
73
81
  #### Methods
74
82
 
@@ -110,14 +118,31 @@ const asset = await client.assets.create({
110
118
  });
111
119
  ```
112
120
 
113
- ##### assets.moderate(options)
121
+ ##### assets.moderate(options) and text.moderate(options)
114
122
 
115
- Submit an asset and wait for classification to finish. Accepts the same options as `assets.create()` for text, images, documents, and URLs.
123
+ Submit an asset and wait for classification to finish. Accepts the same options as `assets.create()` for text, images, documents, and URLs. `text.moderate({ text, name?, externalId?, ... })` is a shortcut that sends `text` as `text/plain` content named `text` by default.
116
124
 
117
125
  ```typescript
118
126
  async assets.moderate(options: SubmitAssetOptions): Promise<Asset>
127
+ async text.moderate(options: ModerateTextOptions): Promise<Asset>
128
+ ```
129
+
130
+ Both resolve to the stored `Asset`, a plain object with camelCase keys exactly as the API returns it:
131
+
132
+ ```typescript
133
+ const asset = await client.text.moderate({ text: "Great photo!", externalId: "comment-42" });
134
+ asset.id; // string
135
+ asset.status; // "approved" | "rejected" | "error"; "pending" for audio and video, which always run in the background
136
+ asset.type; // "plaintext-document" for text
137
+ asset.report?.flagged; // boolean
138
+ asset.report?.score; // number, the overall score
139
+ asset.report?.categories.harassment.status; // "evaluated" | "partial" | "not-evaluated"
140
+ asset.moderationAction; // the policy action applied, if any
141
+ asset.createdAt; // milliseconds since the Unix epoch
119
142
  ```
120
143
 
144
+ Always present: `id`, `projectId`, `name`, `type`, `status`, `createdAt` and `updatedAt`. Every other field may be `null` or missing: `report` is absent when processing failed (`status: "error"`) or has not finished, `moderationAction` when no policy action applied, `externalId`, `description`, `metadata`, `folderId`, `visibility` and `processingOptions` when you did not set them, and `sizeInBytes`, `fileFormat`, `imageData`, `documentData`, `previewImage` and `extractedText` when they do not apply to the content. Inside a report, a category's `score` and `likelihood` exist only when its `status` is `evaluated` or `partial` (see [Moderation Reports](#moderation-reports)).
145
+
121
146
  ##### assets.retrieve(options)
122
147
 
123
148
  Retrieve details of a specific asset.
@@ -504,7 +529,21 @@ const client = new MockCalmLensClient({
504
529
  });
505
530
  ```
506
531
 
507
- For configuration options (`mockOptions`), deterministic timestamps and IDs, storage inspection, and testing recipes, see the [JavaScript SDK Testing Guide](https://calmlens.com/docs/sdk/javascript#testing).
532
+ `MockCalmLensClient` accepts the same options as `CalmLensClient` (`apiKey`, `projectId`, `baseUrl`, `cdnBaseUrl`, `timeoutMs`, ...) and validates them the same way, plus `mockOptions`. `cdn.getAssetUrl` uses `cdnBaseUrl` (default `mock://calmlens/v1`).
533
+
534
+ Every call is recorded in `client.calls` as `{ operation, options }`, where `operation` is the method path (`"text.moderate"`, `"assets.retrieve"`, `"events.list"`, ...) and `options` is the object you passed. `client.callsTo(operation)` filters them. To test failure handling, `client.failNextRequest()` makes the next call throw the same `CalmLensError` the real client throws when the network fails (`sdk/transport-error`); pass `"timeout"` for `sdk/timeout`, or any `Error` to throw it as is, and `{ operation }` to fail only that method. Each queued failure is used once.
535
+
536
+ ```typescript
537
+ const client = new MockCalmLensClient({ apiKey: createMockApiKey(), projectId, timeoutMs: 5_000 });
538
+
539
+ client.failNextRequest("timeout", { operation: "text.moderate" });
540
+ await expect(postComment(client, "hello")).rejects.toMatchObject({ code: "sdk/timeout" });
541
+
542
+ await postComment(client, "hello");
543
+ expect(client.callsTo("text.moderate")[0]?.options).toMatchObject({ text: "hello" });
544
+ ```
545
+
546
+ For `mockOptions`, deterministic timestamps and IDs, storage inspection, and testing recipes, see the [JavaScript SDK Testing Guide](https://calmlens.com/docs/sdk/javascript/testing).
508
547
 
509
548
  ## Webhooks
510
549
 
@@ -516,40 +555,101 @@ A `webhook` connector POSTs each event to your URL as JSON:
516
555
  "type": "asset.flagged",
517
556
  "createdAt": 1760000000000,
518
557
  "projectId": "…",
519
- "data": { "severity": "high", "title": "…", "description": null, "assetId": "…", "asset": null, "metadata": null }
558
+ "data": {
559
+ "severity": "high",
560
+ "title": "…",
561
+ "description": null,
562
+ "assetId": "…",
563
+ "asset": null,
564
+ "facts": [{ "key": "asset-name", "value": "photo.jpg" }]
565
+ }
520
566
  }
521
567
  ```
522
568
 
523
- Each delivery has these headers:
569
+ Each delivery has these headers (the names are exported as constants):
570
+
571
+ | Header | Constant | Value |
572
+ |---|---|---|
573
+ | `x-calmlens-delivery` | `WEBHOOK_DELIVERY_HEADER` | Delivery ID (the same across retries) |
574
+ | `x-calmlens-timestamp` | `WEBHOOK_TIMESTAMP_HEADER` | Unix time in seconds when the delivery was signed |
575
+ | `x-calmlens-signature` | `WEBHOOK_SIGNATURE_HEADER` | `v1=<lowercase hex HMAC-SHA256(secret, "<timestamp>.<raw body>")>` |
524
576
 
525
- | Header | Value |
526
- |---|---|
527
- | `x-calmlens-delivery` | Delivery ID (the same across retries) |
528
- | `x-calmlens-timestamp` | Unix time in seconds when the delivery was signed |
529
- | `x-calmlens-signature` | `v1=<lowercase hex HMAC-SHA256(secret, "<timestamp>.<raw body>")>` |
577
+ ```typescript
578
+ import {
579
+ verifyWebhookSignature,
580
+ WEBHOOK_SIGNATURE_HEADER,
581
+ WEBHOOK_TIMESTAMP_HEADER,
582
+ } from "@calmlens/sdk";
530
583
 
531
- Verify the exact raw request body with `verifyWebhookSignature` before parsing it; re-serializing parsed JSON can change the signed bytes. Deliveries more than 5 minutes old (or ahead of your clock) are rejected to prevent replays. The body type is exported as `WebhookEventPayload`; narrow it on `type` to get that event type's typed `data.facts` (`EventType` and `EventFacts<Type>` are exported too, and [the webhooks guide](https://calmlens.com/docs/guides/webhooks#event-types) lists every type). The header names are exported as `WEBHOOK_SIGNATURE_HEADER`, `WEBHOOK_TIMESTAMP_HEADER` and `WEBHOOK_DELIVERY_HEADER`.
584
+ const payload = await request.text();
585
+ const valid = await verifyWebhookSignature({
586
+ payload,
587
+ signature: request.headers.get(WEBHOOK_SIGNATURE_HEADER),
588
+ timestamp: request.headers.get(WEBHOOK_TIMESTAMP_HEADER),
589
+ secret: process.env.CALMLENS_WEBHOOK_SECRET!,
590
+ });
591
+ ```
592
+
593
+ Verify the exact raw request body with `verifyWebhookSignature` before parsing it; re-serializing parsed JSON can change the signed bytes. Deliveries more than 5 minutes old (or ahead of your clock) are rejected to prevent replays. The body type is exported as `WebhookEventPayload`; narrow it on `type` to get that event type's typed `data.facts` (`EventType` and `EventFacts<Type>` are exported too, and [the webhooks guide](https://calmlens.com/docs/guides/webhooks#event-types) lists every type).
532
594
 
533
595
  ## Command-line tool
534
596
 
535
- The SDK installs a `calmlens` command. Webhook connectors must use public HTTPS URLs, so during development use it to forward events to a local handler instead. Each event is sent with the same body, headers and signature as a real delivery:
597
+ Install the standalone [CalmLens CLI](https://calmlens.com/docs/guides/local-development) through Homebrew, a platform download or npm. Webhook connectors must use public HTTPS URLs, so during development use it to forward events to a local handler instead. Each event is sent with the same body, headers and signature as a real delivery:
536
598
 
537
599
  ```bash
538
600
  npx calmlens listen --forward-to http://localhost:3000/api/webhooks/calmlens --env-file .env.local
539
601
  ```
540
602
 
541
- The CLI reads `CALMLENS_API_KEY`, `CALMLENS_PROJECT_ID` and `CALMLENS_WEBHOOK_SECRET` from the environment or `--env-file`; without a secret it prints a generated one for your app. `npx calmlens trigger [harassment|hate|criminal-activity|clean]` moderates a harmless test input so events appear (one upload each). Run `npx calmlens --help` for all options. To install the command globally, use `npm install -g calmlens`: the same package, published under a shorter name.
603
+ The CLI reads `CALMLENS_API_KEY`, `CALMLENS_PROJECT_ID` and `CALMLENS_WEBHOOK_SECRET` from the environment or `--env-file`; without a secret it prints a generated one for your app. `npx calmlens trigger [harassment|hate|criminal-activity|clean]` moderates a harmless test input so events appear (one upload each). Run `npx calmlens --help` for all options. To install the command globally, use `npm install -g calmlens`. The CLI is released separately from the SDK.
542
604
 
543
- The same feed is available in code: `client.events.list({ after, limit })` returns events oldest first with a `cursor` to pass back.
605
+ The same feed is available in code with [`events.list`](#events-feed).
544
606
 
545
- ## Releasing
607
+ ## Events feed
546
608
 
547
- Stable releases are published automatically from `main` when packed SDK contents differ from npm `latest` and an SDK Changeset is present. Contributors must run `bun run changeset` to choose the release type and write release notes. CI updates and commits this package's `CHANGELOG.md` automatically. See the repository's [.changeset release guide](../../.changeset/README.md).
609
+ `client.events.list({ after?, limit? })` reads the project's events, oldest first, without a public webhook URL. It resolves to an `EventsPage`:
610
+
611
+ ```typescript
612
+ {
613
+ events: TypedEvent[]; // { id, projectId, type, severity, title, description?, assetId?, asset?, facts, createdAt, updatedAt }
614
+ cursor: string; // pass back as `after` to continue after the last event returned
615
+ hasMore: boolean; // true when more events are already waiting after `cursor`
616
+ }
617
+ ```
618
+
619
+ - Without `after` (or with an empty string), it returns **no events** and a `cursor` for the current time, so you only see events created after you start reading. It does not return history.
620
+ - With `after`, it returns up to `limit` events (default 50) created after that cursor. When there are none, `cursor` is the `after` you passed.
621
+ - Keep passing the latest `cursor` as `after` until `hasMore` is `false`, then wait before polling again. Store the cursor if you want to resume after a restart.
622
+ - An unknown or malformed cursor fails with the `CalmLensError` code `events/invalid-cursor`.
623
+
624
+ ```typescript
625
+ let { cursor } = await client.events.list(); // no events, cursor = "now"
626
+
627
+ async function drainEvents() {
628
+ let hasMore = true;
629
+ while (hasMore) {
630
+ const page = await client.events.list({ after: cursor, limit: 100 });
631
+ for (const event of page.events) {
632
+ // narrow on event.type to read its typed facts
633
+ console.log(event.type, event.facts);
634
+ }
635
+ cursor = page.cursor;
636
+ hasMore = page.hasMore;
637
+ }
638
+ }
639
+
640
+ setInterval(drainEvents, 5_000);
641
+ ```
548
642
 
549
- ### Request budgets and errors
643
+ Events carry `facts: { key, value, url? }[]`; webhook deliveries carry the same events reshaped as `{ id, type, createdAt, projectId, data }`.
644
+
645
+ ## Request budgets and errors
550
646
 
551
647
  Set `timeoutMs` (default 120000) or `signal` on the client. Use `client.withRequestOptions({ signal, timeoutMs: 30000 })` to scope them to an operation without changing the original client. Multipart state is shared between scoped clients. Deadlines include response-body reads; authenticated default REST, upload and CDN requests reject redirects. Custom non-Fetch backends own their redirect/cancellation policies.
552
648
 
553
- `CalmLensError` exposes `code`, optional HTTP `status`, `details` and native `cause`. Transport failures use `sdk/transport-error`, deadlines `sdk/timeout`, cancellation `sdk/cancelled`, malformed successful uploads `sdk/invalid-response`, and unstructured HTTP failures `sdk/http-error`. API error codes/messages are preserved. Core model types (`Asset`, `Folder`, `Lens`, `AssetPage`, `EventsPage`, `ClassificationReport`) and request types are exported from the package root.
649
+ `CalmLensError` exposes `code`, optional HTTP `status`, `details` and native `cause`. Every failure to complete a request (DNS, TLS, refused or reset connections, truncated bodies, decompression errors) is `sdk/transport-error`, with the underlying error as `cause`; deadlines are `sdk/timeout`, cancellation `sdk/cancelled`, successful responses whose body is not the expected JSON `sdk/invalid-response`, and HTTP failures without an API error body `sdk/http-error`. `cdn.serve` returns the `Response` once headers arrive, so errors while you read its body come from your `fetch`. API error codes/messages are preserved. Core model types (`Asset`, `Folder`, `Lens`, `AssetPage`, `EventsPage`, `ClassificationReport`) and request types are exported from the package root.
554
650
 
555
651
  `text.moderate` is a convenience wrapper around asset moderation: it supplies `text/plain` content and defaults the asset name to `text`. `assets.create` queues processing; `assets.moderate` waits for classification. The old asset analyze method is removed.
652
+
653
+ ## Releasing
654
+
655
+ Stable releases are published automatically from `main` when packed SDK contents differ from npm `latest` and an SDK Changeset is present. Contributors must run `bun run changeset` to choose the release type and write release notes. CI updates and commits this package's `CHANGELOG.md` automatically. See the repository's [.changeset release guide](../../.changeset/README.md).
@@ -1245,6 +1245,7 @@ declare const ASSET_SCHEMA: zod.ZodObject<{
1245
1245
  audioTranscribed: zod.ZodBoolean;
1246
1246
  segments: zod.ZodArray<zod.ZodObject<{
1247
1247
  source: zod.ZodEnum<{
1248
+ clip: "clip";
1248
1249
  frame: "frame";
1249
1250
  "frame-text": "frame-text";
1250
1251
  speech: "speech";
@@ -3518,4 +3519,4 @@ interface ModerateTextOptions extends Omit<SubmitAssetCommonOptions, "name" | "v
3518
3519
  name?: string;
3519
3520
  }
3520
3521
  //#endregion
3521
- export { GetEventsResponse as A, EventFacts as B, UpdateAssetOptions as C, GetAssetChildrenResponse as D, VerifyWebhookSignatureOptions as E, Folder as F, AssetType as G, EventType as H, TypedEvent as I, ProcessingOptions as J, AssetVisibility as K, EventCategory as L, RequestErrorCode as M, AssetBinaryPayloadInput as N, GetAssetResponse as O, Lens as P, EventFact as R, SubmitAssetOptions as S, UploadAssetPartOptions as T, Asset as U, EventSeverity as V, AssetStatus as W, MultipartUploadOptions as _, CreateAssetSignedUrlOptions as a, RetrieveAssetOptions as b, EnsureLensOptions as c, ListAssetChildrenOptions as d, ListAssetsOptions as f, MoveFolderOptions as g, MoveAssetOptions as h, CdnServeOptions as i, PostAssetSignedUrlResponse as j, GetAssetsPageResponse as k, EnsureLensesOptions as l, ModerateTextOptions as m, CalmLensClientOptions as n, CreateFolderOptions as o, ListEventsOptions as p, MetadataItem as q, CdnAssetUrlOptions as r, CreateMultipartUploadPartOptions as s, AssetContentOptions as t, FolderIdOptions as u, MultipartUploadPartOptions as v, UpdateFolderOptions as w, RetryAssetOptions as x, RequestOptions as y, EventFactValues as z };
3522
+ export { GetEventsResponse as A, EventFactValues as B, UpdateAssetOptions as C, GetAssetChildrenResponse as D, VerifyWebhookSignatureOptions as E, Folder as F, AssetStatus as G, EventSeverity as H, Event as I, MetadataItem as J, AssetType as K, TypedEvent as L, RequestErrorCode as M, AssetBinaryPayloadInput as N, GetAssetResponse as O, Lens as P, EventCategory as R, SubmitAssetOptions as S, UploadAssetPartOptions as T, EventType as U, EventFacts as V, Asset as W, ProcessingOptions as Y, MultipartUploadOptions as _, CreateAssetSignedUrlOptions as a, RetrieveAssetOptions as b, EnsureLensOptions as c, ListAssetChildrenOptions as d, ListAssetsOptions as f, MoveFolderOptions as g, MoveAssetOptions as h, CdnServeOptions as i, PostAssetSignedUrlResponse as j, GetAssetsPageResponse as k, EnsureLensesOptions as l, ModerateTextOptions as m, CalmLensClientOptions as n, CreateFolderOptions as o, ListEventsOptions as p, AssetVisibility as q, CdnAssetUrlOptions as r, CreateMultipartUploadPartOptions as s, AssetContentOptions as t, FolderIdOptions as u, MultipartUploadPartOptions as v, UpdateFolderOptions as w, RetryAssetOptions as x, RequestOptions as y, EventFact as z };
@@ -417,6 +417,27 @@ function _objectWithoutProperties(e, t) {
417
417
  //#endregion
418
418
  //#region src/client/CalmLensUtils.ts
419
419
  const _excluded = ["text", "name"];
420
+ const invalidOptions = (message) => calmLensError({ id: "upload/invalid-payload" }, { message });
421
+ const assertHttpUrl = (name, value) => {
422
+ if (value === void 0) return;
423
+ let url;
424
+ try {
425
+ url = new URL(value);
426
+ } catch (_unused) {}
427
+ if (!url || url.protocol !== "https:" && url.protocol !== "http:") throw invalidOptions(`${name} must be an absolute http(s) URL`);
428
+ };
429
+ /**
430
+ * Checks client options before the client sets anything up. `CalmLensClient` and
431
+ * `MockCalmLensClient` share it so both reject the same configuration.
432
+ */
433
+ const validateClientOptions = (options) => {
434
+ if (!options || typeof options !== "object") throw invalidOptions("Client options are required");
435
+ if (!options.apiKey) throw invalidOptions("API key is required");
436
+ if (!options.projectId) throw invalidOptions("Project ID is required");
437
+ if (options.timeoutMs !== void 0 && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs <= 0)) throw invalidOptions("timeoutMs must be a positive integer");
438
+ assertHttpUrl("baseUrl", options.baseUrl);
439
+ assertHttpUrl("cdnBaseUrl", options.cdnBaseUrl);
440
+ };
420
441
  const toTextAssetOptions = (_ref) => {
421
442
  let { text, name } = _ref, options = _objectWithoutProperties(_ref, _excluded);
422
443
  if (typeof text !== "string" || !text.trim()) throw calmLensError(PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Text to moderate must be a non-empty string" });
@@ -549,3 +570,9 @@ Object.defineProperty(exports, "validateAssetPayload", {
549
570
  return validateAssetPayload;
550
571
  }
551
572
  });
573
+ Object.defineProperty(exports, "validateClientOptions", {
574
+ enumerable: true,
575
+ get: function() {
576
+ return validateClientOptions;
577
+ }
578
+ });
package/cjs/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as GetEventsResponse, B as EventFacts, C as UpdateAssetOptions, D as GetAssetChildrenResponse, E as VerifyWebhookSignatureOptions, F as Folder, G as AssetType, H as EventType, I as TypedEvent, J as ProcessingOptions, K as AssetVisibility, L as EventCategory, M as RequestErrorCode, O as GetAssetResponse, P as Lens, R as EventFact, S as SubmitAssetOptions, T as UploadAssetPartOptions, U as Asset, V as EventSeverity, W as AssetStatus, _ as MultipartUploadOptions, a as CreateAssetSignedUrlOptions, b as RetrieveAssetOptions, c as EnsureLensOptions, d as ListAssetChildrenOptions, f as ListAssetsOptions, g as MoveFolderOptions, h as MoveAssetOptions, i as CdnServeOptions, j as PostAssetSignedUrlResponse, k as GetAssetsPageResponse, l as EnsureLensesOptions, m as ModerateTextOptions, n as CalmLensClientOptions, o as CreateFolderOptions, p as ListEventsOptions, q as MetadataItem, r as CdnAssetUrlOptions, s as CreateMultipartUploadPartOptions, t as AssetContentOptions, u as FolderIdOptions, v as MultipartUploadPartOptions, w as UpdateFolderOptions, x as RetryAssetOptions, y as RequestOptions, z as EventFactValues } from "./CalmLensTypes-DG_kHVqY.js";
1
+ import { A as GetEventsResponse, B as EventFactValues, C as UpdateAssetOptions, D as GetAssetChildrenResponse, E as VerifyWebhookSignatureOptions, F as Folder, G as AssetStatus, H as EventSeverity, I as Event, J as MetadataItem, K as AssetType, L as TypedEvent, M as RequestErrorCode, O as GetAssetResponse, P as Lens, R as EventCategory, S as SubmitAssetOptions, T as UploadAssetPartOptions, U as EventType, V as EventFacts, W as Asset, Y as ProcessingOptions, _ as MultipartUploadOptions, a as CreateAssetSignedUrlOptions, b as RetrieveAssetOptions, c as EnsureLensOptions, d as ListAssetChildrenOptions, f as ListAssetsOptions, g as MoveFolderOptions, h as MoveAssetOptions, i as CdnServeOptions, j as PostAssetSignedUrlResponse, k as GetAssetsPageResponse, l as EnsureLensesOptions, m as ModerateTextOptions, n as CalmLensClientOptions, o as CreateFolderOptions, p as ListEventsOptions, q as AssetVisibility, r as CdnAssetUrlOptions, s as CreateMultipartUploadPartOptions, t as AssetContentOptions, u as FolderIdOptions, v as MultipartUploadPartOptions, w as UpdateFolderOptions, x as RetryAssetOptions, y as RequestOptions, z as EventFact } from "./CalmLensTypes-BpmT60j-.js";
2
2
  import * as zod from "zod";
3
3
  //#region ../shared/src/Classification.d.ts
4
4
  declare const CLASSIFICATION_CATEGORY_SCHEMA: zod.ZodEnum<{
@@ -1348,6 +1348,7 @@ declare const CLASSIFICATION_REPORT_SCHEMA: zod.ZodObject<{
1348
1348
  audioTranscribed: zod.ZodBoolean;
1349
1349
  segments: zod.ZodArray<zod.ZodObject<{
1350
1350
  source: zod.ZodEnum<{
1351
+ clip: "clip";
1351
1352
  frame: "frame";
1352
1353
  "frame-text": "frame-text";
1353
1354
  speech: "speech";
@@ -2731,6 +2732,15 @@ export declare const WEBHOOK_TIMESTAMP_HEADER = "x-calmlens-timestamp";
2731
2732
  export declare const WEBHOOK_SIGNATURE_HEADER = "x-calmlens-signature";
2732
2733
  /** Deliveries whose timestamp is further than this from the receiver's clock are rejected. */
2733
2734
  export declare const WEBHOOK_SIGNATURE_TOLERANCE_SECONDS = 300;
2735
+ /** The JSON body of a webhook delivery for a stored event. */
2736
+ export declare const toWebhookEventPayload: (event: Event) => WebhookEventPayload;
2737
+ /** Headers for one signed delivery of `body`. */
2738
+ export declare const createWebhookHeaders: (options: {
2739
+ secret: string;
2740
+ body: string;
2741
+ deliveryId: string;
2742
+ timestampSeconds?: number;
2743
+ }) => Promise<Record<string, string>>;
2734
2744
  /**
2735
2745
  * Verifies a webhook delivery: `x-calmlens-signature` must contain a `v1=` HMAC-SHA256 of
2736
2746
  * `"<x-calmlens-timestamp>.<raw body>"`, and the timestamp must be within `toleranceSeconds`