@pithy-sh/cloudflare 0.1.2 → 0.1.4
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/ai/aiManager.d.ts +101 -0
- package/dist/ai/aiManager.d.ts.map +1 -0
- package/dist/ai/aiManager.js +150 -0
- package/dist/ai/vectorizeManager.d.ts +69 -0
- package/dist/ai/vectorizeManager.d.ts.map +1 -0
- package/dist/ai/vectorizeManager.js +103 -0
- package/dist/ai/vectorizeProvisioner.d.ts +125 -0
- package/dist/ai/vectorizeProvisioner.d.ts.map +1 -0
- package/dist/ai/vectorizeProvisioner.js +197 -0
- package/dist/client/accounts.d.ts +50 -0
- package/dist/client/accounts.d.ts.map +1 -0
- package/dist/client/accounts.js +57 -0
- package/dist/client/clients.d.ts +124 -0
- package/dist/client/clients.d.ts.map +1 -0
- package/dist/client/clients.js +218 -0
- package/dist/client/errors.d.ts +75 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +100 -0
- package/dist/client/manager.d.ts +47 -0
- package/dist/client/manager.d.ts.map +1 -0
- package/dist/client/manager.js +53 -0
- package/dist/d1/d1Manager.d.ts +76 -0
- package/dist/d1/d1Manager.d.ts.map +1 -0
- package/dist/d1/d1Manager.js +130 -0
- package/dist/d1/d1PreparedStatement.d.ts +26 -0
- package/dist/d1/d1PreparedStatement.d.ts.map +1 -0
- package/dist/d1/d1PreparedStatement.js +93 -0
- package/dist/d1/d1Provisioner.d.ts +30 -0
- package/dist/d1/d1Provisioner.d.ts.map +1 -0
- package/dist/d1/d1Provisioner.js +69 -0
- package/dist/email/emailRoutingManager.d.ts +38 -0
- package/dist/email/emailRoutingManager.d.ts.map +1 -0
- package/dist/email/emailRoutingManager.js +126 -0
- package/dist/email/emailSendManager.d.ts +47 -0
- package/dist/email/emailSendManager.d.ts.map +1 -0
- package/dist/email/emailSendManager.js +54 -0
- package/dist/env/devVars.d.ts +67 -0
- package/dist/env/devVars.d.ts.map +1 -0
- package/dist/env/devVars.js +86 -0
- package/dist/hostnames/customHostnamesManager.d.ts +53 -0
- package/dist/hostnames/customHostnamesManager.d.ts.map +1 -0
- package/dist/hostnames/customHostnamesManager.js +86 -0
- package/dist/kv/kvManager.d.ts +78 -0
- package/dist/kv/kvManager.d.ts.map +1 -0
- package/dist/kv/kvManager.js +137 -0
- package/dist/kv/kvProvisioner.d.ts +30 -0
- package/dist/kv/kvProvisioner.d.ts.map +1 -0
- package/dist/kv/kvProvisioner.js +73 -0
- package/dist/media/assetSeeder.d.ts +33 -0
- package/dist/media/assetSeeder.d.ts.map +1 -0
- package/dist/media/assetSeeder.js +72 -0
- package/dist/media/imageManager.d.ts +36 -0
- package/dist/media/imageManager.d.ts.map +1 -0
- package/dist/media/imageManager.js +105 -0
- package/dist/media/ownership.d.ts +42 -0
- package/dist/media/ownership.d.ts.map +1 -0
- package/dist/media/ownership.js +48 -0
- package/dist/media/streamManager.d.ts +85 -0
- package/dist/media/streamManager.d.ts.map +1 -0
- package/dist/media/streamManager.js +147 -0
- package/dist/queue/queueManager.d.ts +86 -0
- package/dist/queue/queueManager.d.ts.map +1 -0
- package/dist/queue/queueManager.js +116 -0
- package/dist/r2/r2Credentials.d.ts +14 -0
- package/dist/r2/r2Credentials.d.ts.map +1 -0
- package/dist/r2/r2Credentials.js +15 -0
- package/dist/r2/r2Manager.d.ts +228 -0
- package/dist/r2/r2Manager.d.ts.map +1 -0
- package/dist/r2/r2Manager.js +407 -0
- package/dist/r2/r2Provisioner.d.ts +41 -0
- package/dist/r2/r2Provisioner.d.ts.map +1 -0
- package/dist/r2/r2Provisioner.js +88 -0
- package/dist/secrets/secretsStoreManager.d.ts +84 -0
- package/dist/secrets/secretsStoreManager.d.ts.map +1 -0
- package/dist/secrets/secretsStoreManager.js +149 -0
- package/dist/secrets/secretsStores.d.ts +49 -0
- package/dist/secrets/secretsStores.d.ts.map +1 -0
- package/dist/secrets/secretsStores.js +67 -0
- package/dist/test-utils/emailRoutingRules.d.ts +41 -0
- package/dist/test-utils/emailRoutingRules.d.ts.map +1 -0
- package/dist/test-utils/emailRoutingRules.js +91 -0
- package/dist/test-utils/fixtureReportSetup.d.ts +27 -0
- package/dist/test-utils/fixtureReportSetup.d.ts.map +1 -0
- package/dist/test-utils/fixtureReportSetup.js +32 -0
- package/dist/test-utils/fixtures.d.ts +226 -0
- package/dist/test-utils/fixtures.d.ts.map +1 -0
- package/dist/test-utils/fixtures.js +279 -0
- package/dist/test-utils/harness.d.ts +194 -0
- package/dist/test-utils/harness.d.ts.map +1 -0
- package/dist/test-utils/harness.js +353 -0
- package/dist/test-utils/inboundRecorder.d.ts +93 -0
- package/dist/test-utils/inboundRecorder.d.ts.map +1 -0
- package/dist/test-utils/inboundRecorder.js +143 -0
- package/dist/test-utils/integrationSetup.d.ts +24 -0
- package/dist/test-utils/integrationSetup.d.ts.map +1 -0
- package/dist/test-utils/integrationSetup.js +39 -0
- package/dist/test-utils/reap.d.ts +114 -0
- package/dist/test-utils/reap.d.ts.map +1 -0
- package/dist/test-utils/reap.js +187 -0
- package/dist/tokens/accountTokensManager.d.ts +159 -0
- package/dist/tokens/accountTokensManager.d.ts.map +1 -0
- package/dist/tokens/accountTokensManager.js +282 -0
- package/dist/tokens/permissions.d.ts +38 -0
- package/dist/tokens/permissions.d.ts.map +1 -0
- package/dist/tokens/permissions.js +52 -0
- package/dist/tokens/profiles.d.ts +104 -0
- package/dist/tokens/profiles.d.ts.map +1 -0
- package/dist/tokens/profiles.js +139 -0
- package/dist/turnstile/turnstileManager.d.ts +77 -0
- package/dist/turnstile/turnstileManager.d.ts.map +1 -0
- package/dist/turnstile/turnstileManager.js +140 -0
- package/dist/user/userManager.d.ts +38 -0
- package/dist/user/userManager.d.ts.map +1 -0
- package/dist/user/userManager.js +60 -0
- package/dist/workers/buildsManager.d.ts +118 -0
- package/dist/workers/buildsManager.d.ts.map +1 -0
- package/dist/workers/buildsManager.js +239 -0
- package/dist/workers/buildsTypes.d.ts +104 -0
- package/dist/workers/buildsTypes.d.ts.map +1 -0
- package/dist/workers/buildsTypes.js +89 -0
- package/dist/workers/workersBuildEvent.d.ts +66 -0
- package/dist/workers/workersBuildEvent.d.ts.map +1 -0
- package/dist/workers/workersBuildEvent.js +49 -0
- package/dist/workers/workersManager.d.ts +138 -0
- package/dist/workers/workersManager.d.ts.map +1 -0
- package/dist/workers/workersManager.js +314 -0
- package/dist/workers/workersProvisioner.d.ts +82 -0
- package/dist/workers/workersProvisioner.d.ts.map +1 -0
- package/dist/workers/workersProvisioner.js +108 -0
- package/dist/workflows/stepFailure.d.ts +182 -0
- package/dist/workflows/stepFailure.d.ts.map +1 -0
- package/dist/workflows/stepFailure.js +229 -0
- package/dist/workflows/workflowsClient.d.ts +89 -0
- package/dist/workflows/workflowsClient.d.ts.map +1 -0
- package/dist/workflows/workflowsClient.js +158 -0
- package/dist/zones/zonesManager.d.ts +52 -0
- package/dist/zones/zonesManager.d.ts.map +1 -0
- package/dist/zones/zonesManager.js +76 -0
- package/package.json +18 -9
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { CloudflareNotConfiguredError } from "../client/errors.js";
|
|
4
|
+
//#region src/media/ownership.ts
|
|
5
|
+
/**
|
|
6
|
+
* The ownership stamp on every Cloudflare Images and Stream asset Pithy creates.
|
|
7
|
+
*
|
|
8
|
+
* Every other resource this toolset provisions is isolated by its **name** —
|
|
9
|
+
* `<project>-<env>-<thing>`, because those namespaces are flat and account-wide (CLAUDE.md
|
|
10
|
+
* §Resource naming). Images and Stream cannot use that lever: their stores are just as flat and
|
|
11
|
+
* account-wide, but an asset is keyed by a **Cloudflare-minted id**, not by a name we choose. So
|
|
12
|
+
* metadata is the only place ownership can live, and without it two projects' assets in one account
|
|
13
|
+
* are indistinguishable — you cannot tell which app owns an asset, cannot sweep one app's assets at
|
|
14
|
+
* teardown, and `pithy doctor` is blind to them.
|
|
15
|
+
*
|
|
16
|
+
* The **same two keys** are used for both stores (Images `metadata`, Stream `meta`), so one query
|
|
17
|
+
* answers "what does this project own" across them rather than two dialects answering half each.
|
|
18
|
+
* Nothing else is imposed: a caller's own metadata rides along untouched — but it can never displace
|
|
19
|
+
* the reserved keys, which are merged last on purpose.
|
|
20
|
+
*/
|
|
21
|
+
/** The metadata key naming the owning project — the root `pithy.config.ts` `name`. */
|
|
22
|
+
const ASSET_PROJECT_KEY = "pithyProject";
|
|
23
|
+
/** The metadata key naming the owning environment (`dev` | `staging` | `production`). */
|
|
24
|
+
const ASSET_ENV_KEY = "pithyEnv";
|
|
25
|
+
/**
|
|
26
|
+
* Merge the ownership stamp over a caller's metadata bag.
|
|
27
|
+
*
|
|
28
|
+
* Ownership is applied **last**, so a caller-supplied `pithyProject`/`pithyEnv` cannot displace the
|
|
29
|
+
* real one — a fixture or a client-supplied bag claiming another project's assets would defeat the
|
|
30
|
+
* whole point of the stamp. A blank project or environment is refused rather than written: an asset
|
|
31
|
+
* stamped with an empty string reads as owned and still sweeps to nothing.
|
|
32
|
+
*/
|
|
33
|
+
function withAssetOwnership(owner, metadata) {
|
|
34
|
+
const project = owner.project.trim();
|
|
35
|
+
const env = owner.env.trim();
|
|
36
|
+
if (!project || !env) throw new CloudflareNotConfiguredError({
|
|
37
|
+
message: "A Cloudflare Images or Stream asset cannot be created without an owner.",
|
|
38
|
+
action: "Pass the project (requireProjectName) and the environment to the uploader.",
|
|
39
|
+
detail: `asset owner is incomplete: project="${owner.project}", env="${owner.env}"`
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
...metadata,
|
|
43
|
+
[ASSET_PROJECT_KEY]: project,
|
|
44
|
+
[ASSET_ENV_KEY]: env
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { ASSET_ENV_KEY, ASSET_PROJECT_KEY, withAssetOwnership };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import type { DirectUploadCreateParams, DirectUploadCreateResponse } from "cloudflare/resources/stream/direct-upload";
|
|
4
|
+
import type { StreamEditParams, Video } from "cloudflare/resources/stream/stream";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { CloudflareManager } from "../client/manager";
|
|
7
|
+
/**
|
|
8
|
+
* One download artifact's state, as returned by the Stream downloads API. The SDK types these
|
|
9
|
+
* responses as `unknown`, so this Zod object is the validated shape — it is the JS↔wire boundary
|
|
10
|
+
* for the raw downloads payloads.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DownloadEntry: z.ZodObject<{
|
|
13
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
14
|
+
error: "error";
|
|
15
|
+
inprogress: "inprogress";
|
|
16
|
+
ready: "ready";
|
|
17
|
+
}>>;
|
|
18
|
+
url: z.ZodOptional<z.ZodString>;
|
|
19
|
+
percentComplete: z.ZodOptional<z.ZodNumber>;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
export type DownloadEntry = z.infer<typeof DownloadEntry>;
|
|
22
|
+
/**
|
|
23
|
+
* The Stream downloads payload: the default (MP4) artifact, plus an optional audio-only (M4A) one.
|
|
24
|
+
* `GET /downloads` returns every type; `POST /downloads/{type}` returns just that type.
|
|
25
|
+
*/
|
|
26
|
+
export declare const StreamDownloadStatus: z.ZodObject<{
|
|
27
|
+
default: z.ZodObject<{
|
|
28
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
29
|
+
error: "error";
|
|
30
|
+
inprogress: "inprogress";
|
|
31
|
+
ready: "ready";
|
|
32
|
+
}>>;
|
|
33
|
+
url: z.ZodOptional<z.ZodString>;
|
|
34
|
+
percentComplete: z.ZodOptional<z.ZodNumber>;
|
|
35
|
+
}, z.core.$strip>;
|
|
36
|
+
audio: z.ZodOptional<z.ZodObject<{
|
|
37
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
38
|
+
error: "error";
|
|
39
|
+
inprogress: "inprogress";
|
|
40
|
+
ready: "ready";
|
|
41
|
+
}>>;
|
|
42
|
+
url: z.ZodOptional<z.ZodString>;
|
|
43
|
+
percentComplete: z.ZodOptional<z.ZodNumber>;
|
|
44
|
+
}, z.core.$strip>>;
|
|
45
|
+
}, z.core.$strip>;
|
|
46
|
+
export type StreamDownloadStatus = z.infer<typeof StreamDownloadStatus>;
|
|
47
|
+
/**
|
|
48
|
+
* Out-of-Worker Cloudflare Stream access over the REST API: delete, fetch, edit, direct uploads,
|
|
49
|
+
* and download (MP4 + audio-only) management from a CLI/CI/provisioning context. Video metadata is
|
|
50
|
+
* the SDK's own `Video` shape — this class imposes none of its own.
|
|
51
|
+
*
|
|
52
|
+
* The ownership stamp is imposed one layer up, by `withAssetOwnership` in `ownership.ts`, which every
|
|
53
|
+
* Pithy path that *creates* a video goes through (the seeder here, and media's `videoMinter`). Stream
|
|
54
|
+
* is account-flat and keys a video by a Cloudflare-minted uid, so `meta.pithyProject` is the only
|
|
55
|
+
* thing that says which project owns it.
|
|
56
|
+
*/
|
|
57
|
+
export declare class CloudflareStreamManager extends CloudflareManager {
|
|
58
|
+
/** Delete a video by its Stream UID. */
|
|
59
|
+
deleteVideo(videoId: string): Promise<void>;
|
|
60
|
+
/** Fetch a video's details by UID. */
|
|
61
|
+
getVideoDetails(videoId: string): Promise<Video>;
|
|
62
|
+
/** Update a video's metadata and settings. */
|
|
63
|
+
updateVideo(videoId: string, params: Omit<StreamEditParams, "account_id">): Promise<Video>;
|
|
64
|
+
/** Read the download status for a video (all artifact types). */
|
|
65
|
+
videoDownloadStatus(videoId: string): Promise<StreamDownloadStatus>;
|
|
66
|
+
/** Trigger Cloudflare to generate a downloadable MP4 for a video. */
|
|
67
|
+
createVideoDownload(videoId: string): Promise<StreamDownloadStatus>;
|
|
68
|
+
/**
|
|
69
|
+
* Request an audio-only (M4A) download for a video. The CF Node SDK does not expose the
|
|
70
|
+
* type-specific `POST /downloads/audio` endpoint, so this falls back to a direct `fetch` — the
|
|
71
|
+
* documented escape hatch — authenticated with the manager's API token.
|
|
72
|
+
*/
|
|
73
|
+
createAudioDownload(videoId: string): Promise<DownloadEntry>;
|
|
74
|
+
/**
|
|
75
|
+
* Read the audio-download status for a video. The SDK has no standalone `GET /downloads/audio`,
|
|
76
|
+
* so this reads the generic downloads endpoint and extracts the `audio` entry.
|
|
77
|
+
*/
|
|
78
|
+
audioDownloadStatus(videoId: string): Promise<DownloadEntry>;
|
|
79
|
+
/** Create a direct-upload URL for a client-side video upload. */
|
|
80
|
+
createDirectUpload(params: Omit<DirectUploadCreateParams, "account_id">): Promise<DirectUploadCreateResponse>;
|
|
81
|
+
getServiceType(): string;
|
|
82
|
+
/** Prove access by listing videos. Never throws. */
|
|
83
|
+
validateServiceAccess(): Promise<boolean>;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=streamManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streamManager.d.ts","sourceRoot":"","sources":["../../src/media/streamManager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AACtH,OAAO,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,oCAAoC,CAAC;AAClF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAWtD;;;;GAIG;AACH,eAAO,MAAM,aAAa;;;;;;;;iBAM8C,CAAC;AACzE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;iBAKwD,CAAC;AAC1F,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AA2BxE;;;;;;;;;GASG;AACH,qBAAa,uBAAwB,SAAQ,iBAAiB;IAC5D,wCAAwC;IAClC,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhD;IAED,sCAAsC;IAChC,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAIrD;IAED,8CAA8C;IACxC,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,YAAY,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAI/F;IAED,iEAAiE;IAC3D,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CASxE;IAED,qEAAqE;IAC/D,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CASxE;IAED;;;;OAIG;IACG,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA4BjE;IAED;;;OAGG;IACG,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAUjE;IAED,iEAAiE;IAC3D,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,CAAC,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAIlH;IAED,cAAc,IAAI,MAAM,CAEvB;IAED,oDAAoD;IAC9C,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO9C;CACF"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { CloudflareInvalidResponseError, cloudflareRequest } from "../client/errors.js";
|
|
4
|
+
import { CloudflareManager } from "../client/manager.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
//#region src/media/streamManager.ts
|
|
7
|
+
/**
|
|
8
|
+
* Per-request CF SDK options for every Stream call: a 10s timeout and up to 3 retries — the same
|
|
9
|
+
* generous envelope the Images manager uses, since video operations can be slow.
|
|
10
|
+
*/
|
|
11
|
+
const requestOptions = {
|
|
12
|
+
timeout: 1e4,
|
|
13
|
+
maxRetries: 3
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* One download artifact's state, as returned by the Stream downloads API. The SDK types these
|
|
17
|
+
* responses as `unknown`, so this Zod object is the validated shape — it is the JS↔wire boundary
|
|
18
|
+
* for the raw downloads payloads.
|
|
19
|
+
*/
|
|
20
|
+
const DownloadEntry = z.object({
|
|
21
|
+
status: z.enum([
|
|
22
|
+
"error",
|
|
23
|
+
"inprogress",
|
|
24
|
+
"ready"
|
|
25
|
+
]).optional().describe("Where this download is in its lifecycle."),
|
|
26
|
+
url: z.string().optional().describe("The download URL, present once the artifact is ready."),
|
|
27
|
+
percentComplete: z.number().optional().describe("Generation progress, 0–100, while in progress.")
|
|
28
|
+
}).describe("The state of one Stream download artifact (MP4 or audio).");
|
|
29
|
+
/**
|
|
30
|
+
* The Stream downloads payload: the default (MP4) artifact, plus an optional audio-only (M4A) one.
|
|
31
|
+
* `GET /downloads` returns every type; `POST /downloads/{type}` returns just that type.
|
|
32
|
+
*/
|
|
33
|
+
const StreamDownloadStatus = z.object({
|
|
34
|
+
default: DownloadEntry.describe("The default MP4 download artifact."),
|
|
35
|
+
audio: DownloadEntry.optional().describe("The audio-only (M4A) download artifact, when requested.")
|
|
36
|
+
}).describe("The set of download artifacts Cloudflare Stream has generated for a video.");
|
|
37
|
+
/** The envelope the raw audio-download endpoint returns: `{ result: { audio } }`. */
|
|
38
|
+
const AudioDownloadEnvelope = z.object({ result: z.object({ audio: DownloadEntry.describe("The audio-only download artifact.") }).describe("The CF API result wrapper for the audio download.") }).describe("The raw `POST /downloads/audio` response envelope.");
|
|
39
|
+
/**
|
|
40
|
+
* Validate a raw downloads payload against `StreamDownloadStatus`, mapping a Zod failure to a
|
|
41
|
+
* `CloudflareInvalidResponseError` (the response did not match its expected shape) rather than
|
|
42
|
+
* letting it surface as a generic request failure.
|
|
43
|
+
*/
|
|
44
|
+
function parseDownloadStatus(response) {
|
|
45
|
+
const result = StreamDownloadStatus.safeParse(response);
|
|
46
|
+
if (!result.success) throw new CloudflareInvalidResponseError({
|
|
47
|
+
message: "A Stream downloads response had an unexpected shape.",
|
|
48
|
+
detail: result.error.message
|
|
49
|
+
});
|
|
50
|
+
return result.data;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Out-of-Worker Cloudflare Stream access over the REST API: delete, fetch, edit, direct uploads,
|
|
54
|
+
* and download (MP4 + audio-only) management from a CLI/CI/provisioning context. Video metadata is
|
|
55
|
+
* the SDK's own `Video` shape — this class imposes none of its own.
|
|
56
|
+
*
|
|
57
|
+
* The ownership stamp is imposed one layer up, by `withAssetOwnership` in `ownership.ts`, which every
|
|
58
|
+
* Pithy path that *creates* a video goes through (the seeder here, and media's `videoMinter`). Stream
|
|
59
|
+
* is account-flat and keys a video by a Cloudflare-minted uid, so `meta.pithyProject` is the only
|
|
60
|
+
* thing that says which project owns it.
|
|
61
|
+
*/
|
|
62
|
+
var CloudflareStreamManager = class extends CloudflareManager {
|
|
63
|
+
/** Delete a video by its Stream UID. */
|
|
64
|
+
async deleteVideo(videoId) {
|
|
65
|
+
await cloudflareRequest(`Stream delete '${videoId}'`, () => this.getClient().stream.delete(videoId, { account_id: this.accountId }, requestOptions));
|
|
66
|
+
}
|
|
67
|
+
/** Fetch a video's details by UID. */
|
|
68
|
+
async getVideoDetails(videoId) {
|
|
69
|
+
return cloudflareRequest(`Stream get details for '${videoId}'`, () => this.getClient().stream.get(videoId, { account_id: this.accountId }, requestOptions));
|
|
70
|
+
}
|
|
71
|
+
/** Update a video's metadata and settings. */
|
|
72
|
+
async updateVideo(videoId, params) {
|
|
73
|
+
return cloudflareRequest(`Stream update '${videoId}'`, () => this.getClient().stream.edit(videoId, {
|
|
74
|
+
account_id: this.accountId,
|
|
75
|
+
...params
|
|
76
|
+
}, requestOptions));
|
|
77
|
+
}
|
|
78
|
+
/** Read the download status for a video (all artifact types). */
|
|
79
|
+
async videoDownloadStatus(videoId) {
|
|
80
|
+
return cloudflareRequest(`Stream download status for '${videoId}'`, async () => {
|
|
81
|
+
return parseDownloadStatus(await this.getClient().stream.downloads.get(videoId, { account_id: this.accountId }, requestOptions));
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/** Trigger Cloudflare to generate a downloadable MP4 for a video. */
|
|
85
|
+
async createVideoDownload(videoId) {
|
|
86
|
+
return cloudflareRequest(`Stream create download for '${videoId}'`, async () => {
|
|
87
|
+
return parseDownloadStatus(await this.getClient().stream.downloads.create(videoId, { account_id: this.accountId }, requestOptions));
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Request an audio-only (M4A) download for a video. The CF Node SDK does not expose the
|
|
92
|
+
* type-specific `POST /downloads/audio` endpoint, so this falls back to a direct `fetch` — the
|
|
93
|
+
* documented escape hatch — authenticated with the manager's API token.
|
|
94
|
+
*/
|
|
95
|
+
async createAudioDownload(videoId) {
|
|
96
|
+
return cloudflareRequest(`Stream create audio download for '${videoId}'`, async () => {
|
|
97
|
+
const response = await fetch(`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/stream/${videoId}/downloads/audio`, {
|
|
98
|
+
method: "POST",
|
|
99
|
+
headers: {
|
|
100
|
+
Authorization: `Bearer ${this.getApiToken()}`,
|
|
101
|
+
"Content-Type": "application/json"
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
if (!response.ok) throw new CloudflareInvalidResponseError({
|
|
105
|
+
message: "The Stream audio-download request failed.",
|
|
106
|
+
detail: `Stream audio download returned ${response.status}: ${await response.text()}`
|
|
107
|
+
});
|
|
108
|
+
const json = await response.json();
|
|
109
|
+
const envelope = AudioDownloadEnvelope.safeParse(json);
|
|
110
|
+
if (!envelope.success) throw new CloudflareInvalidResponseError({
|
|
111
|
+
message: "The Stream audio-download response had an unexpected shape.",
|
|
112
|
+
detail: envelope.error.message
|
|
113
|
+
});
|
|
114
|
+
return envelope.data.result.audio;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Read the audio-download status for a video. The SDK has no standalone `GET /downloads/audio`,
|
|
119
|
+
* so this reads the generic downloads endpoint and extracts the `audio` entry.
|
|
120
|
+
*/
|
|
121
|
+
async audioDownloadStatus(videoId) {
|
|
122
|
+
return cloudflareRequest(`Stream audio download status for '${videoId}'`, async () => {
|
|
123
|
+
return parseDownloadStatus(await this.getClient().stream.downloads.get(videoId, { account_id: this.accountId }, requestOptions)).audio ?? {};
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/** Create a direct-upload URL for a client-side video upload. */
|
|
127
|
+
async createDirectUpload(params) {
|
|
128
|
+
return cloudflareRequest("Stream create direct upload", () => this.getClient().stream.directUpload.create({
|
|
129
|
+
account_id: this.accountId,
|
|
130
|
+
...params
|
|
131
|
+
}, requestOptions));
|
|
132
|
+
}
|
|
133
|
+
getServiceType() {
|
|
134
|
+
return "Cloudflare Stream";
|
|
135
|
+
}
|
|
136
|
+
/** Prove access by listing videos. Never throws. */
|
|
137
|
+
async validateServiceAccess() {
|
|
138
|
+
try {
|
|
139
|
+
await this.getClient().stream.list({ account_id: this.accountId });
|
|
140
|
+
return true;
|
|
141
|
+
} catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
//#endregion
|
|
147
|
+
export { CloudflareStreamManager, DownloadEntry, StreamDownloadStatus };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import type { MessageBulkPushResponse } from "cloudflare/resources/queues/messages";
|
|
4
|
+
import { CloudflareManager, type CloudflareManagerConfig } from "../client/manager";
|
|
5
|
+
/** Config for the Queue manager: the shared client config plus the queue it targets by name. */
|
|
6
|
+
export interface QueueManagerConfig extends CloudflareManagerConfig {
|
|
7
|
+
/**
|
|
8
|
+
* The queue name. The REST API addresses queues by id, so the manager resolves this name to an
|
|
9
|
+
* id once (and caches it). Required for every operation.
|
|
10
|
+
*/
|
|
11
|
+
queueName: string;
|
|
12
|
+
}
|
|
13
|
+
/** The per-batch outcome of a `sendBatches` run: a partial-failure report, never a thrown batch. */
|
|
14
|
+
export interface QueueBatchResult {
|
|
15
|
+
/** The caller-supplied number identifying this batch. */
|
|
16
|
+
batchNumber: number;
|
|
17
|
+
/** Whether the batch was sent. */
|
|
18
|
+
success: boolean;
|
|
19
|
+
/** The failure reason, present only when `success` is false. */
|
|
20
|
+
error?: string;
|
|
21
|
+
/** How many messages the batch carried. */
|
|
22
|
+
messageCount: number;
|
|
23
|
+
}
|
|
24
|
+
/** The aggregate result of a `sendBatches` run. */
|
|
25
|
+
export interface SendBatchesResult {
|
|
26
|
+
/** How many batches sent successfully. */
|
|
27
|
+
successCount: number;
|
|
28
|
+
/** How many batches failed. */
|
|
29
|
+
failureCount: number;
|
|
30
|
+
/** The per-batch outcomes, in input order (truncated if a non-continuing run stopped early). */
|
|
31
|
+
results: QueueBatchResult[];
|
|
32
|
+
}
|
|
33
|
+
/** One batch of messages to send, with bookkeeping for progress reporting. */
|
|
34
|
+
export interface QueueBatch<T> {
|
|
35
|
+
/** A number identifying this batch in callbacks and results. */
|
|
36
|
+
batchNumber: number;
|
|
37
|
+
/** How many messages the batch carries (for progress reporting; need not equal `messages.length`). */
|
|
38
|
+
messageCount: number;
|
|
39
|
+
/** The message bodies to enqueue. */
|
|
40
|
+
messages: T[];
|
|
41
|
+
}
|
|
42
|
+
/** Options governing a `sendBatches` run. Callbacks are pure reporting hooks — no I/O is built in. */
|
|
43
|
+
export interface SendBatchesOptions {
|
|
44
|
+
/** Called before each batch is sent. */
|
|
45
|
+
onProgress?: (batchNumber: number, totalBatches: number, messageCount: number) => void;
|
|
46
|
+
/** Called after a batch sends successfully. */
|
|
47
|
+
onSuccess?: (batchNumber: number, messageCount: number) => void;
|
|
48
|
+
/** Called when a batch fails. */
|
|
49
|
+
onError?: (batchNumber: number, error: string) => void;
|
|
50
|
+
/** Milliseconds to wait between batches. Defaults to 0 (no delay). */
|
|
51
|
+
delayBetweenBatches?: number;
|
|
52
|
+
/** Keep going after a batch fails (collecting results). When false, the first failure throws. */
|
|
53
|
+
continueOnError?: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Out-of-Worker Cloudflare Queues access over the REST API: resolve a queue by name and bulk-push
|
|
57
|
+
* messages from a CLI/CI/provisioning context. Inside a Worker you use the `Queue` binding directly
|
|
58
|
+
* — this manager is the REST counterpart, addressed by queue id (resolved from the name).
|
|
59
|
+
*/
|
|
60
|
+
export declare class CloudflareQueueManager extends CloudflareManager {
|
|
61
|
+
private readonly queueName;
|
|
62
|
+
private queueId?;
|
|
63
|
+
constructor(config: QueueManagerConfig);
|
|
64
|
+
/** Resolve (and cache) the queue id for the configured name by listing the account's queues. */
|
|
65
|
+
getQueueId(): Promise<string>;
|
|
66
|
+
/** Bulk-push a batch of message bodies to the queue. Each item becomes one queue message. */
|
|
67
|
+
sendBatch<T>(messages: T[]): Promise<MessageBulkPushResponse>;
|
|
68
|
+
/** Send a single message. Convenience wrapper over `sendBatch`. */
|
|
69
|
+
send<T>(message: T): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Send many batches in sequence, collecting a per-batch result. By default it continues past a
|
|
72
|
+
* failed batch (reporting it) rather than aborting; set `continueOnError: false` to throw on the
|
|
73
|
+
* first failure. Optional callbacks are pure reporting hooks; nothing is logged or prompted.
|
|
74
|
+
*/
|
|
75
|
+
sendBatches<T>(batches: QueueBatch<T>[], options?: SendBatchesOptions): Promise<SendBatchesResult>;
|
|
76
|
+
/** The queue this manager targets, the account it lives in, and the resolved id (once known). */
|
|
77
|
+
getQueueInfo(): {
|
|
78
|
+
queueName: string;
|
|
79
|
+
accountId: string;
|
|
80
|
+
queueId?: string;
|
|
81
|
+
};
|
|
82
|
+
getServiceType(): string;
|
|
83
|
+
/** Prove access by listing the account's queues. Never throws. */
|
|
84
|
+
validateServiceAccess(): Promise<boolean>;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=queueManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queueManager.d.ts","sourceRoot":"","sources":["../../src/queue/queueManager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AAEpF,OAAO,EAAE,iBAAiB,EAAE,KAAK,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAEpF,gGAAgG;AAChG,MAAM,WAAW,kBAAmB,SAAQ,uBAAuB;IACjE;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,oGAAoG;AACpG,MAAM,WAAW,gBAAgB;IAC/B,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;IACpB,kCAAkC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,mDAAmD;AACnD,MAAM,WAAW,iBAAiB;IAChC,0CAA0C;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,gGAAgG;IAChG,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC7B;AAED,8EAA8E;AAC9E,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,sGAAsG;IACtG,YAAY,EAAE,MAAM,CAAC;IACrB,qCAAqC;IACrC,QAAQ,EAAE,CAAC,EAAE,CAAC;CACf;AAED,sGAAsG;AACtG,MAAM,WAAW,kBAAkB;IACjC,wCAAwC;IACxC,UAAU,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACvF,+CAA+C;IAC/C,SAAS,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IAChE,iCAAiC;IACjC,OAAO,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvD,sEAAsE;IACtE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iGAAiG;IACjG,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;;GAIG;AACH,qBAAa,sBAAuB,SAAQ,iBAAiB;IAC3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IAEnC,OAAO,CAAC,OAAO,CAAC,CAAS;IAEzB,YAAY,MAAM,EAAE,kBAAkB,EAMrC;IAED,gGAAgG;IAC1F,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAoBlC;IAED,6FAA6F;IACvF,SAAS,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAUlE;IAED,mEAAmE;IAC7D,IAAI,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvC;IAED;;;;OAIG;IACG,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAqC3G;IAED,iGAAiG;IACjG,YAAY,IAAI;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAEzE;IAED,cAAc,IAAI,MAAM,CAEvB;IAED,kEAAkE;IAC5D,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO9C;CACF"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { CloudflareNotConfiguredError, cloudflareRequest, reasonOf } from "../client/errors.js";
|
|
4
|
+
import { CloudflareManager } from "../client/manager.js";
|
|
5
|
+
import { NotFoundError } from "@pithy-sh/core/src/error/pithyError";
|
|
6
|
+
//#region src/queue/queueManager.ts
|
|
7
|
+
/**
|
|
8
|
+
* Out-of-Worker Cloudflare Queues access over the REST API: resolve a queue by name and bulk-push
|
|
9
|
+
* messages from a CLI/CI/provisioning context. Inside a Worker you use the `Queue` binding directly
|
|
10
|
+
* — this manager is the REST counterpart, addressed by queue id (resolved from the name).
|
|
11
|
+
*/
|
|
12
|
+
var CloudflareQueueManager = class extends CloudflareManager {
|
|
13
|
+
queueName;
|
|
14
|
+
queueId;
|
|
15
|
+
constructor(config) {
|
|
16
|
+
super(config);
|
|
17
|
+
if (!config.queueName) throw new CloudflareNotConfiguredError({ detail: "Missing queueName for Queue REST access." });
|
|
18
|
+
this.queueName = config.queueName;
|
|
19
|
+
}
|
|
20
|
+
/** Resolve (and cache) the queue id for the configured name by listing the account's queues. */
|
|
21
|
+
async getQueueId() {
|
|
22
|
+
if (this.queueId) return this.queueId;
|
|
23
|
+
const queues = await cloudflareRequest("list queues", async () => {
|
|
24
|
+
return (await this.getClient().queues.list({ account_id: this.accountId })).result;
|
|
25
|
+
});
|
|
26
|
+
const match = queues.find((queue) => queue.queue_name === this.queueName);
|
|
27
|
+
if (!match?.queue_id) {
|
|
28
|
+
const available = queues.map((queue) => queue.queue_name).join(", ");
|
|
29
|
+
throw new NotFoundError({
|
|
30
|
+
message: `Queue '${this.queueName}' not found.`,
|
|
31
|
+
detail: `Available queues: ${available || "(none)"}.`,
|
|
32
|
+
action: "Verify the queue name and that the API token has Queues access."
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
this.queueId = match.queue_id;
|
|
36
|
+
return this.queueId;
|
|
37
|
+
}
|
|
38
|
+
/** Bulk-push a batch of message bodies to the queue. Each item becomes one queue message. */
|
|
39
|
+
async sendBatch(messages) {
|
|
40
|
+
const queueId = await this.getQueueId();
|
|
41
|
+
return cloudflareRequest(`send batch of ${messages.length} message(s)`, async () => {
|
|
42
|
+
return await this.getClient().queues.messages.bulkPush(queueId, {
|
|
43
|
+
account_id: this.accountId,
|
|
44
|
+
messages: messages.map((body) => ({ body }))
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/** Send a single message. Convenience wrapper over `sendBatch`. */
|
|
49
|
+
async send(message) {
|
|
50
|
+
await this.sendBatch([message]);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Send many batches in sequence, collecting a per-batch result. By default it continues past a
|
|
54
|
+
* failed batch (reporting it) rather than aborting; set `continueOnError: false` to throw on the
|
|
55
|
+
* first failure. Optional callbacks are pure reporting hooks; nothing is logged or prompted.
|
|
56
|
+
*/
|
|
57
|
+
async sendBatches(batches, options = {}) {
|
|
58
|
+
const { onProgress, onSuccess, onError, delayBetweenBatches = 0, continueOnError = true } = options;
|
|
59
|
+
await this.getQueueId();
|
|
60
|
+
let successCount = 0;
|
|
61
|
+
let failureCount = 0;
|
|
62
|
+
const results = [];
|
|
63
|
+
for (const batch of batches) {
|
|
64
|
+
onProgress?.(batch.batchNumber, batches.length, batch.messageCount);
|
|
65
|
+
try {
|
|
66
|
+
await this.sendBatch(batch.messages);
|
|
67
|
+
onSuccess?.(batch.batchNumber, batch.messageCount);
|
|
68
|
+
successCount++;
|
|
69
|
+
results.push({
|
|
70
|
+
batchNumber: batch.batchNumber,
|
|
71
|
+
success: true,
|
|
72
|
+
messageCount: batch.messageCount
|
|
73
|
+
});
|
|
74
|
+
} catch (error) {
|
|
75
|
+
const reason = reasonOf(error);
|
|
76
|
+
onError?.(batch.batchNumber, reason);
|
|
77
|
+
failureCount++;
|
|
78
|
+
results.push({
|
|
79
|
+
batchNumber: batch.batchNumber,
|
|
80
|
+
success: false,
|
|
81
|
+
error: reason,
|
|
82
|
+
messageCount: batch.messageCount
|
|
83
|
+
});
|
|
84
|
+
if (!continueOnError) throw error;
|
|
85
|
+
}
|
|
86
|
+
if (!(batch === batches[batches.length - 1]) && delayBetweenBatches > 0) await new Promise((resolve) => setTimeout(resolve, delayBetweenBatches));
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
successCount,
|
|
90
|
+
failureCount,
|
|
91
|
+
results
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/** The queue this manager targets, the account it lives in, and the resolved id (once known). */
|
|
95
|
+
getQueueInfo() {
|
|
96
|
+
return {
|
|
97
|
+
queueName: this.queueName,
|
|
98
|
+
accountId: this.accountId,
|
|
99
|
+
queueId: this.queueId
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
getServiceType() {
|
|
103
|
+
return "Queues";
|
|
104
|
+
}
|
|
105
|
+
/** Prove access by listing the account's queues. Never throws. */
|
|
106
|
+
async validateServiceAccess() {
|
|
107
|
+
try {
|
|
108
|
+
await this.getClient().queues.list({ account_id: this.accountId });
|
|
109
|
+
return true;
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
//#endregion
|
|
116
|
+
export { CloudflareQueueManager };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
/**
|
|
5
|
+
* The R2 S3-compatible credential pair. R2 speaks the S3 protocol, so presigned-URL signing needs
|
|
6
|
+
* an access-key/secret-key pair (distinct from the CF API token). This is the canonical shape —
|
|
7
|
+
* `@pithy-sh/secrets` validates the persisted credential envelope against it.
|
|
8
|
+
*/
|
|
9
|
+
export declare const R2Credentials: z.ZodObject<{
|
|
10
|
+
accessKeyId: z.ZodString;
|
|
11
|
+
secretAccessKey: z.ZodString;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
export type R2Credentials = z.infer<typeof R2Credentials>;
|
|
14
|
+
//# sourceMappingURL=r2Credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"r2Credentials.d.ts","sourceRoot":"","sources":["../../src/r2/r2Credentials.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;GAIG;AACH,eAAO,MAAM,aAAa;;;iBAKiE,CAAC;AAC5F,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
//#region src/r2/r2Credentials.ts
|
|
5
|
+
/**
|
|
6
|
+
* The R2 S3-compatible credential pair. R2 speaks the S3 protocol, so presigned-URL signing needs
|
|
7
|
+
* an access-key/secret-key pair (distinct from the CF API token). This is the canonical shape —
|
|
8
|
+
* `@pithy-sh/secrets` validates the persisted credential envelope against it.
|
|
9
|
+
*/
|
|
10
|
+
const R2Credentials = z.object({
|
|
11
|
+
accessKeyId: z.string().min(1).describe("The R2 S3 access key id."),
|
|
12
|
+
secretAccessKey: z.string().min(1).describe("The R2 S3 secret access key. Never logged or serialized to clients.")
|
|
13
|
+
}).describe("The S3-compatible access-key/secret-key pair R2 uses to sign presigned URLs.");
|
|
14
|
+
//#endregion
|
|
15
|
+
export { R2Credentials };
|