@pithy-sh/cloudflare 0.1.1 → 0.1.3

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.
Files changed (139) hide show
  1. package/dist/ai/aiManager.d.ts +99 -0
  2. package/dist/ai/aiManager.d.ts.map +1 -0
  3. package/dist/ai/aiManager.js +148 -0
  4. package/dist/ai/vectorizeManager.d.ts +67 -0
  5. package/dist/ai/vectorizeManager.d.ts.map +1 -0
  6. package/dist/ai/vectorizeManager.js +101 -0
  7. package/dist/ai/vectorizeProvisioner.d.ts +123 -0
  8. package/dist/ai/vectorizeProvisioner.d.ts.map +1 -0
  9. package/dist/ai/vectorizeProvisioner.js +195 -0
  10. package/dist/client/accounts.d.ts +48 -0
  11. package/dist/client/accounts.d.ts.map +1 -0
  12. package/dist/client/accounts.js +55 -0
  13. package/dist/client/clients.d.ts +122 -0
  14. package/dist/client/clients.d.ts.map +1 -0
  15. package/dist/client/clients.js +216 -0
  16. package/dist/client/errors.d.ts +73 -0
  17. package/dist/client/errors.d.ts.map +1 -0
  18. package/dist/client/errors.js +98 -0
  19. package/dist/client/manager.d.ts +45 -0
  20. package/dist/client/manager.d.ts.map +1 -0
  21. package/dist/client/manager.js +51 -0
  22. package/dist/d1/d1Manager.d.ts +74 -0
  23. package/dist/d1/d1Manager.d.ts.map +1 -0
  24. package/dist/d1/d1Manager.js +128 -0
  25. package/dist/d1/d1PreparedStatement.d.ts +24 -0
  26. package/dist/d1/d1PreparedStatement.d.ts.map +1 -0
  27. package/dist/d1/d1PreparedStatement.js +91 -0
  28. package/dist/d1/d1Provisioner.d.ts +28 -0
  29. package/dist/d1/d1Provisioner.d.ts.map +1 -0
  30. package/dist/d1/d1Provisioner.js +67 -0
  31. package/dist/email/emailRoutingManager.d.ts +36 -0
  32. package/dist/email/emailRoutingManager.d.ts.map +1 -0
  33. package/dist/email/emailRoutingManager.js +124 -0
  34. package/dist/email/emailSendManager.d.ts +45 -0
  35. package/dist/email/emailSendManager.d.ts.map +1 -0
  36. package/dist/email/emailSendManager.js +52 -0
  37. package/dist/env/devVars.d.ts +65 -0
  38. package/dist/env/devVars.d.ts.map +1 -0
  39. package/dist/env/devVars.js +84 -0
  40. package/dist/hostnames/customHostnamesManager.d.ts +51 -0
  41. package/dist/hostnames/customHostnamesManager.d.ts.map +1 -0
  42. package/dist/hostnames/customHostnamesManager.js +84 -0
  43. package/dist/kv/kvManager.d.ts +76 -0
  44. package/dist/kv/kvManager.d.ts.map +1 -0
  45. package/dist/kv/kvManager.js +135 -0
  46. package/dist/kv/kvProvisioner.d.ts +28 -0
  47. package/dist/kv/kvProvisioner.d.ts.map +1 -0
  48. package/dist/kv/kvProvisioner.js +71 -0
  49. package/dist/media/assetSeeder.d.ts +31 -0
  50. package/dist/media/assetSeeder.d.ts.map +1 -0
  51. package/dist/media/assetSeeder.js +70 -0
  52. package/dist/media/imageManager.d.ts +34 -0
  53. package/dist/media/imageManager.d.ts.map +1 -0
  54. package/dist/media/imageManager.js +103 -0
  55. package/dist/media/ownership.d.ts +40 -0
  56. package/dist/media/ownership.d.ts.map +1 -0
  57. package/dist/media/ownership.js +46 -0
  58. package/dist/media/streamManager.d.ts +83 -0
  59. package/dist/media/streamManager.d.ts.map +1 -0
  60. package/dist/media/streamManager.js +145 -0
  61. package/dist/queue/queueManager.d.ts +84 -0
  62. package/dist/queue/queueManager.d.ts.map +1 -0
  63. package/dist/queue/queueManager.js +114 -0
  64. package/dist/r2/r2Credentials.d.ts +12 -0
  65. package/dist/r2/r2Credentials.d.ts.map +1 -0
  66. package/dist/r2/r2Credentials.js +13 -0
  67. package/dist/r2/r2Manager.d.ts +226 -0
  68. package/dist/r2/r2Manager.d.ts.map +1 -0
  69. package/dist/r2/r2Manager.js +405 -0
  70. package/dist/r2/r2Provisioner.d.ts +39 -0
  71. package/dist/r2/r2Provisioner.d.ts.map +1 -0
  72. package/dist/r2/r2Provisioner.js +86 -0
  73. package/dist/secrets/secretsStoreManager.d.ts +82 -0
  74. package/dist/secrets/secretsStoreManager.d.ts.map +1 -0
  75. package/dist/secrets/secretsStoreManager.js +147 -0
  76. package/dist/secrets/secretsStores.d.ts +47 -0
  77. package/dist/secrets/secretsStores.d.ts.map +1 -0
  78. package/dist/secrets/secretsStores.js +65 -0
  79. package/dist/test-utils/emailRoutingRules.d.ts +39 -0
  80. package/dist/test-utils/emailRoutingRules.d.ts.map +1 -0
  81. package/dist/test-utils/emailRoutingRules.js +89 -0
  82. package/dist/test-utils/fixtureReportSetup.d.ts +25 -0
  83. package/dist/test-utils/fixtureReportSetup.d.ts.map +1 -0
  84. package/dist/test-utils/fixtureReportSetup.js +30 -0
  85. package/dist/test-utils/fixtures.d.ts +224 -0
  86. package/dist/test-utils/fixtures.d.ts.map +1 -0
  87. package/dist/test-utils/fixtures.js +277 -0
  88. package/dist/test-utils/harness.d.ts +192 -0
  89. package/dist/test-utils/harness.d.ts.map +1 -0
  90. package/dist/test-utils/harness.js +351 -0
  91. package/dist/test-utils/inboundRecorder.d.ts +91 -0
  92. package/dist/test-utils/inboundRecorder.d.ts.map +1 -0
  93. package/dist/test-utils/inboundRecorder.js +141 -0
  94. package/dist/test-utils/integrationSetup.d.ts +22 -0
  95. package/dist/test-utils/integrationSetup.d.ts.map +1 -0
  96. package/dist/test-utils/integrationSetup.js +37 -0
  97. package/dist/test-utils/reap.d.ts +112 -0
  98. package/dist/test-utils/reap.d.ts.map +1 -0
  99. package/dist/test-utils/reap.js +185 -0
  100. package/dist/tokens/accountTokensManager.d.ts +157 -0
  101. package/dist/tokens/accountTokensManager.d.ts.map +1 -0
  102. package/dist/tokens/accountTokensManager.js +280 -0
  103. package/dist/tokens/permissions.d.ts +36 -0
  104. package/dist/tokens/permissions.d.ts.map +1 -0
  105. package/dist/tokens/permissions.js +50 -0
  106. package/dist/tokens/profiles.d.ts +102 -0
  107. package/dist/tokens/profiles.d.ts.map +1 -0
  108. package/dist/tokens/profiles.js +137 -0
  109. package/dist/turnstile/turnstileManager.d.ts +75 -0
  110. package/dist/turnstile/turnstileManager.d.ts.map +1 -0
  111. package/dist/turnstile/turnstileManager.js +138 -0
  112. package/dist/user/userManager.d.ts +36 -0
  113. package/dist/user/userManager.d.ts.map +1 -0
  114. package/dist/user/userManager.js +58 -0
  115. package/dist/workers/buildsManager.d.ts +116 -0
  116. package/dist/workers/buildsManager.d.ts.map +1 -0
  117. package/dist/workers/buildsManager.js +237 -0
  118. package/dist/workers/buildsTypes.d.ts +102 -0
  119. package/dist/workers/buildsTypes.d.ts.map +1 -0
  120. package/dist/workers/buildsTypes.js +87 -0
  121. package/dist/workers/workersBuildEvent.d.ts +64 -0
  122. package/dist/workers/workersBuildEvent.d.ts.map +1 -0
  123. package/dist/workers/workersBuildEvent.js +47 -0
  124. package/dist/workers/workersManager.d.ts +136 -0
  125. package/dist/workers/workersManager.d.ts.map +1 -0
  126. package/dist/workers/workersManager.js +312 -0
  127. package/dist/workers/workersProvisioner.d.ts +80 -0
  128. package/dist/workers/workersProvisioner.d.ts.map +1 -0
  129. package/dist/workers/workersProvisioner.js +106 -0
  130. package/dist/workflows/stepFailure.d.ts +180 -0
  131. package/dist/workflows/stepFailure.d.ts.map +1 -0
  132. package/dist/workflows/stepFailure.js +227 -0
  133. package/dist/workflows/workflowsClient.d.ts +87 -0
  134. package/dist/workflows/workflowsClient.d.ts.map +1 -0
  135. package/dist/workflows/workflowsClient.js +156 -0
  136. package/dist/zones/zonesManager.d.ts +50 -0
  137. package/dist/zones/zonesManager.d.ts.map +1 -0
  138. package/dist/zones/zonesManager.js +74 -0
  139. package/package.json +18 -9
@@ -0,0 +1,76 @@
1
+ import type { Key } from "cloudflare/resources/kv/namespaces/keys";
2
+ import { CloudflareManager, type CloudflareManagerConfig } from "../client/manager";
3
+ /** Config for the KV manager: the shared client config plus the namespace it targets. */
4
+ export interface KVManagerConfig extends CloudflareManagerConfig {
5
+ /** The KV namespace id (the REST API addresses namespaces by id, not binding name). */
6
+ namespaceId: string;
7
+ }
8
+ /** Options for a single KV write. TTL is explicit (CLAUDE.md §KV). */
9
+ export interface KVPutOptions {
10
+ /** Seconds until the key expires. Omit for no expiry. */
11
+ expirationTtl?: number;
12
+ /** Arbitrary metadata stored alongside the value. */
13
+ metadata?: Record<string, unknown>;
14
+ }
15
+ /** One entry in a `batchSet` call. */
16
+ export interface KVOperation {
17
+ /** The key to write. */
18
+ key: string;
19
+ /** The value to store. */
20
+ value: string;
21
+ /** Seconds until the key expires. */
22
+ expirationTtl?: number;
23
+ /** Metadata to store with the value. */
24
+ metadata?: Record<string, unknown>;
25
+ }
26
+ /** The per-key outcome of a `batchSet`: a partial-failure report, never a thrown batch. */
27
+ export interface KVBatchResult {
28
+ /** The key this result is for. */
29
+ key: string;
30
+ /** Whether the write succeeded. */
31
+ success: boolean;
32
+ /** The failure reason, present only when `success` is false. */
33
+ error?: string;
34
+ }
35
+ /** A KV value together with its metadata, as returned by `getWithMetadata`. */
36
+ export interface KVValueWithMetadata<M = unknown> {
37
+ /** The stored value, or null if the key is absent. */
38
+ value: string | null;
39
+ /** The stored metadata, or null if absent. */
40
+ metadata: M | null;
41
+ }
42
+ /**
43
+ * Out-of-Worker KV access over the REST API: namespace management and key reads/writes from a
44
+ * CLI/CI/provisioning context. Inside a Worker you use the `KVNamespace` binding directly — this
45
+ * manager is the REST counterpart, addressed by namespace id.
46
+ */
47
+ export declare class CloudflareKVManager extends CloudflareManager {
48
+ private readonly namespaceId;
49
+ constructor(config: KVManagerConfig);
50
+ /** Read a key's value as text. Returns null when the key is absent (the API 404s a missing key). */
51
+ get(key: string): Promise<string | null>;
52
+ /** Read a key and `JSON.parse` it. Returns null when absent; throws on malformed JSON. */
53
+ getJson<T = unknown>(key: string): Promise<T | null>;
54
+ /** Write a value, with optional TTL and metadata. */
55
+ set(key: string, value: string, options?: KVPutOptions): Promise<void>;
56
+ /** Delete a key. */
57
+ delete(key: string): Promise<void>;
58
+ /** List keys in the namespace, optionally filtered by prefix and bounded by limit. */
59
+ listKeys(prefix?: string, limit?: number): Promise<Key[]>;
60
+ /** Read a key's value and metadata together. Both are null when absent. */
61
+ getWithMetadata<M = unknown>(key: string): Promise<KVValueWithMetadata<M>>;
62
+ /**
63
+ * Write many keys, collecting a per-key result instead of failing the whole batch on the first
64
+ * error — the caller decides what to do with partial failure.
65
+ */
66
+ batchSet(operations: KVOperation[]): Promise<KVBatchResult[]>;
67
+ /** The namespace this manager targets and the account it lives in. */
68
+ getKVInfo(): {
69
+ namespaceId: string;
70
+ accountId: string;
71
+ };
72
+ getServiceType(): string;
73
+ /** Prove access by reading the namespace record. Never throws. */
74
+ validateServiceAccess(): Promise<boolean>;
75
+ }
76
+ //# sourceMappingURL=kvManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kvManager.d.ts","sourceRoot":"","sources":["../../src/kv/kvManager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,yCAAyC,CAAC;AAQnE,OAAO,EAAE,iBAAiB,EAAE,KAAK,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAEpF,yFAAyF;AACzF,MAAM,WAAW,eAAgB,SAAQ,uBAAuB;IAC9D,uFAAuF;IACvF,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,sEAAsE;AACtE,MAAM,WAAW,YAAY;IAC3B,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,sCAAsC;AACtC,MAAM,WAAW,WAAW;IAC1B,wBAAwB;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,2FAA2F;AAC3F,MAAM,WAAW,aAAa;IAC5B,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,mCAAmC;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,+EAA+E;AAC/E,MAAM,WAAW,mBAAmB,CAAC,CAAC,GAAG,OAAO;IAC9C,sDAAsD;IACtD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,8CAA8C;IAC9C,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAC;CACpB;AAED;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,iBAAiB;IACxD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAErC,YAAY,MAAM,EAAE,eAAe,EAMlC;IAED,oGAAoG;IAC9F,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAe7C;IAED,0FAA0F;IACpF,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAWzD;IAED,qDAAqD;IAC/C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAU3E;IAED,oBAAoB;IACd,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOvC;IAED,sFAAsF;IAChF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAS9D;IAED,2EAA2E;IACrE,eAAe,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAoB/E;IAED;;;OAGG;IACG,QAAQ,CAAC,UAAU,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAclE;IAED,sEAAsE;IACtE,SAAS,IAAI;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAEtD;IAED,cAAc,IAAI,MAAM,CAEvB;IAED,kEAAkE;IAC5D,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO9C;CACF"}
@@ -0,0 +1,135 @@
1
+ import { CloudflareInvalidResponseError, CloudflareNotConfiguredError, cloudflareRequest, isNotFoundError, reasonOf } from "../client/errors.js";
2
+ import { CloudflareManager } from "../client/manager.js";
3
+ //#region src/kv/kvManager.ts
4
+ /**
5
+ * Out-of-Worker KV access over the REST API: namespace management and key reads/writes from a
6
+ * CLI/CI/provisioning context. Inside a Worker you use the `KVNamespace` binding directly — this
7
+ * manager is the REST counterpart, addressed by namespace id.
8
+ */
9
+ var CloudflareKVManager = class extends CloudflareManager {
10
+ namespaceId;
11
+ constructor(config) {
12
+ super(config);
13
+ if (!config.namespaceId) throw new CloudflareNotConfiguredError({ detail: "Missing namespaceId for KV REST access." });
14
+ this.namespaceId = config.namespaceId;
15
+ }
16
+ /** Read a key's value as text. Returns null when the key is absent (the API 404s a missing key). */
17
+ async get(key) {
18
+ return cloudflareRequest(`KV get for key '${key}'`, async () => {
19
+ try {
20
+ return await (await this.getClient().kv.namespaces.values.get(key, {
21
+ account_id: this.accountId,
22
+ namespace_id: this.namespaceId
23
+ })).text();
24
+ } catch (error) {
25
+ if (isNotFoundError(error)) return null;
26
+ throw error;
27
+ }
28
+ });
29
+ }
30
+ /** Read a key and `JSON.parse` it. Returns null when absent; throws on malformed JSON. */
31
+ async getJson(key) {
32
+ const raw = await this.get(key);
33
+ if (raw === null) return null;
34
+ try {
35
+ return JSON.parse(raw);
36
+ } catch (error) {
37
+ throw new CloudflareInvalidResponseError({
38
+ message: `KV value for key '${key}' is not valid JSON.`,
39
+ detail: error instanceof Error ? error.message : String(error)
40
+ });
41
+ }
42
+ }
43
+ /** Write a value, with optional TTL and metadata. */
44
+ async set(key, value, options) {
45
+ await cloudflareRequest(`KV set for key '${key}'`, async () => {
46
+ await this.getClient().kv.namespaces.values.update(key, {
47
+ account_id: this.accountId,
48
+ namespace_id: this.namespaceId,
49
+ value,
50
+ ...options?.expirationTtl !== void 0 ? { expiration_ttl: options.expirationTtl } : {},
51
+ ...options?.metadata !== void 0 ? { metadata: JSON.stringify(options.metadata) } : {}
52
+ });
53
+ });
54
+ }
55
+ /** Delete a key. */
56
+ async delete(key) {
57
+ await cloudflareRequest(`KV delete for key '${key}'`, async () => {
58
+ await this.getClient().kv.namespaces.values.delete(key, {
59
+ account_id: this.accountId,
60
+ namespace_id: this.namespaceId
61
+ });
62
+ });
63
+ }
64
+ /** List keys in the namespace, optionally filtered by prefix and bounded by limit. */
65
+ async listKeys(prefix, limit) {
66
+ return cloudflareRequest("KV list keys", async () => {
67
+ return (await this.getClient().kv.namespaces.keys.list(this.namespaceId, {
68
+ account_id: this.accountId,
69
+ ...prefix !== void 0 ? { prefix } : {},
70
+ ...limit !== void 0 ? { limit } : {}
71
+ })).result;
72
+ });
73
+ }
74
+ /** Read a key's value and metadata together. Both are null when absent. */
75
+ async getWithMetadata(key) {
76
+ return cloudflareRequest(`KV getWithMetadata for key '${key}'`, async () => {
77
+ const [value, metadata] = await Promise.all([this.get(key), this.getClient().kv.namespaces.metadata.get(key, {
78
+ account_id: this.accountId,
79
+ namespace_id: this.namespaceId
80
+ }).catch((error) => {
81
+ if (isNotFoundError(error)) return null;
82
+ throw error;
83
+ })]);
84
+ return {
85
+ value,
86
+ metadata: metadata ?? null
87
+ };
88
+ });
89
+ }
90
+ /**
91
+ * Write many keys, collecting a per-key result instead of failing the whole batch on the first
92
+ * error — the caller decides what to do with partial failure.
93
+ */
94
+ async batchSet(operations) {
95
+ const results = [];
96
+ for (const operation of operations) try {
97
+ await this.set(operation.key, operation.value, {
98
+ expirationTtl: operation.expirationTtl,
99
+ metadata: operation.metadata
100
+ });
101
+ results.push({
102
+ key: operation.key,
103
+ success: true
104
+ });
105
+ } catch (error) {
106
+ results.push({
107
+ key: operation.key,
108
+ success: false,
109
+ error: reasonOf(error)
110
+ });
111
+ }
112
+ return results;
113
+ }
114
+ /** The namespace this manager targets and the account it lives in. */
115
+ getKVInfo() {
116
+ return {
117
+ namespaceId: this.namespaceId,
118
+ accountId: this.accountId
119
+ };
120
+ }
121
+ getServiceType() {
122
+ return "KV Storage";
123
+ }
124
+ /** Prove access by reading the namespace record. Never throws. */
125
+ async validateServiceAccess() {
126
+ try {
127
+ await this.getClient().kv.namespaces.get(this.namespaceId, { account_id: this.accountId });
128
+ return true;
129
+ } catch {
130
+ return false;
131
+ }
132
+ }
133
+ };
134
+ //#endregion
135
+ export { CloudflareKVManager };
@@ -0,0 +1,28 @@
1
+ import { z } from "zod";
2
+ import { CloudflareManager } from "../client/manager";
3
+ /** A KV namespace's identity, decoded from the create response. */
4
+ export declare const KVNamespaceInfo: z.ZodObject<{
5
+ id: z.ZodString;
6
+ title: z.ZodString;
7
+ }, z.core.$strip>;
8
+ export type KVNamespaceInfo = z.output<typeof KVNamespaceInfo>;
9
+ /**
10
+ * Account-level KV control plane: **create and delete namespaces**. Customers use this to stand up
11
+ * and tear down per-environment KV namespaces (e.g. ephemeral staging), and `pithy add`-driven
12
+ * provisioning uses it to create each environment's namespaces. Addressed by account — unlike
13
+ * {@link CloudflareKVManager}, which targets one namespace id for key reads/writes.
14
+ */
15
+ export declare class CloudflareKVProvisioner extends CloudflareManager {
16
+ getServiceType(): string;
17
+ /** Prove access by listing namespaces — a read, never a destructive create/delete. Never throws. */
18
+ validateServiceAccess(): Promise<boolean>;
19
+ /** Find a namespace by exact title in the account, or `null` — for idempotent provisioning. */
20
+ findNamespaceByTitle(title: string): Promise<KVNamespaceInfo | null>;
21
+ /** List every namespace in the account — for prefix-scan reconcile teardown. */
22
+ listNamespaces(): Promise<KVNamespaceInfo[]>;
23
+ /** Create a KV namespace by title; returns its id and title. */
24
+ createNamespace(title: string): Promise<KVNamespaceInfo>;
25
+ /** Delete a KV namespace by id. Idempotent — a missing namespace is not an error, so teardown can re-run safely. */
26
+ deleteNamespace(namespaceId: string): Promise<void>;
27
+ }
28
+ //# sourceMappingURL=kvProvisioner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kvProvisioner.d.ts","sourceRoot":"","sources":["../../src/kv/kvProvisioner.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,mEAAmE;AACnE,eAAO,MAAM,eAAe;;;iBAK4D,CAAC;AACzF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC;AAE/D;;;;;GAKG;AACH,qBAAa,uBAAwB,SAAQ,iBAAiB;IAC5D,cAAc,IAAI,MAAM,CAEvB;IAED,oGAAoG;IAC9F,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO9C;IAED,+FAA+F;IACzF,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAQzE;IAED,gFAAgF;IAC1E,cAAc,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CASjD;IAED,gEAAgE;IAC1D,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAK7D;IAED,oHAAoH;IAC9G,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASxD;CACF"}
@@ -0,0 +1,71 @@
1
+ import { cloudflareRequest, decodeResponse, isNotFoundError } from "../client/errors.js";
2
+ import { CloudflareManager } from "../client/manager.js";
3
+ import { z } from "zod";
4
+ //#region src/kv/kvProvisioner.ts
5
+ /** A KV namespace's identity, decoded from the create response. */
6
+ const KVNamespaceInfo = z.object({
7
+ id: z.string().describe("The CF-assigned namespace id used to address the namespace."),
8
+ title: z.string().describe("The namespace title.")
9
+ }).describe("A Cloudflare KV namespace's identity, as returned by the create endpoint.");
10
+ /**
11
+ * Account-level KV control plane: **create and delete namespaces**. Customers use this to stand up
12
+ * and tear down per-environment KV namespaces (e.g. ephemeral staging), and `pithy add`-driven
13
+ * provisioning uses it to create each environment's namespaces. Addressed by account — unlike
14
+ * {@link CloudflareKVManager}, which targets one namespace id for key reads/writes.
15
+ */
16
+ var CloudflareKVProvisioner = class extends CloudflareManager {
17
+ getServiceType() {
18
+ return "Cloudflare KV (control plane)";
19
+ }
20
+ /** Prove access by listing namespaces — a read, never a destructive create/delete. Never throws. */
21
+ async validateServiceAccess() {
22
+ try {
23
+ await this.getClient().kv.namespaces.list({ account_id: this.accountId });
24
+ return true;
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+ /** Find a namespace by exact title in the account, or `null` — for idempotent provisioning. */
30
+ async findNamespaceByTitle(title) {
31
+ return cloudflareRequest(`find KV namespace ${title}`, async () => {
32
+ for await (const ns of this.getClient().kv.namespaces.list({ account_id: this.accountId })) {
33
+ const parsed = KVNamespaceInfo.safeParse(ns);
34
+ if (parsed.success && parsed.data.title === title) return parsed.data;
35
+ }
36
+ return null;
37
+ });
38
+ }
39
+ /** List every namespace in the account — for prefix-scan reconcile teardown. */
40
+ async listNamespaces() {
41
+ return cloudflareRequest("list KV namespaces", async () => {
42
+ const namespaces = [];
43
+ for await (const ns of this.getClient().kv.namespaces.list({ account_id: this.accountId })) {
44
+ const parsed = KVNamespaceInfo.safeParse(ns);
45
+ if (parsed.success) namespaces.push(parsed.data);
46
+ }
47
+ return namespaces;
48
+ });
49
+ }
50
+ /** Create a KV namespace by title; returns its id and title. */
51
+ async createNamespace(title) {
52
+ const response = await cloudflareRequest(`create KV namespace ${title}`, () => this.getClient().kv.namespaces.create({
53
+ account_id: this.accountId,
54
+ title
55
+ }));
56
+ return decodeResponse(KVNamespaceInfo, response, "KV namespace create");
57
+ }
58
+ /** Delete a KV namespace by id. Idempotent — a missing namespace is not an error, so teardown can re-run safely. */
59
+ async deleteNamespace(namespaceId) {
60
+ await cloudflareRequest(`delete KV namespace ${namespaceId}`, async () => {
61
+ try {
62
+ await this.getClient().kv.namespaces.delete(namespaceId, { account_id: this.accountId });
63
+ } catch (error) {
64
+ if (isNotFoundError(error)) return;
65
+ throw error;
66
+ }
67
+ });
68
+ }
69
+ };
70
+ //#endregion
71
+ export { CloudflareKVProvisioner, KVNamespaceInfo };
@@ -0,0 +1,31 @@
1
+ import type { CloudflareImageManager } from "./imageManager";
2
+ import { type AssetOwner } from "./ownership";
3
+ import type { CloudflareStreamManager } from "./streamManager";
4
+ /**
5
+ * Upload image bytes to Cloudflare Images, stamped with the owner, returning the minted id. Images
6
+ * supports a direct byte upload, so this is a single `uploadImage({ file, metadata })` call — the
7
+ * manager JSON-encodes the metadata bag for the wire. The fixture's own metadata rides along; the
8
+ * ownership keys are merged last, so a fixture cannot claim another project's assets.
9
+ */
10
+ export declare function uploadImageBytes(manager: CloudflareImageManager, bytes: Uint8Array | Blob, opts: {
11
+ owner: AssetOwner;
12
+ metadata?: Record<string, string>;
13
+ }): Promise<{
14
+ id: string;
15
+ }>;
16
+ /**
17
+ * Upload video bytes to Cloudflare Stream, returning the minted uid. Stream has no single direct-bytes
18
+ * method, so this mints a one-time upload URL with `createDirectUpload` — carrying the stamped metadata
19
+ * as `meta` so ownership lands with the video — then POSTs the bytes to that URL as `multipart/form-data` (the
20
+ * basic direct-upload contract). Because the mint carries the metadata, no follow-up `updateVideo`
21
+ * stamp is needed; if a future Stream contract dropped mint-time `meta`, stamp with
22
+ * `updateVideo(uid, { meta })` after the upload.
23
+ */
24
+ export declare function uploadStreamBytes(manager: CloudflareStreamManager, bytes: Uint8Array | Blob, opts: {
25
+ owner: AssetOwner;
26
+ metadata?: Record<string, string>;
27
+ maxDurationSeconds?: number;
28
+ }): Promise<{
29
+ uid: string;
30
+ }>;
31
+ //# sourceMappingURL=assetSeeder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assetSeeder.d.ts","sourceRoot":"","sources":["../../src/media/assetSeeder.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EAAE,KAAK,UAAU,EAAsB,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAyB/D;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,sBAAsB,EAC/B,KAAK,EAAE,UAAU,GAAG,IAAI,EACxB,IAAI,EAAE;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAC7D,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAMzB;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,uBAAuB,EAChC,KAAK,EAAE,UAAU,GAAG,IAAI,EACxB,IAAI,EAAE;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1F,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAwB1B"}
@@ -0,0 +1,70 @@
1
+ import { CloudflareRequestError, cloudflareRequest, decodeResponse } from "../client/errors.js";
2
+ import { withAssetOwnership } from "./ownership.js";
3
+ import { z } from "zod";
4
+ //#region src/media/assetSeeder.ts
5
+ /**
6
+ * Seed-time byte uploads to the account-flat Cloudflare Images and Stream stores. Seeding hands a
7
+ * manager raw bytes and a metadata bag and gets back the minted asset id/uid to record — the one
8
+ * place these two stores accept fixture bytes from a CLI/CI context. Every wire response is
9
+ * validated with a local Zod object so a shape drift fails loudly (`cloudflare/invalid_response`)
10
+ * instead of surfacing as `undefined`. These calls are always remote: neither store has a local
11
+ * emulation, so even a `pithy seed` against dev writes into the same store production shares.
12
+ *
13
+ * Which is why {@link AssetOwner} is **required**, not optional. An asset here is keyed by a
14
+ * Cloudflare-minted id, so its metadata is the only thing that says which project seeded it —
15
+ * see `ownership.ts`. Seed fixtures are the likeliest source of debris, and an unattributable
16
+ * fixture asset is one nobody can sweep.
17
+ */
18
+ /** The default max video duration (seconds) CF Stream requires for a direct upload — 6 hours. */
19
+ const STREAM_MAX_DURATION_SECONDS = 21600;
20
+ /** The only field of an Images upload response the seeder needs: the minted image id. */
21
+ const ImageUploadResult = z.object({ id: z.string() });
22
+ /** The fields of a Stream direct-upload mint the seeder needs: the uid and the one-time upload URL. */
23
+ const StreamDirectUploadResult = z.object({
24
+ uid: z.string(),
25
+ uploadURL: z.string()
26
+ });
27
+ /**
28
+ * Upload image bytes to Cloudflare Images, stamped with the owner, returning the minted id. Images
29
+ * supports a direct byte upload, so this is a single `uploadImage({ file, metadata })` call — the
30
+ * manager JSON-encodes the metadata bag for the wire. The fixture's own metadata rides along; the
31
+ * ownership keys are merged last, so a fixture cannot claim another project's assets.
32
+ */
33
+ async function uploadImageBytes(manager, bytes, opts) {
34
+ const file = bytes instanceof File ? bytes : new File([bytes], "seed");
35
+ const response = await manager.uploadImage({
36
+ file,
37
+ metadata: withAssetOwnership(opts.owner, opts.metadata)
38
+ });
39
+ return { id: decodeResponse(ImageUploadResult, response, "Images seed upload").id };
40
+ }
41
+ /**
42
+ * Upload video bytes to Cloudflare Stream, returning the minted uid. Stream has no single direct-bytes
43
+ * method, so this mints a one-time upload URL with `createDirectUpload` — carrying the stamped metadata
44
+ * as `meta` so ownership lands with the video — then POSTs the bytes to that URL as `multipart/form-data` (the
45
+ * basic direct-upload contract). Because the mint carries the metadata, no follow-up `updateVideo`
46
+ * stamp is needed; if a future Stream contract dropped mint-time `meta`, stamp with
47
+ * `updateVideo(uid, { meta })` after the upload.
48
+ */
49
+ async function uploadStreamBytes(manager, bytes, opts) {
50
+ const mint = decodeResponse(StreamDirectUploadResult, await manager.createDirectUpload({
51
+ maxDurationSeconds: opts.maxDurationSeconds ?? STREAM_MAX_DURATION_SECONDS,
52
+ meta: withAssetOwnership(opts.owner, opts.metadata)
53
+ }), "Stream seed direct upload");
54
+ await cloudflareRequest("Stream seed byte upload", async () => {
55
+ const file = bytes instanceof Blob ? bytes : new Blob([bytes]);
56
+ const form = new FormData();
57
+ form.append("file", file, "seed");
58
+ const response = await fetch(mint.uploadURL, {
59
+ method: "POST",
60
+ body: form
61
+ });
62
+ if (!response.ok) throw new CloudflareRequestError({
63
+ message: "The Stream direct upload failed.",
64
+ detail: `Stream direct upload returned ${response.status}: ${await response.text()}`
65
+ });
66
+ });
67
+ return { uid: mint.uid };
68
+ }
69
+ //#endregion
70
+ export { uploadImageBytes, uploadStreamBytes };
@@ -0,0 +1,34 @@
1
+ import type { Image, V1CreateParams, V1EditParams } from "cloudflare/resources/images/v1/v1";
2
+ import type { DirectUploadCreateParams, DirectUploadCreateResponse } from "cloudflare/resources/images/v2/direct-uploads";
3
+ import type { V2ListParams } from "cloudflare/resources/images/v2/v2";
4
+ import { CloudflareManager } from "../client/manager";
5
+ /**
6
+ * Out-of-Worker Cloudflare Images access over the REST API: upload, fetch, edit, delete, list, and
7
+ * direct-upload URLs from a CLI/CI/provisioning context. Image metadata is an arbitrary
8
+ * `Record<string, string>` passthrough at this layer — the manager imposes no shape.
9
+ *
10
+ * The ownership stamp is imposed one layer up, by `withAssetOwnership` in `ownership.ts`, which every
11
+ * Pithy path that *creates* an image goes through (the seeder here, and media's `imageMinter`). The
12
+ * split is deliberate: this class stays a faithful REST client usable for reading and sweeping
13
+ * anyone's images, while nothing Pithy writes can escape carrying `pithyProject`/`pithyEnv`.
14
+ */
15
+ export declare class CloudflareImageManager extends CloudflareManager {
16
+ /** Upload an image via the V1 API. `account_id` is set from the manager's config. */
17
+ uploadImage(params: Omit<V1CreateParams, "account_id">): Promise<Image>;
18
+ /** Fetch details for one image by id. */
19
+ imageDetails(imageId: string): Promise<Image>;
20
+ /** Update an image's metadata and settings. */
21
+ updateImage(imageId: string, params: Omit<V1EditParams, "account_id">): Promise<Image>;
22
+ /** Delete an image by id. */
23
+ deleteImage(imageId: string): Promise<void>;
24
+ /** Create a direct-upload URL for a client-side upload (V2 API). */
25
+ createDirectUploadUrl(params: Omit<DirectUploadCreateParams, "account_id">): Promise<DirectUploadCreateResponse>;
26
+ /** List images (V2 API), first page only. Returns the page's images, never null. */
27
+ listImages(params?: Omit<V2ListParams, "account_id">): Promise<Image[]>;
28
+ /** List all images across every page, following the V2 continuation token. */
29
+ listAllImages(params?: Omit<V2ListParams, "account_id">): Promise<Image[]>;
30
+ getServiceType(): string;
31
+ /** Prove access by listing images. Never throws. */
32
+ validateServiceAccess(): Promise<boolean>;
33
+ }
34
+ //# sourceMappingURL=imageManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imageManager.d.ts","sourceRoot":"","sources":["../../src/media/imageManager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAC7F,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAC3B,MAAM,+CAA+C,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAEtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAoBtD;;;;;;;;;GASG;AACH,qBAAa,sBAAuB,SAAQ,iBAAiB;IAC3D,qFAAqF;IAC/E,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,YAAY,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAO5E;IAED,yCAAyC;IACnC,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAIlD;IAED,+CAA+C;IACzC,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAI3F;IAED,6BAA6B;IACvB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhD;IAED,oEAAoE;IAC9D,qBAAqB,CACzB,MAAM,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,CAAC,GACnD,OAAO,CAAC,0BAA0B,CAAC,CAOrC;IAED,oFAAoF;IAC9E,UAAU,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAK5E;IAED,8EAA8E;IACxE,aAAa,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAe/E;IAED,cAAc,IAAI,MAAM,CAEvB;IAED,oDAAoD;IAC9C,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO9C;CACF"}
@@ -0,0 +1,103 @@
1
+ import { cloudflareRequest } from "../client/errors.js";
2
+ import { CloudflareManager } from "../client/manager.js";
3
+ //#region src/media/imageManager.ts
4
+ /**
5
+ * Per-request CF SDK options for every Images call: a 10s timeout and up to 3 retries. Uploads and
6
+ * list calls can be slow, so the timeout is generous and retries cover transient API blips.
7
+ */
8
+ const requestOptions = {
9
+ timeout: 1e4,
10
+ maxRetries: 3
11
+ };
12
+ /**
13
+ * Encode a user-metadata bag to the string the Images API expects. CF stores image metadata as a
14
+ * JSON string; we serialize the generic `Record<string, string>` passthrough before sending, and
15
+ * leave an absent bag untouched.
16
+ */
17
+ function encodeMetadata(metadata) {
18
+ return metadata === void 0 || metadata === null ? metadata : JSON.stringify(metadata);
19
+ }
20
+ /**
21
+ * Out-of-Worker Cloudflare Images access over the REST API: upload, fetch, edit, delete, list, and
22
+ * direct-upload URLs from a CLI/CI/provisioning context. Image metadata is an arbitrary
23
+ * `Record<string, string>` passthrough at this layer — the manager imposes no shape.
24
+ *
25
+ * The ownership stamp is imposed one layer up, by `withAssetOwnership` in `ownership.ts`, which every
26
+ * Pithy path that *creates* an image goes through (the seeder here, and media's `imageMinter`). The
27
+ * split is deliberate: this class stays a faithful REST client usable for reading and sweeping
28
+ * anyone's images, while nothing Pithy writes can escape carrying `pithyProject`/`pithyEnv`.
29
+ */
30
+ var CloudflareImageManager = class extends CloudflareManager {
31
+ /** Upload an image via the V1 API. `account_id` is set from the manager's config. */
32
+ async uploadImage(params) {
33
+ return cloudflareRequest("Images upload", () => this.getClient().images.v1.create({
34
+ account_id: this.accountId,
35
+ ...params,
36
+ metadata: encodeMetadata(params.metadata)
37
+ }, requestOptions));
38
+ }
39
+ /** Fetch details for one image by id. */
40
+ async imageDetails(imageId) {
41
+ return cloudflareRequest(`Images get details for '${imageId}'`, () => this.getClient().images.v1.get(imageId, { account_id: this.accountId }, requestOptions));
42
+ }
43
+ /** Update an image's metadata and settings. */
44
+ async updateImage(imageId, params) {
45
+ return cloudflareRequest(`Images update '${imageId}'`, () => this.getClient().images.v1.edit(imageId, {
46
+ account_id: this.accountId,
47
+ ...params
48
+ }, requestOptions));
49
+ }
50
+ /** Delete an image by id. */
51
+ async deleteImage(imageId) {
52
+ await cloudflareRequest(`Images delete '${imageId}'`, () => this.getClient().images.v1.delete(imageId, { account_id: this.accountId }, requestOptions));
53
+ }
54
+ /** Create a direct-upload URL for a client-side upload (V2 API). */
55
+ async createDirectUploadUrl(params) {
56
+ return cloudflareRequest("Images create direct upload URL", () => this.getClient().images.v2.directUploads.create({
57
+ account_id: this.accountId,
58
+ ...params,
59
+ metadata: encodeMetadata(params.metadata)
60
+ }, requestOptions));
61
+ }
62
+ /** List images (V2 API), first page only. Returns the page's images, never null. */
63
+ async listImages(params) {
64
+ return cloudflareRequest("Images list", async () => {
65
+ return (await this.getClient().images.v2.list({
66
+ account_id: this.accountId,
67
+ ...params
68
+ }, requestOptions)).images ?? [];
69
+ });
70
+ }
71
+ /** List all images across every page, following the V2 continuation token. */
72
+ async listAllImages(params) {
73
+ return cloudflareRequest("Images list all", async () => {
74
+ const allImages = [];
75
+ const client = this.getClient();
76
+ const fetchPage = async (token) => {
77
+ const response = await client.images.v2.list({
78
+ account_id: this.accountId,
79
+ ...params,
80
+ continuation_token: token
81
+ }, requestOptions);
82
+ if (response.images) allImages.push(...response.images);
83
+ if (response.continuation_token) await fetchPage(response.continuation_token);
84
+ };
85
+ await fetchPage();
86
+ return allImages;
87
+ });
88
+ }
89
+ getServiceType() {
90
+ return "Cloudflare Images";
91
+ }
92
+ /** Prove access by listing images. Never throws. */
93
+ async validateServiceAccess() {
94
+ try {
95
+ await this.listImages();
96
+ return true;
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
101
+ };
102
+ //#endregion
103
+ export { CloudflareImageManager };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The ownership stamp on every Cloudflare Images and Stream asset Pithy creates.
3
+ *
4
+ * Every other resource this toolset provisions is isolated by its **name** —
5
+ * `<project>-<env>-<thing>`, because those namespaces are flat and account-wide (CLAUDE.md
6
+ * §Resource naming). Images and Stream cannot use that lever: their stores are just as flat and
7
+ * account-wide, but an asset is keyed by a **Cloudflare-minted id**, not by a name we choose. So
8
+ * metadata is the only place ownership can live, and without it two projects' assets in one account
9
+ * are indistinguishable — you cannot tell which app owns an asset, cannot sweep one app's assets at
10
+ * teardown, and `pithy doctor` is blind to them.
11
+ *
12
+ * The **same two keys** are used for both stores (Images `metadata`, Stream `meta`), so one query
13
+ * answers "what does this project own" across them rather than two dialects answering half each.
14
+ * Nothing else is imposed: a caller's own metadata rides along untouched — but it can never displace
15
+ * the reserved keys, which are merged last on purpose.
16
+ */
17
+ /** The metadata key naming the owning project — the root `pithy.config.ts` `name`. */
18
+ export declare const ASSET_PROJECT_KEY = "pithyProject";
19
+ /** The metadata key naming the owning environment (`dev` | `staging` | `production`). */
20
+ export declare const ASSET_ENV_KEY = "pithyEnv";
21
+ /** Who owns an asset: the project that created it, in the environment that created it. */
22
+ export interface AssetOwner {
23
+ /**
24
+ * The project name — the root `pithy.config.ts` `name`, resolved by `requireProjectName` and never
25
+ * guessed. It is the segment every sweep, listing, and teardown filters on.
26
+ */
27
+ project: string;
28
+ /** The environment the asset was created in (`dev` | `staging` | `production`). */
29
+ env: string;
30
+ }
31
+ /**
32
+ * Merge the ownership stamp over a caller's metadata bag.
33
+ *
34
+ * Ownership is applied **last**, so a caller-supplied `pithyProject`/`pithyEnv` cannot displace the
35
+ * real one — a fixture or a client-supplied bag claiming another project's assets would defeat the
36
+ * whole point of the stamp. A blank project or environment is refused rather than written: an asset
37
+ * stamped with an empty string reads as owned and still sweeps to nothing.
38
+ */
39
+ export declare function withAssetOwnership(owner: AssetOwner, metadata?: Record<string, string>): Record<string, string>;
40
+ //# sourceMappingURL=ownership.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ownership.d.ts","sourceRoot":"","sources":["../../src/media/ownership.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;GAeG;AAEH,sFAAsF;AACtF,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAEhD,yFAAyF;AACzF,eAAO,MAAM,aAAa,aAAa,CAAC;AAExC,0FAA0F;AAC1F,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAW/G"}