@stack0/sdk 0.5.17 → 0.6.1

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.mjs CHANGED
@@ -428,11 +428,19 @@ var Mailboxes = class {
428
428
  }
429
429
  http;
430
430
  /**
431
- * Create a new mailbox
431
+ * Create a new mailbox. The result carries `webhookSecret`, which signs every
432
+ * inbound delivery; store it now (only `create` and `rotateSecret` return it).
432
433
  */
433
434
  async create(request) {
434
435
  return this.http.post("/mail/mailboxes", request);
435
436
  }
437
+ /**
438
+ * Replace a mailbox's webhook secret and return the new one. Deliveries are
439
+ * signed with the new secret from then on.
440
+ */
441
+ async rotateSecret(id) {
442
+ return this.http.post(`/mail/mailboxes/${id}/rotate-secret`, {});
443
+ }
436
444
  /**
437
445
  * Get a mailbox by ID
438
446
  */
@@ -1894,6 +1902,7 @@ var CDN = class {
1894
1902
  const params = new URLSearchParams();
1895
1903
  if (request.projectSlug) params.set("projectSlug", request.projectSlug);
1896
1904
  if (request.environment) params.set("environment", request.environment);
1905
+ if (request.folder) params.set("folder", request.folder);
1897
1906
  if (request.periodStart) {
1898
1907
  const date = request.periodStart instanceof Date ? request.periodStart.toISOString() : request.periodStart;
1899
1908
  params.set("periodStart", date);
@@ -1953,6 +1962,36 @@ var CDN = class {
1953
1962
  const query = params.toString();
1954
1963
  return this.http.get(`/cdn/usage/storage-breakdown${query ? `?${query}` : ""}`);
1955
1964
  }
1965
+ /**
1966
+ * Get total stored bytes for a project or folder, derived assets included.
1967
+ *
1968
+ * Totals uploads plus everything the pipelines derived from them — video renditions, HLS
1969
+ * segments and manifests, thumbnails, GIFs — in one call. Use this to meter a plan limit:
1970
+ * paging `list()` and summing `size` counts uploads only, and an HLS ladder typically runs
1971
+ * well past the size of the source it came from.
1972
+ *
1973
+ * Check `unmeasuredAssets`. A nonzero value means some assets have derivatives whose bytes
1974
+ * have not been measured, so `totalBytes` is a floor rather than the full number.
1975
+ *
1976
+ * @example
1977
+ * ```typescript
1978
+ * const usage = await cdn.getStorageUsage({
1979
+ * projectSlug: 'my-project',
1980
+ * folder: '/customers/acme',
1981
+ * });
1982
+ * console.log(`${usage.totalFormatted} across ${usage.objectCount} objects`);
1983
+ * console.log(` uploads: ${usage.breakdown.originals.bytesFormatted}`);
1984
+ * console.log(` derived: ${usage.breakdown.derived.bytesFormatted}`);
1985
+ * ```
1986
+ */
1987
+ async getStorageUsage(request = {}) {
1988
+ const params = new URLSearchParams();
1989
+ if (request.projectSlug) params.set("projectSlug", request.projectSlug);
1990
+ if (request.environment) params.set("environment", request.environment);
1991
+ if (request.folder) params.set("folder", request.folder);
1992
+ const query = params.toString();
1993
+ return this.http.get(`/cdn/usage/storage${query ? `?${query}` : ""}`);
1994
+ }
1956
1995
  convertUsageDates(usage) {
1957
1996
  if (typeof usage.periodStart === "string") {
1958
1997
  usage.periodStart = new Date(usage.periodStart);
@@ -2229,6 +2268,112 @@ var CDN = class {
2229
2268
  return this.convertMergeJobDates(job);
2230
2269
  }
2231
2270
  // ============================================================================
2271
+ // Video Render Methods
2272
+ // ============================================================================
2273
+ /**
2274
+ * Render a declarative video spec into a video asset
2275
+ *
2276
+ * The spec describes scenes, layers, timing and media as data. There are no
2277
+ * compositions to write and no code to upload — Stack0 owns the renderer.
2278
+ *
2279
+ * `@stack0/video-spec` has the full types, validation and worked examples;
2280
+ * validating locally before you POST turns a round trip into a type error.
2281
+ *
2282
+ * @example
2283
+ * ```typescript
2284
+ * const job = await cdn.createRenderJob({
2285
+ * projectSlug: 'my-project',
2286
+ * spec: {
2287
+ * canvas: { width: 1080, height: 1920, fps: 30 },
2288
+ * audio: [{ src: musicAssetId, volume: 0.34 }],
2289
+ * scenes: [
2290
+ * {
2291
+ * id: 'hook',
2292
+ * duration: 2.5,
2293
+ * layers: [
2294
+ * { type: 'image', src: afterImageAssetId, zoom: { from: 1, to: 1.06 } },
2295
+ * { type: 'caption', text: 'i told my mom\nthe kitchen exploded', position: 'upperThird' },
2296
+ * ],
2297
+ * },
2298
+ * {
2299
+ * // No duration: the thread derives its own length from the messages
2300
+ * id: 'payoff',
2301
+ * layers: [
2302
+ * {
2303
+ * type: 'messageThread',
2304
+ * recipient: 'Mom',
2305
+ * imageSrc: afterImageAssetId,
2306
+ * messages: [
2307
+ * { sender: 'recipient', text: 'you home?', history: true },
2308
+ * { sender: 'user', text: '[IMAGE]' },
2309
+ * { sender: 'recipient', text: 'WHAT IS THAT' },
2310
+ * ],
2311
+ * },
2312
+ * ],
2313
+ * },
2314
+ * ],
2315
+ * },
2316
+ * webhookUrl: 'https://your-app.com/webhook',
2317
+ * });
2318
+ * console.log(job.durationInFrames); // known before rendering starts
2319
+ * ```
2320
+ */
2321
+ async createRenderJob(request) {
2322
+ const response = await this.http.post("/cdn/video/render", request);
2323
+ return this.convertRenderJobDates(response);
2324
+ }
2325
+ /**
2326
+ * Get a render job by ID with output asset details
2327
+ *
2328
+ * @example
2329
+ * ```typescript
2330
+ * const job = await cdn.getRenderJob('job-id');
2331
+ * if (job.status === 'completed' && job.outputAsset) {
2332
+ * console.log(`Output video: ${job.outputAsset.cdnUrl}`);
2333
+ * }
2334
+ * ```
2335
+ */
2336
+ async getRenderJob(jobId) {
2337
+ const response = await this.http.get(`/cdn/video/render/${jobId}`);
2338
+ return this.convertRenderJobDates(response);
2339
+ }
2340
+ /**
2341
+ * List render jobs with optional filters
2342
+ */
2343
+ async listRenderJobs(request) {
2344
+ const params = new URLSearchParams();
2345
+ params.set("projectSlug", request.projectSlug);
2346
+ if (request.status) params.set("status", request.status);
2347
+ if (request.limit) params.set("limit", request.limit.toString());
2348
+ if (request.offset) params.set("offset", request.offset.toString());
2349
+ const response = await this.http.get(`/cdn/video/render?${params.toString()}`);
2350
+ return {
2351
+ ...response,
2352
+ jobs: response.jobs.map((job) => this.convertRenderJobDates(job))
2353
+ };
2354
+ }
2355
+ /**
2356
+ * Cancel a pending or processing render job
2357
+ */
2358
+ async cancelRenderJob(jobId) {
2359
+ return this.http.post(`/cdn/video/render/${jobId}/cancel`, {});
2360
+ }
2361
+ convertRenderJobDates(job) {
2362
+ if (typeof job.createdAt === "string") {
2363
+ job.createdAt = new Date(job.createdAt);
2364
+ }
2365
+ if (job.updatedAt && typeof job.updatedAt === "string") {
2366
+ job.updatedAt = new Date(job.updatedAt);
2367
+ }
2368
+ if (job.startedAt && typeof job.startedAt === "string") {
2369
+ job.startedAt = new Date(job.startedAt);
2370
+ }
2371
+ if (job.completedAt && typeof job.completedAt === "string") {
2372
+ job.completedAt = new Date(job.completedAt);
2373
+ }
2374
+ return job;
2375
+ }
2376
+ // ============================================================================
2232
2377
  // S3 Import Methods
2233
2378
  // ============================================================================
2234
2379
  /**
@@ -5625,6 +5770,92 @@ var Workflows = class {
5625
5770
  }
5626
5771
  };
5627
5772
 
5773
+ // src/mail/webhooks.ts
5774
+ var WebhookVerificationError = class extends Error {
5775
+ constructor(message) {
5776
+ super(message);
5777
+ this.name = "WebhookVerificationError";
5778
+ }
5779
+ };
5780
+ function header(headers, name) {
5781
+ if (typeof headers.get === "function") return headers.get(name) ?? void 0;
5782
+ const record = headers;
5783
+ for (const key of Object.keys(record)) {
5784
+ if (key.toLowerCase() === name) {
5785
+ const v = record[key];
5786
+ return Array.isArray(v) ? v[0] : v;
5787
+ }
5788
+ }
5789
+ return void 0;
5790
+ }
5791
+ function base64ToBytes(b64) {
5792
+ const bin = atob(b64);
5793
+ const out = new Uint8Array(bin.length);
5794
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
5795
+ return out;
5796
+ }
5797
+ function bytesToBase64(bytes) {
5798
+ let bin = "";
5799
+ for (const b of bytes) bin += String.fromCharCode(b);
5800
+ return btoa(bin);
5801
+ }
5802
+ async function subtle() {
5803
+ const global = globalThis.crypto;
5804
+ if (global?.subtle) return global.subtle;
5805
+ const specifier = "node:crypto";
5806
+ const nodeCrypto = await import(
5807
+ /* webpackIgnore: true */
5808
+ /* @vite-ignore */
5809
+ specifier
5810
+ );
5811
+ return nodeCrypto.webcrypto.subtle;
5812
+ }
5813
+ function timingSafeEqual(a, b) {
5814
+ if (a.length !== b.length) return false;
5815
+ let diff = 0;
5816
+ for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
5817
+ return diff === 0;
5818
+ }
5819
+ async function verifyInboundWebhook(options) {
5820
+ const id = header(options.headers, "webhook-id");
5821
+ const timestamp = header(options.headers, "webhook-timestamp");
5822
+ const signatures = header(options.headers, "webhook-signature");
5823
+ if (!id || !timestamp || !signatures) {
5824
+ throw new WebhookVerificationError("missing webhook-id, webhook-timestamp, or webhook-signature header");
5825
+ }
5826
+ const seconds = Number(timestamp);
5827
+ if (!Number.isInteger(seconds)) throw new WebhookVerificationError("webhook-timestamp is not a whole number");
5828
+ const now = Math.floor((options.now ?? /* @__PURE__ */ new Date()).getTime() / 1e3);
5829
+ const tolerance = options.toleranceSeconds ?? 300;
5830
+ if (Math.abs(now - seconds) > tolerance) {
5831
+ throw new WebhookVerificationError(`webhook-timestamp is more than ${tolerance} seconds from now`);
5832
+ }
5833
+ const body = typeof options.payload === "string" ? options.payload : new TextDecoder().decode(options.payload instanceof ArrayBuffer ? new Uint8Array(options.payload) : options.payload);
5834
+ const secret = options.secret.startsWith("whsec_") ? options.secret.slice(6) : options.secret;
5835
+ const webcrypto = await subtle();
5836
+ const key = await webcrypto.importKey(
5837
+ "raw",
5838
+ base64ToBytes(secret),
5839
+ { name: "HMAC", hash: "SHA-256" },
5840
+ false,
5841
+ ["sign"]
5842
+ );
5843
+ const mac = new Uint8Array(
5844
+ await webcrypto.sign("HMAC", key, new TextEncoder().encode(`${id}.${seconds}.${body}`))
5845
+ );
5846
+ const expected = bytesToBase64(mac);
5847
+ const matched = signatures.split(" ").map((s) => s.split(",", 2)).some(([version, sig]) => version === "v1" && sig !== void 0 && timingSafeEqual(sig, expected));
5848
+ if (!matched) throw new WebhookVerificationError("no webhook-signature matches");
5849
+ let payload;
5850
+ try {
5851
+ payload = JSON.parse(body);
5852
+ } catch {
5853
+ throw new WebhookVerificationError("the payload is not JSON");
5854
+ }
5855
+ if (payload.id !== id) throw new WebhookVerificationError("the payload id does not match webhook-id");
5856
+ return payload;
5857
+ }
5858
+
5628
5859
  // src/index.ts
5629
5860
  var Stack0 = class {
5630
5861
  http;
@@ -5685,6 +5916,6 @@ var Stack0 = class {
5685
5916
  };
5686
5917
  var src_default = Stack0;
5687
5918
 
5688
- export { Audiences, CDN, Campaigns, Contacts, Crawl$ as Crawl, Crawl$, Documents, Domains, Events, Extraction, Integrations, Mail, Mailboxes, Map$ as Map, Map$, Marketing, Screenshots, Sequences, Stack0, Templates, Webdata, Workflows, src_default as default };
5919
+ export { Audiences, CDN, Campaigns, Contacts, Crawl$ as Crawl, Crawl$, Documents, Domains, Events, Extraction, Integrations, Mail, Mailboxes, Map$ as Map, Map$, Marketing, Screenshots, Sequences, Stack0, Templates, Webdata, WebhookVerificationError, Workflows, src_default as default, verifyInboundWebhook };
5689
5920
  //# sourceMappingURL=index.mjs.map
5690
5921
  //# sourceMappingURL=index.mjs.map