@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.
Files changed (139) hide show
  1. package/dist/ai/aiManager.d.ts +101 -0
  2. package/dist/ai/aiManager.d.ts.map +1 -0
  3. package/dist/ai/aiManager.js +150 -0
  4. package/dist/ai/vectorizeManager.d.ts +69 -0
  5. package/dist/ai/vectorizeManager.d.ts.map +1 -0
  6. package/dist/ai/vectorizeManager.js +103 -0
  7. package/dist/ai/vectorizeProvisioner.d.ts +125 -0
  8. package/dist/ai/vectorizeProvisioner.d.ts.map +1 -0
  9. package/dist/ai/vectorizeProvisioner.js +197 -0
  10. package/dist/client/accounts.d.ts +50 -0
  11. package/dist/client/accounts.d.ts.map +1 -0
  12. package/dist/client/accounts.js +57 -0
  13. package/dist/client/clients.d.ts +124 -0
  14. package/dist/client/clients.d.ts.map +1 -0
  15. package/dist/client/clients.js +218 -0
  16. package/dist/client/errors.d.ts +75 -0
  17. package/dist/client/errors.d.ts.map +1 -0
  18. package/dist/client/errors.js +100 -0
  19. package/dist/client/manager.d.ts +47 -0
  20. package/dist/client/manager.d.ts.map +1 -0
  21. package/dist/client/manager.js +53 -0
  22. package/dist/d1/d1Manager.d.ts +76 -0
  23. package/dist/d1/d1Manager.d.ts.map +1 -0
  24. package/dist/d1/d1Manager.js +130 -0
  25. package/dist/d1/d1PreparedStatement.d.ts +26 -0
  26. package/dist/d1/d1PreparedStatement.d.ts.map +1 -0
  27. package/dist/d1/d1PreparedStatement.js +93 -0
  28. package/dist/d1/d1Provisioner.d.ts +30 -0
  29. package/dist/d1/d1Provisioner.d.ts.map +1 -0
  30. package/dist/d1/d1Provisioner.js +69 -0
  31. package/dist/email/emailRoutingManager.d.ts +38 -0
  32. package/dist/email/emailRoutingManager.d.ts.map +1 -0
  33. package/dist/email/emailRoutingManager.js +126 -0
  34. package/dist/email/emailSendManager.d.ts +47 -0
  35. package/dist/email/emailSendManager.d.ts.map +1 -0
  36. package/dist/email/emailSendManager.js +54 -0
  37. package/dist/env/devVars.d.ts +67 -0
  38. package/dist/env/devVars.d.ts.map +1 -0
  39. package/dist/env/devVars.js +86 -0
  40. package/dist/hostnames/customHostnamesManager.d.ts +53 -0
  41. package/dist/hostnames/customHostnamesManager.d.ts.map +1 -0
  42. package/dist/hostnames/customHostnamesManager.js +86 -0
  43. package/dist/kv/kvManager.d.ts +78 -0
  44. package/dist/kv/kvManager.d.ts.map +1 -0
  45. package/dist/kv/kvManager.js +137 -0
  46. package/dist/kv/kvProvisioner.d.ts +30 -0
  47. package/dist/kv/kvProvisioner.d.ts.map +1 -0
  48. package/dist/kv/kvProvisioner.js +73 -0
  49. package/dist/media/assetSeeder.d.ts +33 -0
  50. package/dist/media/assetSeeder.d.ts.map +1 -0
  51. package/dist/media/assetSeeder.js +72 -0
  52. package/dist/media/imageManager.d.ts +36 -0
  53. package/dist/media/imageManager.d.ts.map +1 -0
  54. package/dist/media/imageManager.js +105 -0
  55. package/dist/media/ownership.d.ts +42 -0
  56. package/dist/media/ownership.d.ts.map +1 -0
  57. package/dist/media/ownership.js +48 -0
  58. package/dist/media/streamManager.d.ts +85 -0
  59. package/dist/media/streamManager.d.ts.map +1 -0
  60. package/dist/media/streamManager.js +147 -0
  61. package/dist/queue/queueManager.d.ts +86 -0
  62. package/dist/queue/queueManager.d.ts.map +1 -0
  63. package/dist/queue/queueManager.js +116 -0
  64. package/dist/r2/r2Credentials.d.ts +14 -0
  65. package/dist/r2/r2Credentials.d.ts.map +1 -0
  66. package/dist/r2/r2Credentials.js +15 -0
  67. package/dist/r2/r2Manager.d.ts +228 -0
  68. package/dist/r2/r2Manager.d.ts.map +1 -0
  69. package/dist/r2/r2Manager.js +407 -0
  70. package/dist/r2/r2Provisioner.d.ts +41 -0
  71. package/dist/r2/r2Provisioner.d.ts.map +1 -0
  72. package/dist/r2/r2Provisioner.js +88 -0
  73. package/dist/secrets/secretsStoreManager.d.ts +84 -0
  74. package/dist/secrets/secretsStoreManager.d.ts.map +1 -0
  75. package/dist/secrets/secretsStoreManager.js +149 -0
  76. package/dist/secrets/secretsStores.d.ts +49 -0
  77. package/dist/secrets/secretsStores.d.ts.map +1 -0
  78. package/dist/secrets/secretsStores.js +67 -0
  79. package/dist/test-utils/emailRoutingRules.d.ts +41 -0
  80. package/dist/test-utils/emailRoutingRules.d.ts.map +1 -0
  81. package/dist/test-utils/emailRoutingRules.js +91 -0
  82. package/dist/test-utils/fixtureReportSetup.d.ts +27 -0
  83. package/dist/test-utils/fixtureReportSetup.d.ts.map +1 -0
  84. package/dist/test-utils/fixtureReportSetup.js +32 -0
  85. package/dist/test-utils/fixtures.d.ts +226 -0
  86. package/dist/test-utils/fixtures.d.ts.map +1 -0
  87. package/dist/test-utils/fixtures.js +279 -0
  88. package/dist/test-utils/harness.d.ts +194 -0
  89. package/dist/test-utils/harness.d.ts.map +1 -0
  90. package/dist/test-utils/harness.js +353 -0
  91. package/dist/test-utils/inboundRecorder.d.ts +93 -0
  92. package/dist/test-utils/inboundRecorder.d.ts.map +1 -0
  93. package/dist/test-utils/inboundRecorder.js +143 -0
  94. package/dist/test-utils/integrationSetup.d.ts +24 -0
  95. package/dist/test-utils/integrationSetup.d.ts.map +1 -0
  96. package/dist/test-utils/integrationSetup.js +39 -0
  97. package/dist/test-utils/reap.d.ts +114 -0
  98. package/dist/test-utils/reap.d.ts.map +1 -0
  99. package/dist/test-utils/reap.js +187 -0
  100. package/dist/tokens/accountTokensManager.d.ts +159 -0
  101. package/dist/tokens/accountTokensManager.d.ts.map +1 -0
  102. package/dist/tokens/accountTokensManager.js +282 -0
  103. package/dist/tokens/permissions.d.ts +38 -0
  104. package/dist/tokens/permissions.d.ts.map +1 -0
  105. package/dist/tokens/permissions.js +52 -0
  106. package/dist/tokens/profiles.d.ts +104 -0
  107. package/dist/tokens/profiles.d.ts.map +1 -0
  108. package/dist/tokens/profiles.js +139 -0
  109. package/dist/turnstile/turnstileManager.d.ts +77 -0
  110. package/dist/turnstile/turnstileManager.d.ts.map +1 -0
  111. package/dist/turnstile/turnstileManager.js +140 -0
  112. package/dist/user/userManager.d.ts +38 -0
  113. package/dist/user/userManager.d.ts.map +1 -0
  114. package/dist/user/userManager.js +60 -0
  115. package/dist/workers/buildsManager.d.ts +118 -0
  116. package/dist/workers/buildsManager.d.ts.map +1 -0
  117. package/dist/workers/buildsManager.js +239 -0
  118. package/dist/workers/buildsTypes.d.ts +104 -0
  119. package/dist/workers/buildsTypes.d.ts.map +1 -0
  120. package/dist/workers/buildsTypes.js +89 -0
  121. package/dist/workers/workersBuildEvent.d.ts +66 -0
  122. package/dist/workers/workersBuildEvent.d.ts.map +1 -0
  123. package/dist/workers/workersBuildEvent.js +49 -0
  124. package/dist/workers/workersManager.d.ts +138 -0
  125. package/dist/workers/workersManager.d.ts.map +1 -0
  126. package/dist/workers/workersManager.js +314 -0
  127. package/dist/workers/workersProvisioner.d.ts +82 -0
  128. package/dist/workers/workersProvisioner.d.ts.map +1 -0
  129. package/dist/workers/workersProvisioner.js +108 -0
  130. package/dist/workflows/stepFailure.d.ts +182 -0
  131. package/dist/workflows/stepFailure.d.ts.map +1 -0
  132. package/dist/workflows/stepFailure.js +229 -0
  133. package/dist/workflows/workflowsClient.d.ts +89 -0
  134. package/dist/workflows/workflowsClient.d.ts.map +1 -0
  135. package/dist/workflows/workflowsClient.js +158 -0
  136. package/dist/zones/zonesManager.d.ts +52 -0
  137. package/dist/zones/zonesManager.d.ts.map +1 -0
  138. package/dist/zones/zonesManager.js +76 -0
  139. package/package.json +18 -9
@@ -0,0 +1,75 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { PithyError } from "@pithy-sh/core/src/error/pithyError";
4
+ import type { MessageParams } from "@pithy-sh/core/src/i18n/catalog";
5
+ import type { z } from "zod";
6
+ /**
7
+ * Cloudflare REST client throw sugar. The `cloudflare/*` codes live in core's closed
8
+ * `KitErrorPayload` union (CLAUDE.md §Errors: capabilities add their codes to the one union); these
9
+ * subclasses are the package-local vehicles that set one of those members — the same pattern as
10
+ * core's `NotFoundError`/`InternalError`, just owned here. Runtime code in this package throws
11
+ * one of these, never a plain `new Error`.
12
+ */
13
+ /** Variable parts each subclass accepts; `code`/`status` are fixed by the subclass. */
14
+ interface CloudflareErrorArgs {
15
+ /** Override the public, safe-to-expose message. */
16
+ message?: string;
17
+ /** A remediation hint (CLI action line). */
18
+ action?: string;
19
+ /** Internal context for logs + audit. Never serialized to clients. */
20
+ detail?: string;
21
+ /**
22
+ * Values a translating client interpolates into its own wording for this code. Client-facing, so —
23
+ * unlike `action` and `detail` — these cross the boundary with `message`.
24
+ */
25
+ params?: MessageParams;
26
+ }
27
+ /** A required piece of configuration (token, account id, resource id) is missing. */
28
+ export declare class CloudflareNotConfiguredError extends PithyError {
29
+ constructor(args?: CloudflareErrorArgs, options?: {
30
+ cause?: unknown;
31
+ });
32
+ }
33
+ /** A call to the Cloudflare REST API failed (network error, 4xx/5xx from the API). */
34
+ export declare class CloudflareRequestError extends PithyError {
35
+ constructor(args?: CloudflareErrorArgs, options?: {
36
+ cause?: unknown;
37
+ });
38
+ }
39
+ /** A Cloudflare REST API response did not match its expected shape (failed Zod validation). */
40
+ export declare class CloudflareInvalidResponseError extends PithyError {
41
+ constructor(args?: CloudflareErrorArgs, options?: {
42
+ cause?: unknown;
43
+ });
44
+ }
45
+ /**
46
+ * Run a Cloudflare SDK call and turn any failure into a `CloudflareRequestError`, preserving the
47
+ * original error as `cause` and its message as internal `detail`. `operation` names the call for
48
+ * the audit trail (`"KV get for key 'x'"`). A `PithyError` already in flight (e.g. a
49
+ * not-configured guard) passes through untouched — only foreign throws get wrapped.
50
+ */
51
+ export declare function cloudflareRequest<T>(operation: string, fn: () => Promise<T>): Promise<T>;
52
+ /** The raw message of an unknown throw, for use as a PithyError `detail`. One source of truth. */
53
+ export declare function messageOf(error: unknown): string;
54
+ /**
55
+ * The most specific failure reason available, for a partial-failure report's `error` field: a
56
+ * `PithyError`'s internal `detail` (the real cause `cloudflareRequest` captured), else its public
57
+ * message, else the raw throw. Centralized so the detail-vs-message precedence lives in one place.
58
+ */
59
+ export declare function reasonOf(error: unknown): string;
60
+ /** Whether a thrown SDK error is an HTTP 404 — the SDK throws on a missing resource, not null. */
61
+ export declare function isNotFoundError(error: unknown): boolean;
62
+ /**
63
+ * Whether a thrown SDK error is an HTTP 403 — the token reached the API but lacks the permission for
64
+ * the operation. The signal a caller uses to turn a raw "Unauthorized" into an actionable
65
+ * "grant this permission group" message instead of a generic request failure.
66
+ */
67
+ export declare function isAuthorizationError(error: unknown): boolean;
68
+ /**
69
+ * Decode a Cloudflare response against `schema`, throwing `cloudflare/invalid_response` on a shape
70
+ * mismatch. The decode counterpart to `cloudflareRequest`'s error wrapping — one seam so every
71
+ * manager validates the wire the same way instead of hand-rolling `safeParse` + throw.
72
+ */
73
+ export declare function decodeResponse<T extends z.ZodType>(schema: T, raw: unknown, context: string): z.output<T>;
74
+ export {};
75
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/client/errors.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,qCAAqC,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B;;;;;;GAMG;AAEH,uFAAuF;AACvF,UAAU,mBAAmB;IAC3B,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,qFAAqF;AACrF,qBAAa,4BAA6B,SAAQ,UAAU;IAC1D,YAAY,IAAI,GAAE,mBAAwB,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAYxE;CACF;AAED,sFAAsF;AACtF,qBAAa,sBAAuB,SAAQ,UAAU;IACpD,YAAY,IAAI,GAAE,mBAAwB,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAYxE;CACF;AAED,+FAA+F;AAC/F,qBAAa,8BAA+B,SAAQ,UAAU;IAC5D,YAAY,IAAI,GAAE,mBAAwB,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAYxE;CACF;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAU9F;AAED,kGAAkG;AAClG,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAG/C;AAED,kGAAkG;AAClG,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEvD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CASzG"}
@@ -0,0 +1,100 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { PithyError } from "@pithy-sh/core/src/error/pithyError";
4
+ //#region src/client/errors.ts
5
+ /** A required piece of configuration (token, account id, resource id) is missing. */
6
+ var CloudflareNotConfiguredError = class extends PithyError {
7
+ constructor(args = {}, options) {
8
+ super({
9
+ code: "cloudflare/not_configured",
10
+ status: 500,
11
+ message: args.message ?? "The Cloudflare REST client is not fully configured.",
12
+ action: args.action ?? "Provide apiToken, accountId, and any required resource id.",
13
+ detail: args.detail,
14
+ params: args.params
15
+ }, options);
16
+ }
17
+ };
18
+ /** A call to the Cloudflare REST API failed (network error, 4xx/5xx from the API). */
19
+ var CloudflareRequestError = class extends PithyError {
20
+ constructor(args = {}, options) {
21
+ super({
22
+ code: "cloudflare/request_failed",
23
+ status: 502,
24
+ message: args.message ?? "A Cloudflare REST API call failed.",
25
+ action: args.action,
26
+ detail: args.detail,
27
+ params: args.params
28
+ }, options);
29
+ }
30
+ };
31
+ /** A Cloudflare REST API response did not match its expected shape (failed Zod validation). */
32
+ var CloudflareInvalidResponseError = class extends PithyError {
33
+ constructor(args = {}, options) {
34
+ super({
35
+ code: "cloudflare/invalid_response",
36
+ status: 502,
37
+ message: args.message ?? "A Cloudflare REST API response had an unexpected shape.",
38
+ action: args.action,
39
+ detail: args.detail,
40
+ params: args.params
41
+ }, options);
42
+ }
43
+ };
44
+ /**
45
+ * Run a Cloudflare SDK call and turn any failure into a `CloudflareRequestError`, preserving the
46
+ * original error as `cause` and its message as internal `detail`. `operation` names the call for
47
+ * the audit trail (`"KV get for key 'x'"`). A `PithyError` already in flight (e.g. a
48
+ * not-configured guard) passes through untouched — only foreign throws get wrapped.
49
+ */
50
+ async function cloudflareRequest(operation, fn) {
51
+ try {
52
+ return await fn();
53
+ } catch (error) {
54
+ if (error instanceof PithyError) throw error;
55
+ throw new CloudflareRequestError({
56
+ message: `Cloudflare request failed: ${operation}.`,
57
+ detail: messageOf(error)
58
+ }, { cause: error });
59
+ }
60
+ }
61
+ /** The raw message of an unknown throw, for use as a PithyError `detail`. One source of truth. */
62
+ function messageOf(error) {
63
+ return error instanceof Error ? error.message : String(error);
64
+ }
65
+ /**
66
+ * The most specific failure reason available, for a partial-failure report's `error` field: a
67
+ * `PithyError`'s internal `detail` (the real cause `cloudflareRequest` captured), else its public
68
+ * message, else the raw throw. Centralized so the detail-vs-message precedence lives in one place.
69
+ */
70
+ function reasonOf(error) {
71
+ if (error instanceof PithyError) return error.payload.detail ?? error.payload.message;
72
+ return messageOf(error);
73
+ }
74
+ /** Whether a thrown SDK error is an HTTP 404 — the SDK throws on a missing resource, not null. */
75
+ function isNotFoundError(error) {
76
+ return typeof error === "object" && error !== null && error.status === 404;
77
+ }
78
+ /**
79
+ * Whether a thrown SDK error is an HTTP 403 — the token reached the API but lacks the permission for
80
+ * the operation. The signal a caller uses to turn a raw "Unauthorized" into an actionable
81
+ * "grant this permission group" message instead of a generic request failure.
82
+ */
83
+ function isAuthorizationError(error) {
84
+ return typeof error === "object" && error !== null && error.status === 403;
85
+ }
86
+ /**
87
+ * Decode a Cloudflare response against `schema`, throwing `cloudflare/invalid_response` on a shape
88
+ * mismatch. The decode counterpart to `cloudflareRequest`'s error wrapping — one seam so every
89
+ * manager validates the wire the same way instead of hand-rolling `safeParse` + throw.
90
+ */
91
+ function decodeResponse(schema, raw, context) {
92
+ const parsed = schema.safeParse(raw);
93
+ if (!parsed.success) throw new CloudflareInvalidResponseError({
94
+ message: `A Cloudflare response had an unexpected shape: ${context}.`,
95
+ detail: parsed.error.message
96
+ });
97
+ return parsed.data;
98
+ }
99
+ //#endregion
100
+ export { CloudflareInvalidResponseError, CloudflareNotConfiguredError, CloudflareRequestError, cloudflareRequest, decodeResponse, isAuthorizationError, isNotFoundError, messageOf, reasonOf };
@@ -0,0 +1,47 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { Cloudflare } from "cloudflare";
4
+ import type { Account } from "cloudflare/resources/accounts/accounts";
5
+ /**
6
+ * Configuration every manager shares. This is the whole surface: a scoped CF API token and the
7
+ * account it targets. The CMS managers also accepted Worker bindings (dual REST/binding mode);
8
+ * `@pithy-sh/cloudflare` is the *out-of-Worker* client (CLI, CI, provisioning), so it is REST-only
9
+ * — inside a Worker you use the binding directly (CLAUDE.md §Cloudflare access). One token, one
10
+ * account, no env coupling.
11
+ */
12
+ export interface CloudflareManagerConfig {
13
+ /** A scoped, least-privilege CF API token (from `CLOUDFLARE_API_TOKEN`, minted per environment). */
14
+ apiToken: string;
15
+ /** The Cloudflare account id all operations target (`CLOUDFLARE_ACCOUNT_ID`). */
16
+ accountId: string;
17
+ }
18
+ /**
19
+ * Base for every Cloudflare resource manager. Holds the configured SDK client and account id, and
20
+ * carries the account-level operations shared across resources. Subclasses add one resource's REST
21
+ * operations and declare what they are (`getServiceType`) and how to prove access
22
+ * (`validateServiceAccess`).
23
+ */
24
+ export declare abstract class CloudflareManager {
25
+ private readonly client;
26
+ private readonly apiToken;
27
+ protected readonly accountId: string;
28
+ constructor(config: CloudflareManagerConfig);
29
+ /** The configured SDK client. Subclasses route typed calls through this. */
30
+ protected getClient(): Cloudflare;
31
+ /**
32
+ * The raw API token, for subclasses that fall back to direct `fetch()` for endpoints the typed
33
+ * SDK does not cover (e.g. Workers Builds, event subscriptions). The documented escape hatch.
34
+ */
35
+ protected getApiToken(): string;
36
+ /** The account id all operations target. */
37
+ protected getAccountId(): string;
38
+ /** Fetch the full account record. Wraps SDK failures as `cloudflare/request_failed`. */
39
+ getAccountInfo(): Promise<Account>;
40
+ /** Whether the credentials work, proved by a lightweight account read. Never throws. */
41
+ validateCredentials(): Promise<boolean>;
42
+ /** A human label for the resource this manager owns (e.g. "KV Storage"). */
43
+ abstract getServiceType(): string;
44
+ /** Prove this manager can reach its specific resource. Never throws; returns false on failure. */
45
+ abstract validateServiceAccess(): Promise<boolean>;
46
+ }
47
+ //# sourceMappingURL=manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/client/manager.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wCAAwC,CAAC;AAGtE;;;;;;GAMG;AACH,MAAM,WAAW,uBAAuB;IACtC,oGAAoG;IACpG,QAAQ,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,8BAAsB,iBAAiB;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;IAEpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAErC,YAAY,MAAM,EAAE,uBAAuB,EAU1C;IAED,4EAA4E;IAC5E,SAAS,CAAC,SAAS,IAAI,UAAU,CAEhC;IAED;;;OAGG;IACH,SAAS,CAAC,WAAW,IAAI,MAAM,CAE9B;IAED,4CAA4C;IAC5C,SAAS,CAAC,YAAY,IAAI,MAAM,CAE/B;IAED,wFAAwF;IAClF,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAEvC;IAED,wFAAwF;IAClF,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO5C;IAED,4EAA4E;IAC5E,QAAQ,CAAC,cAAc,IAAI,MAAM,CAAC;IAElC,kGAAkG;IAClG,QAAQ,CAAC,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACpD"}
@@ -0,0 +1,53 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { CloudflareNotConfiguredError, cloudflareRequest } from "./errors.js";
4
+ import { Cloudflare } from "cloudflare";
5
+ //#region src/client/manager.ts
6
+ /**
7
+ * Base for every Cloudflare resource manager. Holds the configured SDK client and account id, and
8
+ * carries the account-level operations shared across resources. Subclasses add one resource's REST
9
+ * operations and declare what they are (`getServiceType`) and how to prove access
10
+ * (`validateServiceAccess`).
11
+ */
12
+ var CloudflareManager = class {
13
+ client;
14
+ apiToken;
15
+ accountId;
16
+ constructor(config) {
17
+ if (!config.apiToken) throw new CloudflareNotConfiguredError({ detail: "Missing apiToken in CloudflareManagerConfig." });
18
+ if (!config.accountId) throw new CloudflareNotConfiguredError({ detail: "Missing accountId in CloudflareManagerConfig." });
19
+ this.client = new Cloudflare({ apiToken: config.apiToken });
20
+ this.apiToken = config.apiToken;
21
+ this.accountId = config.accountId;
22
+ }
23
+ /** The configured SDK client. Subclasses route typed calls through this. */
24
+ getClient() {
25
+ return this.client;
26
+ }
27
+ /**
28
+ * The raw API token, for subclasses that fall back to direct `fetch()` for endpoints the typed
29
+ * SDK does not cover (e.g. Workers Builds, event subscriptions). The documented escape hatch.
30
+ */
31
+ getApiToken() {
32
+ return this.apiToken;
33
+ }
34
+ /** The account id all operations target. */
35
+ getAccountId() {
36
+ return this.accountId;
37
+ }
38
+ /** Fetch the full account record. Wraps SDK failures as `cloudflare/request_failed`. */
39
+ async getAccountInfo() {
40
+ return cloudflareRequest("get account info", () => this.getClient().accounts.get({ account_id: this.accountId }));
41
+ }
42
+ /** Whether the credentials work, proved by a lightweight account read. Never throws. */
43
+ async validateCredentials() {
44
+ try {
45
+ await this.getAccountInfo();
46
+ return true;
47
+ } catch {
48
+ return false;
49
+ }
50
+ }
51
+ };
52
+ //#endregion
53
+ export { CloudflareManager };
@@ -0,0 +1,76 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import type { D1Database, D1PreparedStatement, D1Result } from "@cloudflare/workers-types";
4
+ import type { D1 } from "cloudflare/resources/d1/d1";
5
+ import type { QueryResult } from "cloudflare/resources/d1/database";
6
+ import { CloudflareManager, type CloudflareManagerConfig } from "../client/manager";
7
+ /** Config for the D1 manager: the shared client config plus the database it targets. */
8
+ export interface D1ManagerConfig extends CloudflareManagerConfig {
9
+ /** The D1 database id (the REST API addresses databases by id). */
10
+ databaseId: string;
11
+ }
12
+ /** The per-query outcome of `batchQueries`: a partial-failure report, never a thrown batch. */
13
+ export interface D1BatchResult {
14
+ /** The SQL this result is for. */
15
+ query: string;
16
+ /** Whether the query returned at least one result set. */
17
+ success: boolean;
18
+ /** The result sets, present only on success. */
19
+ result?: QueryResult[];
20
+ /** The failure reason, present only when `success` is false. */
21
+ error?: string;
22
+ }
23
+ /**
24
+ * Out-of-Worker D1 access over the REST API. It `implements D1Database`, so the same query builder
25
+ * a Worker runs against a binding runs from a CLI/CI context against D1 over REST — most
26
+ * importantly as the database behind a Kysely `D1Dialect` (kysely-d1), which is how `pithy migrate`
27
+ * promotes/rolls back schema remotely (issue #31). Inside a Worker you use the `D1Database` binding
28
+ * directly; this is the REST counterpart, addressed by database id.
29
+ */
30
+ export declare class CloudflareD1Manager extends CloudflareManager implements D1Database {
31
+ private readonly databaseId;
32
+ constructor(config: D1ManagerConfig);
33
+ prepare(query: string): D1PreparedStatement;
34
+ /**
35
+ * Run each statement and collect its result. **Not atomic:** unlike the Worker binding's `batch()`
36
+ * (which runs the statements in one transaction), the D1 REST API exposes no batch/transaction
37
+ * endpoint, so these execute as independent sequential queries — a mid-batch failure leaves earlier
38
+ * statements committed. D1 has no interactive transactions, and Kysely drives migrations through
39
+ * `prepare().all()` per statement rather than this method, so this affects only direct callers.
40
+ */
41
+ batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;
42
+ exec(query: string): Promise<D1ExecResult>;
43
+ /** `dump()` is deprecated and has no REST equivalent — calling it is a programming error. */
44
+ dump(): Promise<ArrayBuffer>;
45
+ /** Sessions are a binding-only feature; the REST client has no equivalent. */
46
+ withSession(): never;
47
+ /** Run one SQL statement and return its raw REST result sets. */
48
+ executeQuery(sql: string, params?: string[]): Promise<QueryResult[]>;
49
+ /**
50
+ * Run many independent queries, collecting a per-query result instead of failing the whole batch
51
+ * on the first error — the caller decides what to do with partial failure.
52
+ */
53
+ batchQueries(queries: Array<{
54
+ sql: string;
55
+ params?: string[];
56
+ }>): Promise<D1BatchResult[]>;
57
+ /** Fetch the D1 database record. */
58
+ getDatabaseInfo(): Promise<D1>;
59
+ /** List the user tables in the database. */
60
+ listTables(): Promise<string[]>;
61
+ /**
62
+ * Read a table's column schema via `PRAGMA table_info`. SQLite pragmas take no bind parameters, so
63
+ * the table name is interpolated — guard it against a strict identifier allowlist first, so the one
64
+ * un-parameterized SQL path in this client can never become an injection sink.
65
+ */
66
+ getTableSchema(tableName: string): Promise<unknown[]>;
67
+ /** The database this manager targets and the account it lives in. */
68
+ getD1Info(): {
69
+ databaseId: string;
70
+ accountId: string;
71
+ };
72
+ getServiceType(): string;
73
+ /** Prove access by reading the database record. Never throws. */
74
+ validateServiceAccess(): Promise<boolean>;
75
+ }
76
+ //# sourceMappingURL=d1Manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1Manager.d.ts","sourceRoot":"","sources":["../../src/d1/d1Manager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAE3F,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAEpE,OAAO,EAAE,iBAAiB,EAAE,KAAK,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAGpF,wFAAwF;AACxF,MAAM,WAAW,eAAgB,SAAQ,uBAAuB;IAC9D,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,+FAA+F;AAC/F,MAAM,WAAW,aAAa;IAC5B,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,OAAO,EAAE,OAAO,CAAC;IACjB,gDAAgD;IAChD,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,qBAAa,mBAAoB,SAAQ,iBAAkB,YAAW,UAAU;IAC9E,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAEpC,YAAY,MAAM,EAAE,eAAe,EAMlC;IAID,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,mBAAmB,CAE1C;IAED;;;;;;OAMG;IACG,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAMlF;IAEK,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAM/C;IAED,6FAA6F;IACvF,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC,CAEjC;IAED,8EAA8E;IAC9E,WAAW,IAAI,KAAK,CAEnB;IAID,iEAAiE;IAC3D,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAKzE;IAED;;;OAGG;IACG,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAW/F;IAED,oCAAoC;IAC9B,eAAe,IAAI,OAAO,CAAC,EAAE,CAAC,CAInC;IAED,4CAA4C;IACtC,UAAU,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAMpC;IAED;;;;OAIG;IACG,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAS1D;IAED,qEAAqE;IACrE,SAAS,IAAI;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAErD;IAED,cAAc,IAAI,MAAM,CAEvB;IAED,iEAAiE;IAC3D,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO9C;CACF"}
@@ -0,0 +1,130 @@
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 { D1PreparedStatementREST } from "./d1PreparedStatement.js";
6
+ import { InternalError, ValidationError } from "@pithy-sh/core/src/error/pithyError";
7
+ //#region src/d1/d1Manager.ts
8
+ /**
9
+ * Out-of-Worker D1 access over the REST API. It `implements D1Database`, so the same query builder
10
+ * a Worker runs against a binding runs from a CLI/CI context against D1 over REST — most
11
+ * importantly as the database behind a Kysely `D1Dialect` (kysely-d1), which is how `pithy migrate`
12
+ * promotes/rolls back schema remotely (issue #31). Inside a Worker you use the `D1Database` binding
13
+ * directly; this is the REST counterpart, addressed by database id.
14
+ */
15
+ var CloudflareD1Manager = class extends CloudflareManager {
16
+ databaseId;
17
+ constructor(config) {
18
+ super(config);
19
+ if (!config.databaseId) throw new CloudflareNotConfiguredError({ detail: "Missing databaseId for D1 REST access." });
20
+ this.databaseId = config.databaseId;
21
+ }
22
+ prepare(query) {
23
+ return new D1PreparedStatementREST(query, this.getClient(), this.accountId, this.databaseId);
24
+ }
25
+ /**
26
+ * Run each statement and collect its result. **Not atomic:** unlike the Worker binding's `batch()`
27
+ * (which runs the statements in one transaction), the D1 REST API exposes no batch/transaction
28
+ * endpoint, so these execute as independent sequential queries — a mid-batch failure leaves earlier
29
+ * statements committed. D1 has no interactive transactions, and Kysely drives migrations through
30
+ * `prepare().all()` per statement rather than this method, so this affects only direct callers.
31
+ */
32
+ async batch(statements) {
33
+ const results = [];
34
+ for (const statement of statements) results.push(await statement.run());
35
+ return results;
36
+ }
37
+ async exec(query) {
38
+ const result = (await cloudflareRequest("D1 exec", () => this.getClient().d1.database.query(this.databaseId, {
39
+ account_id: this.accountId,
40
+ sql: query
41
+ }))).result[0];
42
+ return {
43
+ count: result?.results?.length ?? 0,
44
+ duration: result?.meta?.duration ?? 0
45
+ };
46
+ }
47
+ /** `dump()` is deprecated and has no REST equivalent — calling it is a programming error. */
48
+ async dump() {
49
+ throw new InternalError({ detail: "D1 dump() is not supported via the REST client." });
50
+ }
51
+ /** Sessions are a binding-only feature; the REST client has no equivalent. */
52
+ withSession() {
53
+ throw new InternalError({ detail: "D1 withSession() is not supported via the REST client." });
54
+ }
55
+ /** Run one SQL statement and return its raw REST result sets. */
56
+ async executeQuery(sql, params) {
57
+ return (await cloudflareRequest("D1 query", () => this.getClient().d1.database.query(this.databaseId, {
58
+ account_id: this.accountId,
59
+ sql,
60
+ params
61
+ }))).result;
62
+ }
63
+ /**
64
+ * Run many independent queries, collecting a per-query result instead of failing the whole batch
65
+ * on the first error — the caller decides what to do with partial failure.
66
+ */
67
+ async batchQueries(queries) {
68
+ return Promise.all(queries.map(async (query) => {
69
+ try {
70
+ const result = await this.executeQuery(query.sql, query.params);
71
+ return {
72
+ query: query.sql,
73
+ success: result.length > 0,
74
+ result
75
+ };
76
+ } catch (error) {
77
+ return {
78
+ query: query.sql,
79
+ success: false,
80
+ error: reasonOf(error)
81
+ };
82
+ }
83
+ }));
84
+ }
85
+ /** Fetch the D1 database record. */
86
+ async getDatabaseInfo() {
87
+ return cloudflareRequest("get D1 database info", () => this.getClient().d1.database.get(this.databaseId, { account_id: this.accountId }));
88
+ }
89
+ /** List the user tables in the database. */
90
+ async listTables() {
91
+ return (await this.executeQuery("SELECT name FROM sqlite_master WHERE type='table'")).flatMap((queryResult) => queryResult.results ?? []).map((row) => isNamedRow(row) ? row.name : void 0).filter((name) => typeof name === "string");
92
+ }
93
+ /**
94
+ * Read a table's column schema via `PRAGMA table_info`. SQLite pragmas take no bind parameters, so
95
+ * the table name is interpolated — guard it against a strict identifier allowlist first, so the one
96
+ * un-parameterized SQL path in this client can never become an injection sink.
97
+ */
98
+ async getTableSchema(tableName) {
99
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(tableName)) throw new ValidationError({
100
+ message: "Invalid table name.",
101
+ detail: `Table name must be a plain SQL identifier, got: ${tableName}`
102
+ });
103
+ return (await this.executeQuery(`PRAGMA table_info(${tableName})`)).flatMap((queryResult) => queryResult.results ?? []);
104
+ }
105
+ /** The database this manager targets and the account it lives in. */
106
+ getD1Info() {
107
+ return {
108
+ databaseId: this.databaseId,
109
+ accountId: this.accountId
110
+ };
111
+ }
112
+ getServiceType() {
113
+ return "D1 Database";
114
+ }
115
+ /** Prove access by reading the database record. Never throws. */
116
+ async validateServiceAccess() {
117
+ try {
118
+ await this.getDatabaseInfo();
119
+ return true;
120
+ } catch {
121
+ return false;
122
+ }
123
+ }
124
+ };
125
+ /** Narrow a result row to one carrying a string `name` column (the `sqlite_master` shape). */
126
+ function isNamedRow(row) {
127
+ return typeof row === "object" && row !== null && "name" in row && typeof row.name === "string";
128
+ }
129
+ //#endregion
130
+ export { CloudflareD1Manager };
@@ -0,0 +1,26 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import type { D1PreparedStatement, D1Result } from "@cloudflare/workers-types";
4
+ import type { Cloudflare } from "cloudflare";
5
+ export declare class D1PreparedStatementREST<T = Record<string, unknown>> implements D1PreparedStatement {
6
+ private readonly sql;
7
+ private readonly boundParams;
8
+ private readonly client;
9
+ private readonly accountId;
10
+ private readonly databaseId;
11
+ constructor(sql: string, client: Cloudflare, accountId: string, databaseId: string, boundParams?: unknown[]);
12
+ /** Bind positional parameters, returning a new statement (the binding's immutable contract). */
13
+ bind(...values: unknown[]): D1PreparedStatementREST<T>;
14
+ run<U = T>(): Promise<D1Result<U>>;
15
+ all<U = T>(): Promise<D1Result<U>>;
16
+ first<U = unknown>(colName: string): Promise<U | null>;
17
+ first<U = T>(): Promise<U | null>;
18
+ raw<U = unknown[]>(options: {
19
+ columnNames: true;
20
+ }): Promise<[string[], ...U[]]>;
21
+ raw<U = unknown[]>(options?: {
22
+ columnNames?: false;
23
+ }): Promise<U[]>;
24
+ private executeQuery;
25
+ }
26
+ //# sourceMappingURL=d1PreparedStatement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1PreparedStatement.d.ts","sourceRoot":"","sources":["../../src/d1/d1PreparedStatement.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAU,mBAAmB,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACvF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAuC7C,qBAAa,uBAAuB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,YAAW,mBAAmB;IAC9F,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAE7B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAY;IAExC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;IAEpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IAEnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAEpC,YAAY,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,GAAE,OAAO,EAAO,EAM9G;IAED,gGAAgG;IAChG,IAAI,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,uBAAuB,CAAC,CAAC,CAAC,CAErD;IAED,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAEjC;IAED,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAEjC;IAEK,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACvD,KAAK,CAAC,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IASlC,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,EAAE,OAAO,EAAE;QAAE,WAAW,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAChF,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,EAAE,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,KAAK,CAAA;KAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;YAe5D,YAAY;CAY3B"}
@@ -0,0 +1,93 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { cloudflareRequest } from "../client/errors.js";
4
+ //#region src/d1/d1PreparedStatement.ts
5
+ /**
6
+ * A `D1PreparedStatement` backed by the D1 REST API instead of a Worker binding. It mirrors the
7
+ * binding's prepared-statement surface (`bind`/`run`/`all`/`first`/`raw`) closely enough that a
8
+ * Kysely `D1Dialect` (kysely-d1) can drive it unchanged — which is how `pithy migrate` runs the
9
+ * same query builder from a CLI/CI context against D1 over REST (issue #31).
10
+ *
11
+ * `D1PreparedStatement` and `D1Meta` are imported by name from `@cloudflare/workers-types`, so this
12
+ * class is structurally a drop-in for the binding's statement. They used to be named off the global
13
+ * scope, which resolved only because this package's own `tsconfig.json` loads the types — and every
14
+ * package here ships raw TypeScript, so an adopter compiling this file got `Cannot find name` unless
15
+ * their program happened to pull the types in too. Importing them is what makes the dependency real
16
+ * (#431).
17
+ */
18
+ function mapMeta(restMeta) {
19
+ return {
20
+ duration: restMeta?.duration ?? 0,
21
+ size_after: restMeta?.size_after ?? 0,
22
+ rows_read: restMeta?.rows_read ?? 0,
23
+ rows_written: restMeta?.rows_written ?? 0,
24
+ last_row_id: restMeta?.last_row_id ?? 0,
25
+ changed_db: restMeta?.changed_db ?? false,
26
+ changes: restMeta?.changes ?? 0
27
+ };
28
+ }
29
+ /** The REST `params` array is strings; coerce each bound value the way the D1 REST API expects. */
30
+ function toStringParams(params) {
31
+ return params.map((value) => {
32
+ if (value === null || value === void 0) return "";
33
+ if (typeof value === "string") return value;
34
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
35
+ return JSON.stringify(value);
36
+ });
37
+ }
38
+ var D1PreparedStatementREST = class D1PreparedStatementREST {
39
+ sql;
40
+ boundParams;
41
+ client;
42
+ accountId;
43
+ databaseId;
44
+ constructor(sql, client, accountId, databaseId, boundParams = []) {
45
+ this.sql = sql;
46
+ this.boundParams = boundParams;
47
+ this.client = client;
48
+ this.accountId = accountId;
49
+ this.databaseId = databaseId;
50
+ }
51
+ /** Bind positional parameters, returning a new statement (the binding's immutable contract). */
52
+ bind(...values) {
53
+ return new D1PreparedStatementREST(this.sql, this.client, this.accountId, this.databaseId, values);
54
+ }
55
+ run() {
56
+ return this.executeQuery();
57
+ }
58
+ all() {
59
+ return this.executeQuery();
60
+ }
61
+ async first(colName) {
62
+ const row = (await this.executeQuery()).results[0] ?? null;
63
+ if (row === null) return null;
64
+ if (colName) return row[colName] ?? null;
65
+ return row;
66
+ }
67
+ async raw(options) {
68
+ const params = this.boundParams.length > 0 ? toStringParams(this.boundParams) : void 0;
69
+ const rawResult = (await cloudflareRequest("D1 raw query", () => this.client.d1.database.raw(this.databaseId, {
70
+ account_id: this.accountId,
71
+ sql: this.sql,
72
+ params
73
+ }))).result[0];
74
+ const rows = rawResult?.results?.rows ?? [];
75
+ if (options?.columnNames) return [rawResult?.results?.columns ?? [], ...rows];
76
+ return rows;
77
+ }
78
+ async executeQuery() {
79
+ const params = this.boundParams.length > 0 ? toStringParams(this.boundParams) : void 0;
80
+ const queryResult = (await cloudflareRequest("D1 query", () => this.client.d1.database.query(this.databaseId, {
81
+ account_id: this.accountId,
82
+ sql: this.sql,
83
+ params
84
+ }))).result[0];
85
+ return {
86
+ success: true,
87
+ meta: mapMeta(queryResult?.meta),
88
+ results: queryResult?.results ?? []
89
+ };
90
+ }
91
+ };
92
+ //#endregion
93
+ export { D1PreparedStatementREST };
@@ -0,0 +1,30 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { z } from "zod";
4
+ import { CloudflareManager } from "../client/manager";
5
+ /** A D1 database's identity, decoded from the create response. */
6
+ export declare const D1DatabaseInfo: z.ZodObject<{
7
+ uuid: z.ZodString;
8
+ name: z.ZodString;
9
+ }, z.core.$strip>;
10
+ export type D1DatabaseInfo = z.output<typeof D1DatabaseInfo>;
11
+ /**
12
+ * Account-level D1 control plane: **create and delete databases**. Customers use this to stand up
13
+ * and tear down per-environment databases (e.g. ephemeral staging), and `pithy add secrets` uses it
14
+ * to provision each environment's secrets D1. Addressed by account — unlike {@link CloudflareD1Manager},
15
+ * which targets one database id for queries.
16
+ */
17
+ export declare class CloudflareD1Provisioner extends CloudflareManager {
18
+ getServiceType(): string;
19
+ /** Prove access by listing databases — a read, never a destructive create/delete. Never throws. */
20
+ validateServiceAccess(): Promise<boolean>;
21
+ /** Every D1 database in the account — for prefix-scan reconcile teardown (`pithy feature destroy`). */
22
+ listDatabases(): Promise<D1DatabaseInfo[]>;
23
+ /** Find a database by exact name in the account, or `null` — for idempotent provisioning. */
24
+ findDatabaseByName(name: string): Promise<D1DatabaseInfo | null>;
25
+ /** Create a D1 database by name; returns its id and name. */
26
+ createDatabase(name: string): Promise<D1DatabaseInfo>;
27
+ /** Delete a D1 database by id. */
28
+ deleteDatabase(databaseId: string): Promise<void>;
29
+ }
30
+ //# sourceMappingURL=d1Provisioner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1Provisioner.d.ts","sourceRoot":"","sources":["../../src/d1/d1Provisioner.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,kEAAkE;AAClE,eAAO,MAAM,cAAc;;;iBAK4D,CAAC;AACxF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC;AAE7D;;;;;GAKG;AACH,qBAAa,uBAAwB,SAAQ,iBAAiB;IAC5D,cAAc,IAAI,MAAM,CAEvB;IAED,mGAAmG;IAC7F,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO9C;IAED,uGAAuG;IACjG,aAAa,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAS/C;IAED,6FAA6F;IACvF,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAQrE;IAED,6DAA6D;IACvD,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAK1D;IAED,kCAAkC;IAC5B,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAItD;CACF"}