@distilled.cloud/expo-eas 1.0.0-rc.13 → 1.0.0-rc.15

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/src/protocol.ts CHANGED
@@ -1,3 +1,8 @@
1
+ import * as API from "@distilled.cloud/core/api";
2
+ import { HTTP_STATUS_MAP } from "@distilled.cloud/core/errors";
3
+ import { getAnn } from "@distilled.cloud/core/protocol-http";
4
+ import { failIfStrict, validateResponse } from "@distilled.cloud/core/response-validation";
5
+ import { parseRetryAfterForStatus } from "@distilled.cloud/core/retry-after";
1
6
  /**
2
7
  * ExpoGraphqlProtocol — hand-written.
3
8
  *
@@ -18,29 +23,21 @@
18
23
  * `EAS_ERROR_CODE_MAP`, then the HTTP status map, then
19
24
  * `UnknownEasError`.
20
25
  * 2. HTTP status >= 400 (network/auth/proxy errors) — body
21
- * usually `{ message }` or a plain `{ errors: [...] }`
22
- * envelope; mapped via the status map with retry-after
23
- * parsing.
26
+ * usually `{ message }`, a plain `{ errors: [...] }`
27
+ * envelope, or non-JSON text (proxy HTML, plain-text 429);
28
+ * mapped via the status map with retry-after parsing.
24
29
  * On success the value of `data.<responsePath>` (see
25
30
  * `T.ResponsePath`) is returned verbatim.
26
31
  */
27
32
  import * as Effect from "effect/Effect";
28
- import * as Layer from "effect/Layer";
29
- import * as Redacted from "effect/Redacted";
30
- import * as Schema from "effect/Schema";
31
- import type * as AST from "effect/SchemaAST";
32
33
  import type * as HttpClient from "effect/http/HttpClient";
33
34
  import type * as HttpClientError from "effect/http/HttpClientError";
34
35
  import * as HttpClientRequest from "effect/http/HttpClientRequest";
35
36
  import type * as HttpClientResponse from "effect/http/HttpClientResponse";
36
- import * as API from "@distilled.cloud/core/api";
37
- import { getAnn } from "@distilled.cloud/core/protocol-http";
38
- import { HTTP_STATUS_MAP } from "@distilled.cloud/core/errors";
39
- import {
40
- failIfStrict,
41
- validateResponse,
42
- } from "@distilled.cloud/core/response-validation";
43
- import { parseRetryAfterForStatus } from "@distilled.cloud/core/retry-after";
37
+ import * as Layer from "effect/Layer";
38
+ import * as Redacted from "effect/Redacted";
39
+ import * as Schema from "effect/Schema";
40
+ import type * as AST from "effect/SchemaAST";
44
41
  import { type Config, Credentials } from "./credentials.ts";
45
42
  import {
46
43
  type DefaultErrors,
@@ -75,8 +72,7 @@ export type ExpoEasOpContext = Credentials | HttpClient.HttpClient;
75
72
  // EAS failures are real typed errors that the operation's explicit
76
73
  // `ExpoEasOpError` annotation re-surfaces. Fail with the instance and erase
77
74
  // the error type here.
78
- const fail = (e: unknown): Effect.Effect<never> =>
79
- Effect.fail(e) as Effect.Effect<never>;
75
+ const fail = (e: unknown): Effect.Effect<never> => Effect.fail(e) as Effect.Effect<never>;
80
76
 
81
77
  // ============================================================================
82
78
  // Error envelope parsing (ported from distilled v0's client.ts)
@@ -118,6 +114,9 @@ type StatusClass = new (args: {
118
114
  retryAfter?: ReturnType<typeof parseRetryAfterForStatus>;
119
115
  }) => unknown;
120
116
 
117
+ const statusClass = (status: number): StatusClass | undefined =>
118
+ (HTTP_STATUS_MAP as Record<number, StatusClass | undefined>)[status];
119
+
121
120
  /**
122
121
  * Match an EAS error response (GraphQL envelope or HTTP-level body) to the
123
122
  * appropriate error class. Ported from distilled v0's `matchError`:
@@ -144,9 +143,7 @@ const matchError = (
144
143
  }
145
144
  }
146
145
 
147
- const StatusClass = (HTTP_STATUS_MAP as Record<number, unknown>)[status] as
148
- | StatusClass
149
- | undefined;
146
+ const StatusClass = statusClass(status);
150
147
  if (StatusClass && status >= 400) {
151
148
  return fail(
152
149
  new StatusClass({
@@ -165,12 +162,25 @@ const matchError = (
165
162
  );
166
163
  }
167
164
 
165
+ // Non-JSON body (HTML proxy page, plain-text 429/401): the trimmed text
166
+ // is the message.
167
+ if (typeof errorBody === "string") {
168
+ const StatusClass = statusClass(status);
169
+ if (StatusClass) {
170
+ return fail(
171
+ new StatusClass({
172
+ message: errorBody.trim() || `HTTP ${status}`,
173
+ retryAfter: parseRetryAfterForStatus(status, headers),
174
+ }),
175
+ );
176
+ }
177
+ return fail(new UnknownEasError({ body: errorBody }));
178
+ }
179
+
168
180
  // Plain REST-ish error body
169
181
  const rest = decodeRest(errorBody);
170
182
  if (rest._tag === "Some") {
171
- const StatusClass = (HTTP_STATUS_MAP as Record<number, unknown>)[status] as
172
- | StatusClass
173
- | undefined;
183
+ const StatusClass = statusClass(status);
174
184
  if (StatusClass) {
175
185
  return fail(
176
186
  new StatusClass({
@@ -200,13 +210,7 @@ const matchError = (
200
210
  // fiber's context on every request instead. The requirement is erased at this
201
211
  // boundary (Protocol effects are typed with no requirements) and reintroduced
202
212
  // for callers by the generated `ExpoEasOpContext` annotations.
203
- const encode = ({
204
- input,
205
- inputAst,
206
- }: {
207
- readonly input: unknown;
208
- readonly inputAst: AST.AST;
209
- }) =>
213
+ const encode = ({ input, inputAst }: { readonly input: unknown; readonly inputAst: AST.AST }) =>
210
214
  Effect.gen(function* () {
211
215
  // The Credentials service holds an effect — resolving it here (per
212
216
  // request) picks up rotations.
@@ -223,9 +227,7 @@ const encode = ({
223
227
  // The operation's input IS the GraphQL variables object (variable names
224
228
  // are emitted verbatim — no wire renames).
225
229
  const variables: Record<string, unknown> = {};
226
- for (const [k, v] of Object.entries(
227
- (input ?? {}) as Record<string, unknown>,
228
- )) {
230
+ for (const [k, v] of Object.entries((input ?? {}) as Record<string, unknown>)) {
229
231
  if (v !== undefined) variables[k] = v;
230
232
  }
231
233
 
@@ -308,9 +310,7 @@ const decode = ({
308
310
  // (core/response-validation) checks it against the output schema.
309
311
  const path = getAnn(outputAst, responsePathSymbol) as string | undefined;
310
312
  let payload: unknown =
311
- envelope !== null && typeof envelope === "object"
312
- ? envelope.data
313
- : undefined;
313
+ envelope !== null && typeof envelope === "object" ? envelope.data : undefined;
314
314
  if (path !== undefined) {
315
315
  for (const seg of path.split(".")) {
316
316
  payload =
@@ -333,8 +333,7 @@ export const ExpoGraphqlProtocol: Layer.Layer<API.Protocol> = Layer.succeed(
333
333
  API.Protocol,
334
334
  API.Protocol.of({
335
335
  // Erase encode's Credentials requirement (see comment above).
336
- encode: (args) =>
337
- encode(args) as Effect.Effect<HttpClientRequest.HttpClientRequest>,
336
+ encode: (args) => encode(args) as Effect.Effect<HttpClientRequest.HttpClientRequest>,
338
337
  decode,
339
338
  }),
340
339
  );
package/src/retry.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type Policy, throttlingFactory, transientFactory } from "@distilled.cloud/core/retry";
1
2
  /**
2
3
  * ExpoEas retry configuration.
3
4
  *
@@ -17,11 +18,6 @@
17
18
  import * as Context from "effect/Context";
18
19
  import * as Effect from "effect/Effect";
19
20
  import * as Layer from "effect/Layer";
20
- import {
21
- type Policy,
22
- throttlingFactory,
23
- transientFactory,
24
- } from "@distilled.cloud/core/retry";
25
21
 
26
22
  export {
27
23
  type Options,
@@ -44,9 +40,7 @@ export const policy = (optionsOrFactory: Policy) =>
44
40
  Effect.provide(Layer.succeed(Retry, optionsOrFactory));
45
41
 
46
42
  /** Disables all automatic retries. */
47
- export const none = Effect.provide(
48
- Layer.succeed(Retry, { while: () => false }),
49
- );
43
+ export const none = Effect.provide(Layer.succeed(Retry, { while: () => false }));
50
44
 
51
45
  /** Apply the throttling retry policy (retries throttling errors indefinitely). */
52
46
  export const throttling = policy(throttlingFactory);