@distilled.cloud/cloudflare 0.2.0-alpha → 0.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@distilled.cloud/cloudflare",
3
- "version": "0.2.0-alpha",
3
+ "version": "0.2.3",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/alchemy-run/distilled",
@@ -57,19 +57,20 @@
57
57
  "specs:update": "git -C specs/cloudflare-typescript fetch && git -C specs/cloudflare-typescript checkout main && git -C specs/cloudflare-typescript pull"
58
58
  },
59
59
  "dependencies": {
60
- "@distilled.cloud/core": "0.2.0",
61
- "effect": "4.0.0-beta.25"
60
+ "@distilled.cloud/core": "0.2.0-alpha",
61
+ "effect": "4.0.0-beta.30"
62
62
  },
63
63
  "devDependencies": {
64
- "@effect/platform-node": "4.0.0-beta.25",
65
- "@effect/vitest": "4.0.0-beta.25",
64
+ "@effect/platform-node": "4.0.0-beta.30",
65
+ "@effect/vitest": "4.0.0-beta.30",
66
66
  "@types/bun": "^1.3.0",
67
67
  "@types/node": "^25.3.5",
68
68
  "dotenv": "^16.5.0",
69
+ "typescript": "^5.9.3",
69
70
  "vite": "^7.3.1",
70
71
  "vitest": "^3.2.3"
71
72
  },
72
73
  "peerDependencies": {
73
- "effect": "4.0.0-beta.25"
74
+ "effect": "4.0.0-beta.30"
74
75
  }
75
76
  }
package/src/client/api.ts CHANGED
@@ -14,10 +14,54 @@ import {
14
14
  type OperationMethod,
15
15
  type PaginatedOperationMethod,
16
16
  } from "@distilled.cloud/core/client";
17
- import { CloudflareHttpError, UnknownCloudflareError } from "../errors.ts";
17
+ import {
18
+ CloudflareHttpError,
19
+ HTTP_STATUS_MAP,
20
+ InternalServerError,
21
+ TooManyRequests,
22
+ UnknownCloudflareError,
23
+ } from "../errors.ts";
18
24
  import { Credentials } from "../credentials.ts";
19
25
  import { type ErrorMatcher, getErrorMatchers } from "../traits.ts";
20
26
 
27
+ // ============================================================================
28
+ // Global Cloudflare error codes
29
+ // ============================================================================
30
+
31
+ /**
32
+ * Cloudflare error codes that map to global/default errors regardless of operation.
33
+ * These are infrastructure-level errors that can occur on any endpoint.
34
+ */
35
+ const GLOBAL_ERROR_CODE_MAP: Record<number, (message: string) => unknown> = {
36
+ // "Please wait and consider throttling your request speed"
37
+ 971: (message) => new TooManyRequests({ message }),
38
+ };
39
+
40
+ /**
41
+ * Create an appropriate error for an HTTP status code.
42
+ *
43
+ * For status codes in HTTP_STATUS_MAP (400, 401, 500, 502, 503, 504, etc.),
44
+ * returns the properly categorized error (with retry categories for 5xx).
45
+ * For unmapped 5xx codes (e.g., Cloudflare-specific 520-530), returns a
46
+ * CloudflareHttpError so the status is preserved.
47
+ */
48
+ function httpStatusError(status: number, body?: string): unknown {
49
+ const ErrorClass = HTTP_STATUS_MAP[status as keyof typeof HTTP_STATUS_MAP];
50
+ if (ErrorClass) {
51
+ return new ErrorClass({ message: body ?? String(status) });
52
+ }
53
+ // For unmapped 5xx codes (e.g., Cloudflare-specific 520-530), use
54
+ // InternalServerError so they get ServerError + Retryable categories
55
+ if (status >= 500) {
56
+ return new InternalServerError({ message: body ?? String(status) });
57
+ }
58
+ return new CloudflareHttpError({
59
+ status,
60
+ statusText: String(status),
61
+ body,
62
+ });
63
+ }
64
+
21
65
  export type { OperationMethod, PaginatedOperationMethod };
22
66
 
23
67
  // ============================================================================
@@ -123,12 +167,18 @@ const matchError = (
123
167
  errorBody: unknown,
124
168
  errors?: readonly ApiErrorClass[],
125
169
  ): Effect.Effect<never, unknown> => {
126
- // Handle non-JSON error responses (e.g., HTML from malformed URLs)
170
+ // Handle non-JSON error responses (e.g., HTML from malformed URLs, 520 pages)
127
171
  const isNonJsonError =
128
172
  typeof errorBody === "object" &&
129
173
  errorBody !== null &&
130
174
  "_nonJsonError" in errorBody;
131
175
  if (isNonJsonError) {
176
+ // For 5xx errors, return a properly categorized error so retries work
177
+ if (status >= 500) {
178
+ return Effect.fail(
179
+ httpStatusError(status, String((errorBody as any).body)),
180
+ );
181
+ }
132
182
  return Effect.fail(
133
183
  new CloudflareHttpError({
134
184
  status,
@@ -165,12 +215,17 @@ const matchError = (
165
215
 
166
216
  if (!isEnvelope) {
167
217
  // Not a Cloudflare envelope — HTTP-level error
218
+ // For 5xx errors, return a properly categorized error so retries work
219
+ const bodyStr =
220
+ typeof errorBody === "string" ? errorBody : JSON.stringify(errorBody);
221
+ if (status >= 500) {
222
+ return Effect.fail(httpStatusError(status, bodyStr));
223
+ }
168
224
  return Effect.fail(
169
225
  new CloudflareHttpError({
170
226
  status,
171
227
  statusText: String(status),
172
- body:
173
- typeof errorBody === "string" ? errorBody : JSON.stringify(errorBody),
228
+ body: bodyStr,
174
229
  }),
175
230
  );
176
231
  }
@@ -221,6 +276,11 @@ const matchError = (
221
276
  }
222
277
  }
223
278
 
279
+ // Check global error codes before falling through to unknown
280
+ if (errorCode !== undefined && errorCode in GLOBAL_ERROR_CODE_MAP) {
281
+ return Effect.fail(GLOBAL_ERROR_CODE_MAP[errorCode](errorMessage));
282
+ }
283
+
224
284
  // No match — return unknown Cloudflare error
225
285
  return Effect.fail(
226
286
  new UnknownCloudflareError({
package/src/errors.ts CHANGED
@@ -2,10 +2,12 @@
2
2
  * Cloudflare-specific error types.
3
3
  */
4
4
  export {
5
+ BadGateway,
5
6
  BadRequest,
6
7
  Conflict,
7
8
  ConfigError,
8
9
  Forbidden,
10
+ GatewayTimeout,
9
11
  InternalServerError,
10
12
  NotFound,
11
13
  ServiceUnavailable,
@@ -21,16 +23,6 @@ export type { DefaultErrors } from "@distilled.cloud/core/errors";
21
23
  import * as Schema from "effect/Schema";
22
24
  import * as Category from "@distilled.cloud/core/category";
23
25
 
24
- // Generic Cloudflare API Error
25
- export class CloudflareApiError extends Schema.TaggedErrorClass<CloudflareApiError>()(
26
- "CloudflareApiError",
27
- {
28
- code: Schema.optional(Schema.Number),
29
- message: Schema.optional(Schema.String),
30
- body: Schema.Unknown,
31
- },
32
- ).pipe(Category.withServerError) {}
33
-
34
26
  // Schema parse error wrapper
35
27
  export class CloudflareParseError extends Schema.TaggedErrorClass<CloudflareParseError>()(
36
28
  "CloudflareParseError",
@@ -27,7 +27,7 @@ export class NotEntitled extends Schema.TaggedErrorClass<NotEntitled>()(
27
27
  "NotEntitled",
28
28
  { code: Schema.Number, message: Schema.String },
29
29
  ) {}
30
- T.applyErrorMatchers(NotEntitled, [{ code: 10403 }]);
30
+ T.applyErrorMatchers(NotEntitled, [{ code: 10403 }, { code: 10404 }]);
31
31
 
32
32
  export class OperationNotFound extends Schema.TaggedErrorClass<OperationNotFound>()(
33
33
  "OperationNotFound",
@@ -17689,7 +17689,7 @@ export const listDevices: API.OperationMethod<
17689
17689
  }));
17690
17690
 
17691
17691
  // =============================================================================
17692
- // DeviceDevices_
17692
+ // DeviceDevice
17693
17693
  // =============================================================================
17694
17694
 
17695
17695
  export interface GetDeviceDevicesRequest {
@@ -17847,7 +17847,7 @@ export const GetDeviceDevicesResponse =
17847
17847
 
17848
17848
  export type GetDeviceDevicesError = DefaultErrors;
17849
17849
 
17850
- export const getDeviceDevices_: API.OperationMethod<
17850
+ export const getDeviceDevices: API.OperationMethod<
17851
17851
  GetDeviceDevicesRequest,
17852
17852
  GetDeviceDevicesResponse,
17853
17853
  GetDeviceDevicesError,
@@ -17858,79 +17858,7 @@ export const getDeviceDevices_: API.OperationMethod<
17858
17858
  errors: [],
17859
17859
  }));
17860
17860
 
17861
- export interface DeleteDeviceDevicesRequest {
17862
- deviceId: string;
17863
- accountId: string;
17864
- }
17865
-
17866
- export const DeleteDeviceDevicesRequest =
17867
- /*@__PURE__*/ /*#__PURE__*/ Schema.Struct({
17868
- deviceId: Schema.String.pipe(T.HttpPath("deviceId")),
17869
- accountId: Schema.String.pipe(T.HttpPath("account_id")),
17870
- }).pipe(
17871
- T.Http({
17872
- method: "DELETE",
17873
- path: "/accounts/{account_id}/devices/physical-devices/{deviceId}",
17874
- }),
17875
- ) as unknown as Schema.Schema<DeleteDeviceDevicesRequest>;
17876
-
17877
- export type DeleteDeviceDevicesResponse = unknown;
17878
-
17879
- export const DeleteDeviceDevicesResponse =
17880
- /*@__PURE__*/ /*#__PURE__*/ Schema.Unknown as unknown as Schema.Schema<DeleteDeviceDevicesResponse>;
17881
-
17882
- export type DeleteDeviceDevicesError = DefaultErrors;
17883
-
17884
- export const deleteDeviceDevices_: API.OperationMethod<
17885
- DeleteDeviceDevicesRequest,
17886
- DeleteDeviceDevicesResponse,
17887
- DeleteDeviceDevicesError,
17888
- Credentials | HttpClient.HttpClient
17889
- > = /*@__PURE__*/ /*#__PURE__*/ API.make(() => ({
17890
- input: DeleteDeviceDevicesRequest,
17891
- output: DeleteDeviceDevicesResponse,
17892
- errors: [],
17893
- }));
17894
-
17895
- export interface RevokeDeviceDevicesRequest {
17896
- deviceId: string;
17897
- accountId: string;
17898
- }
17899
-
17900
- export const RevokeDeviceDevicesRequest =
17901
- /*@__PURE__*/ /*#__PURE__*/ Schema.Struct({
17902
- deviceId: Schema.String.pipe(T.HttpPath("deviceId")),
17903
- accountId: Schema.String.pipe(T.HttpPath("account_id")),
17904
- }).pipe(
17905
- T.Http({
17906
- method: "POST",
17907
- path: "/accounts/{account_id}/devices/physical-devices/{deviceId}/revoke",
17908
- }),
17909
- ) as unknown as Schema.Schema<RevokeDeviceDevicesRequest>;
17910
-
17911
- export type RevokeDeviceDevicesResponse = unknown;
17912
-
17913
- export const RevokeDeviceDevicesResponse =
17914
- /*@__PURE__*/ /*#__PURE__*/ Schema.Unknown as unknown as Schema.Schema<RevokeDeviceDevicesResponse>;
17915
-
17916
- export type RevokeDeviceDevicesError = DefaultErrors;
17917
-
17918
- export const revokeDeviceDevices_: API.OperationMethod<
17919
- RevokeDeviceDevicesRequest,
17920
- RevokeDeviceDevicesResponse,
17921
- RevokeDeviceDevicesError,
17922
- Credentials | HttpClient.HttpClient
17923
- > = /*@__PURE__*/ /*#__PURE__*/ API.make(() => ({
17924
- input: RevokeDeviceDevicesRequest,
17925
- output: RevokeDeviceDevicesResponse,
17926
- errors: [],
17927
- }));
17928
-
17929
- // =============================================================================
17930
- // DeviceDevicesS
17931
- // =============================================================================
17932
-
17933
- export interface ListDeviceDevicesSRequest {
17861
+ export interface ListDeviceDevicesRequest {
17934
17862
  /** Path param: */
17935
17863
  accountId: string;
17936
17864
  /** Query param: Filter by a one or more device IDs. */
@@ -17960,7 +17888,7 @@ export interface ListDeviceDevicesSRequest {
17960
17888
  sortOrder?: "asc" | "desc";
17961
17889
  }
17962
17890
 
17963
- export const ListDeviceDevicesSRequest =
17891
+ export const ListDeviceDevicesRequest =
17964
17892
  /*@__PURE__*/ /*#__PURE__*/ Schema.Struct({
17965
17893
  accountId: Schema.String.pipe(T.HttpPath("account_id")),
17966
17894
  id: Schema.optional(Schema.Array(Schema.String)).pipe(T.HttpQuery("id")),
@@ -17995,9 +17923,9 @@ export const ListDeviceDevicesSRequest =
17995
17923
  method: "GET",
17996
17924
  path: "/accounts/{account_id}/devices/physical-devices",
17997
17925
  }),
17998
- ) as unknown as Schema.Schema<ListDeviceDevicesSRequest>;
17926
+ ) as unknown as Schema.Schema<ListDeviceDevicesRequest>;
17999
17927
 
18000
- export type ListDeviceDevicesSResponse = {
17928
+ export type ListDeviceDevicesResponse = {
18001
17929
  id: string;
18002
17930
  activeRegistrations: number;
18003
17931
  createdAt: string;
@@ -18031,7 +17959,7 @@ export type ListDeviceDevicesSResponse = {
18031
17959
  serialNumber?: string | null;
18032
17960
  }[];
18033
17961
 
18034
- export const ListDeviceDevicesSResponse =
17962
+ export const ListDeviceDevicesResponse =
18035
17963
  /*@__PURE__*/ /*#__PURE__*/ Schema.Array(
18036
17964
  Schema.Struct({
18037
17965
  id: Schema.String,
@@ -18115,18 +18043,86 @@ export const ListDeviceDevicesSResponse =
18115
18043
  serialNumber: "serial_number",
18116
18044
  }),
18117
18045
  ),
18118
- ) as unknown as Schema.Schema<ListDeviceDevicesSResponse>;
18046
+ ) as unknown as Schema.Schema<ListDeviceDevicesResponse>;
18047
+
18048
+ export type ListDeviceDevicesError = DefaultErrors;
18049
+
18050
+ export const listDeviceDevices: API.OperationMethod<
18051
+ ListDeviceDevicesRequest,
18052
+ ListDeviceDevicesResponse,
18053
+ ListDeviceDevicesError,
18054
+ Credentials | HttpClient.HttpClient
18055
+ > = /*@__PURE__*/ /*#__PURE__*/ API.make(() => ({
18056
+ input: ListDeviceDevicesRequest,
18057
+ output: ListDeviceDevicesResponse,
18058
+ errors: [],
18059
+ }));
18060
+
18061
+ export interface DeleteDeviceDevicesRequest {
18062
+ deviceId: string;
18063
+ accountId: string;
18064
+ }
18065
+
18066
+ export const DeleteDeviceDevicesRequest =
18067
+ /*@__PURE__*/ /*#__PURE__*/ Schema.Struct({
18068
+ deviceId: Schema.String.pipe(T.HttpPath("deviceId")),
18069
+ accountId: Schema.String.pipe(T.HttpPath("account_id")),
18070
+ }).pipe(
18071
+ T.Http({
18072
+ method: "DELETE",
18073
+ path: "/accounts/{account_id}/devices/physical-devices/{deviceId}",
18074
+ }),
18075
+ ) as unknown as Schema.Schema<DeleteDeviceDevicesRequest>;
18119
18076
 
18120
- export type ListDeviceDevicesSError = DefaultErrors;
18077
+ export type DeleteDeviceDevicesResponse = unknown;
18121
18078
 
18122
- export const listDeviceDevicesS: API.OperationMethod<
18123
- ListDeviceDevicesSRequest,
18124
- ListDeviceDevicesSResponse,
18125
- ListDeviceDevicesSError,
18079
+ export const DeleteDeviceDevicesResponse =
18080
+ /*@__PURE__*/ /*#__PURE__*/ Schema.Unknown as unknown as Schema.Schema<DeleteDeviceDevicesResponse>;
18081
+
18082
+ export type DeleteDeviceDevicesError = DefaultErrors;
18083
+
18084
+ export const deleteDeviceDevices: API.OperationMethod<
18085
+ DeleteDeviceDevicesRequest,
18086
+ DeleteDeviceDevicesResponse,
18087
+ DeleteDeviceDevicesError,
18126
18088
  Credentials | HttpClient.HttpClient
18127
18089
  > = /*@__PURE__*/ /*#__PURE__*/ API.make(() => ({
18128
- input: ListDeviceDevicesSRequest,
18129
- output: ListDeviceDevicesSResponse,
18090
+ input: DeleteDeviceDevicesRequest,
18091
+ output: DeleteDeviceDevicesResponse,
18092
+ errors: [],
18093
+ }));
18094
+
18095
+ export interface RevokeDeviceDevicesRequest {
18096
+ deviceId: string;
18097
+ accountId: string;
18098
+ }
18099
+
18100
+ export const RevokeDeviceDevicesRequest =
18101
+ /*@__PURE__*/ /*#__PURE__*/ Schema.Struct({
18102
+ deviceId: Schema.String.pipe(T.HttpPath("deviceId")),
18103
+ accountId: Schema.String.pipe(T.HttpPath("account_id")),
18104
+ }).pipe(
18105
+ T.Http({
18106
+ method: "POST",
18107
+ path: "/accounts/{account_id}/devices/physical-devices/{deviceId}/revoke",
18108
+ }),
18109
+ ) as unknown as Schema.Schema<RevokeDeviceDevicesRequest>;
18110
+
18111
+ export type RevokeDeviceDevicesResponse = unknown;
18112
+
18113
+ export const RevokeDeviceDevicesResponse =
18114
+ /*@__PURE__*/ /*#__PURE__*/ Schema.Unknown as unknown as Schema.Schema<RevokeDeviceDevicesResponse>;
18115
+
18116
+ export type RevokeDeviceDevicesError = DefaultErrors;
18117
+
18118
+ export const revokeDeviceDevices: API.OperationMethod<
18119
+ RevokeDeviceDevicesRequest,
18120
+ RevokeDeviceDevicesResponse,
18121
+ RevokeDeviceDevicesError,
18122
+ Credentials | HttpClient.HttpClient
18123
+ > = /*@__PURE__*/ /*#__PURE__*/ API.make(() => ({
18124
+ input: RevokeDeviceDevicesRequest,
18125
+ output: RevokeDeviceDevicesResponse,
18130
18126
  errors: [],
18131
18127
  }));
18132
18128