diff --git a/README.md b/README.md index 77b6ac5..40212cd 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ export default scenario("example http request") .step("assert response", (ctx) => { const response = ctx.previous; assertEquals(response.status, 200); - assertEquals(response.json().args.hello, "world"); + assertEquals(response.json.args.hello, "world"); }) .build(); ``` diff --git a/packages/probitas-client-connectrpc/client.ts b/packages/probitas-client-connectrpc/client.ts index 6ed0aed..32dd14a 100644 --- a/packages/probitas-client-connectrpc/client.ts +++ b/packages/probitas-client-connectrpc/client.ts @@ -292,7 +292,7 @@ function toMethodInfo( * "echo", * { message: "Hello!" } * ); - * console.log(response.data()); + * console.log(response.data); * * await client.close(); * ``` diff --git a/packages/probitas-client-connectrpc/integration_test.ts b/packages/probitas-client-connectrpc/integration_test.ts index b30bef2..5fc9833 100644 --- a/packages/probitas-client-connectrpc/integration_test.ts +++ b/packages/probitas-client-connectrpc/integration_test.ts @@ -189,9 +189,9 @@ Deno.test({ assert(response.ok); assertEquals(response.statusCode, 0); - assertExists(response.data()); + assertExists(response.data); - const data = response.data<{ message: string }>(); + const data = response.data as { message: string } | null; assertExists(data); assertEquals(data.message, "Hello Reflection!"); }); @@ -205,8 +205,8 @@ Deno.test({ assert(response.ok); assertEquals(response.statusCode, 0); - assertExists(response.data()); - const data = response.data<{ message: string }>(); + assertExists(response.data); + const data = response.data as { message: string } | null; assertEquals(data?.message, "Test message"); assertLess(response.duration, 5000); }); @@ -377,7 +377,7 @@ Deno.test({ ) ) { assert(response.ok); - messages.push(response.data()); + messages.push(response.data); } assertEquals(messages.length, 3); @@ -507,7 +507,7 @@ Deno.test({ assertFalse(response.ok); assertEquals(response.statusCode !== 0, true); - assertEquals(response.data(), null); + assertEquals(response.data, null); }); }, }); diff --git a/packages/probitas-client-connectrpc/mod.ts b/packages/probitas-client-connectrpc/mod.ts index 3302688..43379ac 100644 --- a/packages/probitas-client-connectrpc/mod.ts +++ b/packages/probitas-client-connectrpc/mod.ts @@ -43,7 +43,7 @@ * "echo", * { message: "Hello!" } * ); - * console.log(response.data()); + * console.log(response.data); * * await client.close(); * ``` @@ -77,7 +77,7 @@ * await using client = createConnectRpcClient({ url: "http://localhost:50051" }); * * const res = await client.call("echo.EchoService", "echo", { message: "test" }); - * console.log(res.data()); + * console.log(res.data); * // Client automatically closed when block exits * ``` * diff --git a/packages/probitas-client-connectrpc/response.ts b/packages/probitas-client-connectrpc/response.ts index 45c7221..04d100c 100644 --- a/packages/probitas-client-connectrpc/response.ts +++ b/packages/probitas-client-connectrpc/response.ts @@ -66,17 +66,17 @@ interface ConnectRpcResponseBase extends ClientResult { readonly duration: number; /** - * Get deserialized response data. - * Returns the response message as-is (already deserialized by Connect). - * Returns null if the response is an error or has no data. + * Deserialized response data. + * The response message as-is (already deserialized by Connect). + * Null if the response is an error or has no data. */ - data(): U | null; + readonly data: T | null; /** - * Get raw response or error. - * Returns null for failure responses. + * Raw response or error. + * Null for failure responses. */ - raw(): unknown | null; + readonly raw: unknown | null; } /** @@ -101,7 +101,11 @@ export interface ConnectRpcResponseSuccess /** Response trailers (sent at end of RPC). */ readonly trailers: Headers; - raw(): unknown; + /** Raw response. */ + readonly raw: unknown; + + /** Response data. */ + readonly data: T | null; } /** @@ -128,7 +132,11 @@ export interface ConnectRpcResponseError /** Response trailers (sent at end of RPC). */ readonly trailers: Headers; - raw(): ConnectError; + /** Raw ConnectError. */ + readonly raw: ConnectError; + + /** No data for error responses. */ + readonly data: null; } /** @@ -155,7 +163,11 @@ export interface ConnectRpcResponseFailure /** Response trailers (null for failures). */ readonly trailers: null; - raw(): null; + /** No raw response (request didn't reach server). */ + readonly raw: null; + + /** No data (request didn't reach server). */ + readonly data: null; } /** @@ -220,25 +232,15 @@ export class ConnectRpcResponseSuccessImpl readonly headers: Headers; readonly trailers: Headers; readonly duration: number; - - readonly #response: T | null; + readonly data: T | null; + readonly raw: T | null; constructor(params: ConnectRpcResponseSuccessParams) { this.headers = params.headers; this.trailers = params.trailers; this.duration = params.duration; - this.#response = params.response; - } - - data(): U | null { - if (this.#response === null || this.#response === undefined) { - return null; - } - return this.#response as U; - } - - raw(): T | null { - return this.#response; + this.data = params.response; + this.raw = params.response; } } @@ -257,26 +259,18 @@ export class ConnectRpcResponseErrorImpl readonly headers: Headers; readonly trailers: Headers; readonly duration: number; - - readonly #connectError: ConnectError; + readonly data = null; + readonly raw: ConnectError; constructor(params: ConnectRpcResponseErrorParams) { this.headers = params.headers; this.trailers = params.trailers; this.duration = params.duration; this.error = params.rpcError; - this.#connectError = params.error; + this.raw = params.error; this.statusCode = params.rpcError.statusCode; this.statusMessage = params.rpcError.statusMessage; } - - data(): U | null { - return null; - } - - raw(): ConnectError { - return this.#connectError; - } } /** @@ -294,17 +288,11 @@ export class ConnectRpcResponseFailureImpl readonly headers = null; readonly trailers = null; readonly duration: number; + readonly data = null; + readonly raw = null; constructor(params: ConnectRpcResponseFailureParams) { this.error = params.error; this.duration = params.duration; } - - data(): null { - return null; - } - - raw(): null { - return null; - } } diff --git a/packages/probitas-client-connectrpc/response_test.ts b/packages/probitas-client-connectrpc/response_test.ts index 68b9dab..41badb1 100644 --- a/packages/probitas-client-connectrpc/response_test.ts +++ b/packages/probitas-client-connectrpc/response_test.ts @@ -31,7 +31,7 @@ Deno.test("ConnectRpcResponseSuccessImpl", async (t) => { assertEquals(response.error, null); assertEquals(response.statusCode, 0); assertEquals(response.statusMessage, null); - assertEquals(response.data(), { user: { id: 1, name: "John" } }); + assertEquals(response.data, { user: { id: 1, name: "John" } }); assertEquals(response.duration, 100); }); @@ -51,7 +51,7 @@ Deno.test("ConnectRpcResponseSuccessImpl", async (t) => { assertEquals(response.trailers.get("grpc-status"), "0"); }); - await t.step("raw() returns response data", () => { + await t.step("raw returns response data", () => { const rawResponse = { nested: { value: 123 } }; const response = new ConnectRpcResponseSuccessImpl({ response: rawResponse, @@ -60,10 +60,10 @@ Deno.test("ConnectRpcResponseSuccessImpl", async (t) => { duration: 10, }); - assertEquals(response.raw(), rawResponse); + assertEquals(response.raw, rawResponse); }); - await t.step("data() method returns typed data", () => { + await t.step("data supports type assertion", () => { interface User { id: number; name: string; @@ -75,7 +75,7 @@ Deno.test("ConnectRpcResponseSuccessImpl", async (t) => { duration: 100, }); - const result = response.data<{ user: User }>(); + const result = response.data as { user: User } | null; assertEquals(result?.user.id, 1); assertEquals(result?.user.name, "John"); }); @@ -88,8 +88,8 @@ Deno.test("ConnectRpcResponseSuccessImpl", async (t) => { duration: 100, }); - assertEquals(response.data(), null); - assertEquals(response.raw(), null); + assertEquals(response.data, null); + assertEquals(response.raw, null); }); }); @@ -111,10 +111,10 @@ Deno.test("ConnectRpcResponseErrorImpl", async (t) => { assertEquals(response.error, rpcError); assertEquals(response.statusCode, 5); assertEquals(response.statusMessage, "Not found"); - assertEquals(response.data(), null); + assertEquals(response.data, null); }); - await t.step("raw() returns ConnectError", () => { + await t.step("raw returns ConnectError", () => { const connectError = new ConnectError("Internal error", 13); const rpcError = fromConnectError(connectError); const response = new ConnectRpcResponseErrorImpl({ @@ -125,7 +125,7 @@ Deno.test("ConnectRpcResponseErrorImpl", async (t) => { duration: 10, }); - assertEquals(response.raw(), connectError); + assertEquals(response.raw, connectError); }); await t.step("includes headers and trailers", () => { @@ -203,23 +203,23 @@ Deno.test("ConnectRpcResponseFailureImpl", async (t) => { assertEquals(response.trailers, null); }); - await t.step("data() returns null", () => { + await t.step("data is null", () => { const error = new ConnectRpcNetworkError("Network error"); const response = new ConnectRpcResponseFailureImpl({ error, duration: 5, }); - assertEquals(response.data(), null); + assertEquals(response.data, null); }); - await t.step("raw() returns null", () => { + await t.step("raw is null", () => { const error = new ConnectRpcNetworkError("Network error"); const response = new ConnectRpcResponseFailureImpl({ error, duration: 5, }); - assertEquals(response.raw(), null); + assertEquals(response.raw, null); }); }); diff --git a/packages/probitas-client-graphql/client.ts b/packages/probitas-client-graphql/client.ts index c5670d7..c3bfd64 100644 --- a/packages/probitas-client-graphql/client.ts +++ b/packages/probitas-client-graphql/client.ts @@ -533,7 +533,7 @@ class GraphqlClientImpl implements GraphqlClient { * } * `, { id: "123" }); * - * console.log(response.data()); + * console.log(response.data); * await client.close(); * ``` * @@ -560,7 +560,7 @@ class GraphqlClientImpl implements GraphqlClient { * `, { input: { name: "Alice", email: "alice@example.com" } }); * * if (response.ok) { - * console.log("Created user:", response.data().createUser.id); + * console.log("Created user:", (response.data as any).createUser.id); * } * * await client.close(); diff --git a/packages/probitas-client-graphql/client_test.ts b/packages/probitas-client-graphql/client_test.ts index d793215..685aa5f 100644 --- a/packages/probitas-client-graphql/client_test.ts +++ b/packages/probitas-client-graphql/client_test.ts @@ -119,7 +119,7 @@ Deno.test("GraphqlClient.query", async (t) => { await client.close(); assert(response.ok); - assertEquals(response.data(), { user: { id: 1, name: "John" } }); + assertEquals(response.data, { user: { id: 1, name: "John" } }); assertEquals(response.error, null); }); @@ -271,7 +271,7 @@ Deno.test("GraphqlClient.execute", async (t) => { const response = await client.execute("query { test }"); await client.close(); - assertEquals(response.data(), { test: true }); + assertEquals(response.data, { test: true }); }); await t.step("works for mutations", async () => { @@ -287,7 +287,7 @@ Deno.test("GraphqlClient.execute", async (t) => { const response = await client.execute("mutation { updateSomething }"); await client.close(); - assertEquals(response.data(), { updated: true }); + assertEquals(response.data, { updated: true }); }); }); @@ -432,7 +432,7 @@ Deno.test("GraphqlClient response", async (t) => { const response = await client.query("query { test }"); await client.close(); - assertInstanceOf(response.raw(), Response); + assertInstanceOf(response.raw, Response); }); }); @@ -458,8 +458,8 @@ Deno.test("GraphqlClient network errors", async (t) => { assertEquals(response.error?.message, "fetch failed"); assertEquals(response.status, null); assertEquals(response.headers, null); - assertEquals(response.data(), null); - assertEquals(response.raw(), null); + assertEquals(response.data, null); + assertEquals(response.raw, null); }, ); @@ -604,7 +604,7 @@ Deno.test("GraphqlClient partial data with errors", async (t) => { await client.close(); assertFalse(response.ok); - assertEquals(response.data(), { user: { id: 1 }, posts: null }); + assertEquals(response.data, { user: { id: 1 }, posts: null }); assertInstanceOf(response.error, GraphqlExecutionError); }, ); diff --git a/packages/probitas-client-graphql/integration_test.ts b/packages/probitas-client-graphql/integration_test.ts index b461337..0d7c2cc 100644 --- a/packages/probitas-client-graphql/integration_test.ts +++ b/packages/probitas-client-graphql/integration_test.ts @@ -48,9 +48,9 @@ Deno.test({ assert(res.ok); assertEquals(res.status, 200); - assertExists(res.data()); + assertExists(res.data); - assertEquals(res.data()?.__typename, "Query"); + assertEquals(res.data?.__typename, "Query"); }); await t.step("introspection query - __schema", async () => { @@ -59,9 +59,9 @@ Deno.test({ }>("{ __schema { queryType { name } } }"); assert(res.ok); - assertExists(res.data()); + assertExists(res.data); - assertEquals(res.data()?.__schema.queryType.name, "Query"); + assertEquals(res.data?.__schema.queryType.name, "Query"); }); await t.step("echo query - basic message", async () => { @@ -75,8 +75,8 @@ Deno.test({ ); assert(res.ok); - assertExists(res.data()); - assertEquals(res.data()?.echo, "Hello, Probitas!"); + assertExists(res.data); + assertEquals(res.data?.echo, "Hello, Probitas!"); }); await t.step("echoWithDelay for latency testing", async () => { @@ -92,8 +92,8 @@ Deno.test({ const elapsed = Date.now() - start; assert(res.ok); - assertExists(res.data()); - assertEquals(res.data()?.echoWithDelay, "Delayed message"); + assertExists(res.data); + assertEquals(res.data?.echoWithDelay, "Delayed message"); // Should have taken at least 500ms assertEquals( @@ -150,8 +150,8 @@ Deno.test({ ); assert(res.ok); - assertExists(res.data()); - const results = res.data()?.echoPartialError; + assertExists(res.data); + const results = res.data?.echoPartialError; // First and third should succeed, second should have error assertEquals(results?.[0]?.message, "success"); assertEquals(results?.[0]?.error, null); @@ -193,7 +193,7 @@ Deno.test({ await t.step("includes raw response", async () => { const res = await client.query("{ __typename }"); - assertInstanceOf(res.raw(), Response); + assertInstanceOf(res.raw, Response); }); await t.step("using await using (AsyncDisposable)", async () => { @@ -301,9 +301,9 @@ Deno.test({ ); assert(res.ok); - assertExists(res.data()); + assertExists(res.data); - const message = res.data()?.createMessage; + const message = res.data?.createMessage; assertEquals(message?.text, "Hello from integration test"); assertEquals(typeof message?.id, "string"); assertEquals(typeof message?.createdAt, "string"); @@ -314,8 +314,8 @@ Deno.test({ assert(res.ok); assertEquals(res.status, 200); - assertExists(res.data()); - assertEquals(res.data()?.__typename, "Query"); + assertExists(res.data); + assertEquals(res.data?.__typename, "Query"); assertLess(res.duration, 5000); }); @@ -334,8 +334,8 @@ Deno.test({ ); assert(res.ok); - assertExists(res.data()); - assertEquals(res.data()?.echoWithExtensions, "Hello"); + assertExists(res.data); + assertEquals(res.data?.echoWithExtensions, "Hello"); // Server includes timing and tracing extensions assertEquals(typeof res.extensions?.timing, "object"); assertEquals(typeof res.extensions?.tracing, "object"); @@ -358,7 +358,7 @@ Deno.test({ { text: "Original text" }, ); - const messageId = createRes.data()?.createMessage.id; + const messageId = createRes.data?.createMessage.id; // Then update it const updateRes = await client.mutation<{ @@ -376,9 +376,9 @@ Deno.test({ ); assert(updateRes.ok); - assertExists(updateRes.data()); - assertEquals(updateRes.data()?.updateMessage.id, messageId); - assertEquals(updateRes.data()?.updateMessage.text, "Updated text"); + assertExists(updateRes.data); + assertEquals(updateRes.data?.updateMessage.id, messageId); + assertEquals(updateRes.data?.updateMessage.text, "Updated text"); }); await t.step("mutation - deleteMessage", async () => { @@ -396,7 +396,7 @@ Deno.test({ { text: "To be deleted" }, ); - const messageId = createRes.data()?.createMessage.id; + const messageId = createRes.data?.createMessage.id; // Then delete it const deleteRes = await client.mutation<{ @@ -411,8 +411,8 @@ Deno.test({ ); assert(deleteRes.ok); - assertExists(deleteRes.data()); - assertEquals(deleteRes.data()?.deleteMessage, true); + assertExists(deleteRes.data); + assertEquals(deleteRes.data?.deleteMessage, true); }); await t.step("subscription - countdown", async () => { @@ -434,8 +434,8 @@ Deno.test({ ) ) { assert(res.ok); - assertExists(res.data()); - numbers.push(res.data()?.countdown ?? -1); + assertExists(res.data); + numbers.push(res.data?.countdown ?? -1); } assertEquals(numbers, [3, 2, 1, 0]); @@ -475,9 +475,9 @@ Deno.test({ ); assert(res.ok); - assertExists(res.data()); + assertExists(res.data); - const headers = res.data()?.echoHeaders; + const headers = res.data?.echoHeaders; // Verify config-level headers were sent assertEquals(headers?.authorization, "Bearer test-token-123"); assertEquals(headers?.custom, "probitas-integration-test"); diff --git a/packages/probitas-client-graphql/mod.ts b/packages/probitas-client-graphql/mod.ts index 79284af..a142925 100644 --- a/packages/probitas-client-graphql/mod.ts +++ b/packages/probitas-client-graphql/mod.ts @@ -31,7 +31,7 @@ * user(id: $id) { id name email } * } * `, { id: "123" }); - * console.log("User:", res.data()); + * console.log("User:", res.data); * * // Mutation * const created = await client.mutation(outdent` @@ -39,7 +39,7 @@ * createUser(input: $input) { id name } * } * `, { input: { name: "Jane", email: "jane@example.com" } }); - * console.log("Created:", created.data()); + * console.log("Created:", created.data); * * await client.close(); * ``` @@ -52,7 +52,7 @@ * await using client = createGraphqlClient({ url: "http://localhost:4000/graphql" }); * * const res = await client.query(`{ __typename }`); - * console.log(res.data()); + * console.log(res.data); * // Client automatically closed when block exits * ``` * diff --git a/packages/probitas-client-graphql/response.ts b/packages/probitas-client-graphql/response.ts index e5df9a5..2d1ec5e 100644 --- a/packages/probitas-client-graphql/response.ts +++ b/packages/probitas-client-graphql/response.ts @@ -57,16 +57,16 @@ interface GraphqlResponseBase extends ClientResult { readonly url: string; /** - * Get response data (null if no data or request failed). + * Response data (null if no data or request failed). * Does not throw even if errors are present. */ - data(): U | null; + readonly data: T | null; /** - * Get the raw Web standard Response object. - * Returns null for failure responses. + * Raw Web standard Response object. + * Null for failure responses. */ - raw(): globalThis.Response | null; + readonly raw: globalThis.Response | null; } /** @@ -88,7 +88,11 @@ export interface GraphqlResponseSuccess /** Response extensions. */ readonly extensions: Record | null; - raw(): globalThis.Response; + /** Raw Web standard Response. */ + readonly raw: globalThis.Response; + + /** Response data (null if no data). */ + readonly data: T | null; } /** @@ -112,7 +116,11 @@ export interface GraphqlResponseError extends GraphqlResponseBase { /** Response extensions. */ readonly extensions: Record | null; - raw(): globalThis.Response; + /** Raw Web standard Response. */ + readonly raw: globalThis.Response; + + /** Response data (null if no data, may be partial data with errors). */ + readonly data: T | null; } /** @@ -136,7 +144,11 @@ export interface GraphqlResponseFailure /** HTTP response headers (null for failures). */ readonly headers: null; - raw(): null; + /** No raw response (request didn't reach server). */ + readonly raw: null; + + /** No data (request didn't reach server). */ + readonly data: null; } /** @@ -206,26 +218,18 @@ export class GraphqlResponseSuccessImpl readonly status: number; readonly headers: Headers; - readonly #data: T | null; - readonly #raw: globalThis.Response; + readonly data: T | null; + readonly raw: globalThis.Response; constructor(params: GraphqlResponseSuccessParams) { this.url = params.url; - this.#data = params.data; - this.#raw = params.raw; + this.data = params.data; + this.raw = params.raw; this.extensions = params.extensions; this.duration = params.duration; this.status = params.status; this.headers = params.raw.headers; } - - data(): U | null { - return this.#data as U | null; - } - - raw(): globalThis.Response { - return this.#raw; - } } /** @@ -243,27 +247,19 @@ export class GraphqlResponseErrorImpl implements GraphqlResponseError { readonly status: number; readonly headers: Headers; - readonly #data: T | null; - readonly #raw: globalThis.Response; + readonly data: T | null; + readonly raw: globalThis.Response; constructor(params: GraphqlResponseErrorParams) { this.url = params.url; - this.#data = params.data; - this.#raw = params.raw; + this.data = params.data; + this.raw = params.raw; this.error = params.error; this.extensions = params.extensions; this.duration = params.duration; this.status = params.status; this.headers = params.raw.headers; } - - data(): U | null { - return this.#data as U | null; - } - - raw(): globalThis.Response { - return this.#raw; - } } /** @@ -281,18 +277,12 @@ export class GraphqlResponseFailureImpl readonly headers = null; readonly duration: number; readonly url: string; + readonly data = null; + readonly raw = null; constructor(params: GraphqlResponseFailureParams) { this.url = params.url; this.error = params.error; this.duration = params.duration; } - - data(): null { - return null; - } - - raw(): null { - return null; - } } diff --git a/packages/probitas-client-graphql/response_test.ts b/packages/probitas-client-graphql/response_test.ts index 0d905a6..3732b98 100644 --- a/packages/probitas-client-graphql/response_test.ts +++ b/packages/probitas-client-graphql/response_test.ts @@ -26,7 +26,7 @@ Deno.test("GraphqlResponseSuccessImpl", async (t) => { assertEquals(response.processed, true); assert(response.ok); assertEquals(response.error, null); - assertEquals(response.data(), { user: { id: 1, name: "John" } }); + assertEquals(response.data, { user: { id: 1, name: "John" } }); assertEquals(response.duration, 100); assertEquals(response.status, 200); assertEquals(response.url, "http://localhost:4000/graphql"); @@ -56,7 +56,7 @@ Deno.test("GraphqlResponseSuccessImpl", async (t) => { raw: rawResponse, }); - assertEquals(response.raw(), rawResponse); + assertEquals(response.raw, rawResponse); }); await t.step("includes headers from raw response", () => { @@ -76,7 +76,7 @@ Deno.test("GraphqlResponseSuccessImpl", async (t) => { assertEquals(response.headers.get("X-Custom-Header"), "test-value"); }); - await t.step("data() method returns typed data", () => { + await t.step("data supports type assertion", () => { interface User { id: number; name: string; @@ -90,7 +90,7 @@ Deno.test("GraphqlResponseSuccessImpl", async (t) => { raw: new Response(), }); - const result = response.data<{ user: User }>(); + const result = response.data as { user: User } | null; assertEquals(result?.user.id, 1); assertEquals(result?.user.name, "John"); }); @@ -115,7 +115,7 @@ Deno.test("GraphqlResponseErrorImpl", async (t) => { assertEquals(response.processed, true); assertFalse(response.ok); assertEquals(response.error, error); - assertEquals(response.data(), null); + assertEquals(response.data, null); assertEquals(response.status, 200); }); @@ -134,7 +134,7 @@ Deno.test("GraphqlResponseErrorImpl", async (t) => { }); assertFalse(response.ok); - assertEquals(response.data(), { user: { id: 1 }, posts: null }); + assertEquals(response.data, { user: { id: 1 }, posts: null }); }); await t.step("includes raw response", () => { @@ -152,7 +152,7 @@ Deno.test("GraphqlResponseErrorImpl", async (t) => { raw: rawResponse, }); - assertEquals(response.raw(), rawResponse); + assertEquals(response.raw, rawResponse); }); }); @@ -195,7 +195,7 @@ Deno.test("GraphqlResponseFailureImpl", async (t) => { assertEquals(response.headers, null); }); - await t.step("data() returns null", () => { + await t.step("data is null", () => { const error = new GraphqlNetworkError("Network error"); const response = new GraphqlResponseFailureImpl({ url: "http://localhost:4000/graphql", @@ -203,10 +203,10 @@ Deno.test("GraphqlResponseFailureImpl", async (t) => { duration: 5, }); - assertEquals(response.data(), null); + assertEquals(response.data, null); }); - await t.step("raw() returns null", () => { + await t.step("raw is null", () => { const error = new GraphqlNetworkError("Network error"); const response = new GraphqlResponseFailureImpl({ url: "http://localhost:4000/graphql", @@ -214,6 +214,6 @@ Deno.test("GraphqlResponseFailureImpl", async (t) => { duration: 5, }); - assertEquals(response.raw(), null); + assertEquals(response.raw, null); }); }); diff --git a/packages/probitas-client-grpc/integration_test.ts b/packages/probitas-client-grpc/integration_test.ts index 4bbd3ec..2999c3b 100644 --- a/packages/probitas-client-grpc/integration_test.ts +++ b/packages/probitas-client-grpc/integration_test.ts @@ -190,9 +190,9 @@ Deno.test({ assertEquals(response.ok, true); assertEquals(response.statusCode, 0); - assertExists(response.data()); + assertExists(response.data); - const data = response.data<{ message: string }>(); + const data = response.data as { message: string } | null; assertExists(data); assertEquals(data.message, "Hello gRPC!"); }); @@ -206,8 +206,8 @@ Deno.test({ assertEquals(response.ok, true); assertEquals(response.statusCode, 0); - assertExists(response.data()); - const data = response.data<{ message: string }>(); + assertExists(response.data); + const data = response.data as { message: string } | null; assertEquals(data?.message, "Test message"); assertLess(response.duration, 5000); }); @@ -374,7 +374,7 @@ Deno.test({ ) ) { assertEquals(response.ok, true); - messages.push(response.data()); + messages.push(response.data); } assertEquals(messages.length, 3); @@ -447,7 +447,7 @@ Deno.test({ assertEquals(response.ok, false); assertEquals(response.statusCode, 3); - assertEquals(response.data(), null); + assertEquals(response.data, null); }); }, }); diff --git a/packages/probitas-client-grpc/mod.ts b/packages/probitas-client-grpc/mod.ts index cc858b7..164c328 100644 --- a/packages/probitas-client-grpc/mod.ts +++ b/packages/probitas-client-grpc/mod.ts @@ -37,7 +37,7 @@ * "echo", * { message: "Hello!" } * ); - * console.log(response.data()); + * console.log(response.data); * * await client.close(); * ``` @@ -68,7 +68,7 @@ * await using client = createGrpcClient({ url: "http://localhost:50051" }); * * const res = await client.call("echo.EchoService", "echo", { message: "test" }); - * console.log(res.data()); + * console.log(res.data); * // Client automatically closed when block exits * ``` * @@ -154,7 +154,7 @@ export interface GrpcClientConfig * "echo", * { message: "Hello!" } * ); - * console.log(response.data()); + * console.log(response.data); * * await client.close(); * ``` @@ -207,7 +207,7 @@ export interface GrpcClientConfig * }); * * const res = await client.call("echo.EchoService", "echo", { message: "test" }); - * console.log(res.data()); + * console.log(res.data); * // Client automatically closed when scope exits * ``` */ diff --git a/packages/probitas-client-http/body.ts b/packages/probitas-client-http/body.ts index 7764790..1f0e8ef 100644 --- a/packages/probitas-client-http/body.ts +++ b/packages/probitas-client-http/body.ts @@ -8,8 +8,7 @@ export class HttpBody { readonly bytes: Uint8Array | null; readonly #headers: Headers | null; readonly #text: string | null; - readonly #json: unknown; - readonly #jsonError: Error | null; + readonly #json: unknown | null; constructor(bytes: Uint8Array | null, headers?: Headers | null) { this.bytes = bytes; @@ -18,21 +17,19 @@ export class HttpBody { // Decode text and parse JSON once during construction const text = bytes ? new TextDecoder().decode(bytes) : null; let json: unknown = null; - let jsonError: Error | null = null; if (text) { try { json = JSON.parse(text); - } catch (e) { - jsonError = e instanceof Error ? e : new Error(String(e)); + } catch { + // Ignore JSON parse errors } } this.#text = text; this.#json = json; - this.#jsonError = jsonError; } /** Get body as ArrayBuffer (null if no body) */ - arrayBuffer(): ArrayBuffer | null { + get arrayBuffer(): ArrayBuffer | null { if (this.bytes === null) { return null; } @@ -42,16 +39,16 @@ export class HttpBody { } /** Get body as Blob (null if no body) */ - blob(): Blob | null { + get blob(): Blob | null { if (this.bytes === null) { return null; } const contentType = this.#headers?.get("content-type") ?? ""; - return new Blob([this.arrayBuffer()!], { type: contentType }); + return new Blob([this.arrayBuffer!], { type: contentType }); } /** Get body as text (null if no body) */ - text(): string | null { + get text(): string | null { return this.#text; } @@ -59,11 +56,7 @@ export class HttpBody { * Get body as parsed JSON (null if no body). * @throws SyntaxError if body is not valid JSON */ - // deno-lint-ignore no-explicit-any - json(): T | null { - if (this.#jsonError) { - throw this.#jsonError; - } - return this.#json as T | null; + get json(): unknown { + return this.#json; } } diff --git a/packages/probitas-client-http/body_test.ts b/packages/probitas-client-http/body_test.ts index 14266a0..7edd558 100644 --- a/packages/probitas-client-http/body_test.ts +++ b/packages/probitas-client-http/body_test.ts @@ -1,4 +1,4 @@ -import { assertEquals, assertInstanceOf, assertThrows } from "@std/assert"; +import { assertEquals, assertInstanceOf } from "@std/assert"; import { HttpBody } from "./body.ts"; Deno.test("HttpBody", async (t) => { @@ -13,48 +13,48 @@ Deno.test("HttpBody", async (t) => { assertEquals(body.bytes, null); }); - await t.step("text() returns body as string", () => { + await t.step("text returns body as string", () => { const body = new HttpBody(new TextEncoder().encode("hello world")); - assertEquals(body.text(), "hello world"); + assertEquals(body.text, "hello world"); }); - await t.step("text() returns null when no body", () => { + await t.step("text returns null when no body", () => { const body = new HttpBody(null); - assertEquals(body.text(), null); + assertEquals(body.text, null); }); - await t.step("text() can be called multiple times", () => { + await t.step("text can be accessed multiple times", () => { const body = new HttpBody(new TextEncoder().encode("hello")); - assertEquals(body.text(), "hello"); - assertEquals(body.text(), "hello"); - assertEquals(body.text(), "hello"); + assertEquals(body.text, "hello"); + assertEquals(body.text, "hello"); + assertEquals(body.text, "hello"); }); - await t.step("json() returns parsed JSON", () => { + await t.step("json returns parsed JSON", () => { const data = { name: "John", age: 30 }; const body = new HttpBody(new TextEncoder().encode(JSON.stringify(data))); - assertEquals(body.json(), data); + assertEquals(body.json, data); }); - await t.step("json() returns null when no body", () => { + await t.step("json returns null when no body", () => { const body = new HttpBody(null); - assertEquals(body.json(), null); + assertEquals(body.json, null); }); - await t.step("json() throws when body is not valid JSON", () => { + await t.step("json returns null when body is not valid JSON", () => { const body = new HttpBody(new TextEncoder().encode("not json")); - assertThrows(() => body.json(), SyntaxError); + assertEquals(body.json, null); }); - await t.step("json() can be called multiple times", () => { + await t.step("json can be accessed multiple times", () => { const data = { id: 1 }; const body = new HttpBody(new TextEncoder().encode(JSON.stringify(data))); - assertEquals(body.json(), data); - assertEquals(body.json(), data); - assertEquals(body.json(), data); + assertEquals(body.json, data); + assertEquals(body.json, data); + assertEquals(body.json, data); }); - await t.step("json() supports generic type hint", () => { + await t.step("json supports type assertion", () => { interface User { id: number; name: string; @@ -62,47 +62,47 @@ Deno.test("HttpBody", async (t) => { const body = new HttpBody( new TextEncoder().encode(JSON.stringify({ id: 1, name: "Alice" })), ); - const user = body.json(); + const user = body.json as User | null; assertEquals(user?.id, 1); assertEquals(user?.name, "Alice"); }); - await t.step("arrayBuffer() returns ArrayBuffer", () => { + await t.step("arrayBuffer returns ArrayBuffer", () => { const bytes = new Uint8Array([1, 2, 3, 4, 5]); const body = new HttpBody(bytes); - const buffer = body.arrayBuffer(); + const buffer = body.arrayBuffer; assertInstanceOf(buffer, ArrayBuffer); assertEquals(new Uint8Array(buffer!), bytes); }); - await t.step("arrayBuffer() returns null when no body", () => { + await t.step("arrayBuffer returns null when no body", () => { const body = new HttpBody(null); - assertEquals(body.arrayBuffer(), null); + assertEquals(body.arrayBuffer, null); }); - await t.step("blob() returns Blob", async () => { + await t.step("blob returns Blob", async () => { const body = new HttpBody(new TextEncoder().encode("hello")); - const blob = body.blob(); + const blob = body.blob; assertInstanceOf(blob, Blob); assertEquals(await blob!.text(), "hello"); }); await t.step( - "blob() returns Blob with content type from headers", + "blob returns Blob with content type from headers", async () => { const headers = new Headers({ "content-type": "application/json" }); const body = new HttpBody( new TextEncoder().encode('{"a":1}'), headers, ); - const blob = body.blob(); + const blob = body.blob; assertEquals(blob!.type, "application/json"); assertEquals(await blob!.text(), '{"a":1}'); }, ); - await t.step("blob() returns null when no body", () => { + await t.step("blob returns null when no body", () => { const body = new HttpBody(null); - assertEquals(body.blob(), null); + assertEquals(body.blob, null); }); }); diff --git a/packages/probitas-client-http/client.ts b/packages/probitas-client-http/client.ts index ffaea6c..b49315c 100644 --- a/packages/probitas-client-http/client.ts +++ b/packages/probitas-client-http/client.ts @@ -394,7 +394,7 @@ class HttpClientImpl implements HttpClient { * const http = createHttpClient({ url: "http://localhost:3000" }); * * const response = await http.get("/users/123"); - * console.log(response.json()); + * console.log(response.json); * * await http.close(); * ``` diff --git a/packages/probitas-client-http/client_test.ts b/packages/probitas-client-http/client_test.ts index 0979fae..9413c29 100644 --- a/packages/probitas-client-http/client_test.ts +++ b/packages/probitas-client-http/client_test.ts @@ -75,7 +75,7 @@ Deno.test("HttpClient.get", async (t) => { assert(response.ok); assertEquals(response.status, 200); - assertEquals(response.json(), { id: 1, name: "John" }); + assertEquals(response.json, { id: 1, name: "John" }); }); await t.step("includes query parameters", async () => { @@ -938,11 +938,11 @@ Deno.test("HttpClient network failure handling", async (t) => { await client.close(); assert(!response.processed); - assertEquals(response.raw(), null); - assertEquals(response.arrayBuffer(), null); - assertEquals(response.blob(), null); - assertEquals(response.text(), null); - assertEquals(response.json(), null); + assertEquals(response.raw, null); + assertEquals(response.arrayBuffer, null); + assertEquals(response.blob, null); + assertEquals(response.text, null); + assertEquals(response.json, null); }); await t.step( diff --git a/packages/probitas-client-http/errors.ts b/packages/probitas-client-http/errors.ts index 71f7d11..9aba4cc 100644 --- a/packages/probitas-client-http/errors.ts +++ b/packages/probitas-client-http/errors.ts @@ -10,14 +10,14 @@ function formatErrorMessage( statusText: string, body: HttpBody, ): string { - const text = body.text(); + const text = body.text; if (text === null) { return `${status}: ${statusText}`; } // Try to parse as JSON for pretty-printing, otherwise use text as-is let detail: string; try { - const json = body.json(); + const json = body.json; detail = Deno.inspect(json, { compact: false, sorted: true, @@ -59,7 +59,7 @@ export interface HttpErrorOptions extends ErrorOptions { * await http.get("/not-found"); * } catch (error) { * if (error instanceof HttpError && error.status === 404) { - * console.log("Not found:", error.text()); + * console.log("Not found:", error.text); * } * } * ``` @@ -100,28 +100,26 @@ export class HttpError extends ClientError { } /** Get body as ArrayBuffer (null if no body) */ - arrayBuffer(): ArrayBuffer | null { - return this.#body.arrayBuffer(); + get arrayBuffer(): ArrayBuffer | null { + return this.#body.arrayBuffer; } /** Get body as Blob (null if no body) */ - blob(): Blob | null { - return this.#body.blob(); + get blob(): Blob | null { + return this.#body.blob; } /** Get body as text (null if no body) */ - text(): string | null { - return this.#body.text(); + get text(): string | null { + return this.#body.text; } /** * Get body as parsed JSON (null if no body). - * @template T - defaults to any for test convenience * @throws SyntaxError if body is not valid JSON */ - // deno-lint-ignore no-explicit-any - json(): T | null { - return this.#body.json(); + get json(): unknown { + return this.#body.json; } } diff --git a/packages/probitas-client-http/errors_test.ts b/packages/probitas-client-http/errors_test.ts index d8a2d02..e6d1b50 100644 --- a/packages/probitas-client-http/errors_test.ts +++ b/packages/probitas-client-http/errors_test.ts @@ -1,4 +1,4 @@ -import { assertEquals, assertInstanceOf, assertThrows } from "@std/assert"; +import { assertEquals, assertInstanceOf } from "@std/assert"; import { ClientError } from "@probitas/client"; import { HttpError } from "./errors.ts"; @@ -56,83 +56,83 @@ Deno.test("HttpError", async (t) => { await t.step("text() returns body as string", () => { const body = new TextEncoder().encode("error message"); const error = new HttpError(500, "Internal Server Error", { body }); - assertEquals(error.text(), "error message"); + assertEquals(error.text, "error message"); }); await t.step("text() returns null when no body", () => { const error = new HttpError(500, "Internal Server Error"); - assertEquals(error.text(), null); + assertEquals(error.text, null); }); await t.step("text() can be called multiple times", () => { const body = new TextEncoder().encode("error message"); const error = new HttpError(500, "Internal Server Error", { body }); - assertEquals(error.text(), error.text()); + assertEquals(error.text, error.text); }); - await t.step("json() returns parsed JSON", () => { + await t.step("json returns parsed JSON", () => { const body = new TextEncoder().encode('{"code": "NOT_FOUND", "id": 123}'); const error = new HttpError(404, "Not Found", { body }); - assertEquals(error.json(), { code: "NOT_FOUND", id: 123 }); + assertEquals(error.json, { code: "NOT_FOUND", id: 123 }); }); - await t.step("json() returns null when no body", () => { + await t.step("json returns null when no body", () => { const error = new HttpError(500, "Internal Server Error"); - assertEquals(error.json(), null); + assertEquals(error.json, null); }); - await t.step("json() throws when body is not valid JSON", () => { + await t.step("json returns null when body is not valid JSON", () => { const body = new TextEncoder().encode("not json"); const error = new HttpError(500, "Internal Server Error", { body }); - assertThrows(() => error.json(), SyntaxError); + assertEquals(error.json, null); }); - await t.step("json() can be called multiple times", () => { + await t.step("json can be accessed multiple times", () => { const body = new TextEncoder().encode('{"error": true}'); const error = new HttpError(500, "Internal Server Error", { body }); - assertEquals(error.json(), error.json()); + assertEquals(error.json, error.json); }); - await t.step("json() supports generic type hint", () => { + await t.step("json supports type assertion", () => { interface ErrorResponse { code: string; message: string; } const body = new TextEncoder().encode('{"code": "ERR", "message": "fail"}'); const error = new HttpError(500, "Internal Server Error", { body }); - const data = error.json(); + const data = error.json as ErrorResponse; assertEquals(data?.code, "ERR"); assertEquals(data?.message, "fail"); }); - await t.step("arrayBuffer() returns ArrayBuffer", () => { + await t.step("arrayBuffer returns ArrayBuffer", () => { const body = new TextEncoder().encode("data"); const error = new HttpError(500, "Internal Server Error", { body }); - const buffer = error.arrayBuffer(); + const buffer = error.arrayBuffer; assertInstanceOf(buffer, ArrayBuffer); assertEquals(buffer?.byteLength, 4); }); - await t.step("arrayBuffer() returns null when no body", () => { + await t.step("arrayBuffer returns null when no body", () => { const error = new HttpError(500, "Internal Server Error"); - assertEquals(error.arrayBuffer(), null); + assertEquals(error.arrayBuffer, null); }); - await t.step("blob() returns Blob with content type", () => { + await t.step("blob returns Blob with content type", () => { const body = new TextEncoder().encode('{"test": true}'); const headers = new Headers({ "Content-Type": "application/json" }); const error = new HttpError(500, "Internal Server Error", { body, headers, }); - const blob = error.blob(); + const blob = error.blob; assertInstanceOf(blob, Blob); assertEquals(blob?.type, "application/json"); assertEquals(blob?.size, body.length); }); - await t.step("blob() returns null when no body", () => { + await t.step("blob returns null when no body", () => { const error = new HttpError(500, "Internal Server Error"); - assertEquals(error.blob(), null); + assertEquals(error.blob, null); }); }); diff --git a/packages/probitas-client-http/integration_test.ts b/packages/probitas-client-http/integration_test.ts index 2d3e772..00940c8 100644 --- a/packages/probitas-client-http/integration_test.ts +++ b/packages/probitas-client-http/integration_test.ts @@ -46,11 +46,11 @@ Deno.test({ assertEquals(res.status, 200); assert(res.headers.get("content-type")?.match(/^application\/json/)); - const data = res.json<{ + const data = res.json as { args: Record; headers: Record; url: string; - }>(); + } | null; assertEquals(data?.args.foo, "bar"); assertEquals(data?.args.num, "42"); @@ -66,10 +66,10 @@ Deno.test({ assertEquals(res.status, 200); assert(res.headers.get("content-type")?.match(/^application\/json/)); - const data = res.json<{ + const data = res.json as { json: typeof payload; headers: Record; - }>(); + } | null; assertEquals(data?.json, payload); assertEquals(data?.headers["Content-Type"], "application/json"); @@ -85,7 +85,7 @@ Deno.test({ assert(res.ok); assertEquals(res.status, 200); - const data = res.json<{ form: Record }>(); + const data = res.json as { form: Record } | null; assertEquals(data?.form.username, "alice"); assertEquals(data?.form.password, "secret"); }); @@ -96,7 +96,7 @@ Deno.test({ assert(res.ok); assertEquals(res.status, 200); - const data = res.json<{ json: { updated: boolean } }>(); + const data = res.json as { json: { updated: boolean } }; assertEquals(data?.json.updated, true); }); @@ -106,7 +106,7 @@ Deno.test({ assert(res.ok); assertEquals(res.status, 200); - const data = res.json<{ json: { patched: string } }>(); + const data = res.json as { json: { patched: string } }; assertEquals(data?.json.patched, "value"); }); @@ -151,7 +151,7 @@ Deno.test({ assert(res.ok); - const data = res.json<{ headers: Record }>(); + const data = res.json as { headers: Record } | null; // Verify Accept header was sent (echo-http echoes back headers) assertEquals(data?.headers["Accept"], "application/json"); }); @@ -172,13 +172,13 @@ Deno.test({ const res = await client.get("/get"); // Read as text - const text1 = res.text(); - const text2 = res.text(); + const text1 = res.text; + const text2 = res.text; assertEquals(text1, text2); // Read as JSON - const json1 = res.json(); - const json2 = res.json(); + const json1 = res.json; + const json2 = res.json; assertEquals(json1, json2); // Body bytes are also available @@ -195,7 +195,7 @@ Deno.test({ }); const res = await clientWithHeaders.get("/headers"); - const data = res.json<{ headers: Record }>(); + const data = res.json as { headers: Record } | null; assertEquals(data?.headers["Authorization"], "Bearer token123"); assertEquals(data?.headers["X-Api-Version"], "v1"); @@ -212,7 +212,7 @@ Deno.test({ const res = await clientWithHeaders.get("/headers", { headers: { "X-Header": "from-request" }, }); - const data = res.json<{ headers: Record }>(); + const data = res.json as { headers: Record } | null; assertEquals(data?.headers["X-Header"], "from-request"); @@ -225,7 +225,7 @@ Deno.test({ assert(res.ok); assertEquals(res.status, 200); - const data = res.json<{ redirected: boolean }>(); + const data = res.json as { redirected: boolean }; assertEquals(data?.redirected, true); }); @@ -301,7 +301,7 @@ Deno.test({ const res = await client.get("/cookies"); assert(res.ok); - const data = res.json<{ cookies: Record }>(); + const data = res.json as { cookies: Record } | null; assertEquals(data?.cookies.session, "test123"); assertEquals(data?.cookies.user, "alice"); @@ -343,7 +343,7 @@ Deno.test({ // Second request should send the cookie back const res = await client.get("/cookies"); - const data = res.json<{ cookies: Record }>(); + const data = res.json as { cookies: Record } | null; assertEquals(data?.cookies.auth, "bearer-token"); await client.close(); @@ -357,7 +357,7 @@ Deno.test({ // Verify initial cookie is sent let res = await client.get("/cookies"); - let data = res.json<{ cookies: Record }>(); + let data = res.json as { cookies: Record } | null; assertEquals(data?.cookies.initial, "value"); // Clear cookies @@ -365,7 +365,7 @@ Deno.test({ // Verify no cookies are sent res = await client.get("/cookies"); - data = res.json<{ cookies: Record }>(); + data = res.json as { cookies: Record } | null; assertEquals(data?.cookies.initial, undefined); await client.close(); @@ -381,7 +381,7 @@ Deno.test({ // Verify it's sent const res = await client.get("/cookies"); - const data = res.json<{ cookies: Record }>(); + const data = res.json as { cookies: Record } | null; assertEquals(data?.cookies.manual, "cookie-value"); await client.close(); diff --git a/packages/probitas-client-http/mod.ts b/packages/probitas-client-http/mod.ts index 5f78c01..e39eed4 100644 --- a/packages/probitas-client-http/mod.ts +++ b/packages/probitas-client-http/mod.ts @@ -34,7 +34,7 @@ * console.log("Status:", res.status); * * // Extract typed data - * const user = res.json(); + * const user = res.json as User; * * // POST request * const created = await http.post("/users", { diff --git a/packages/probitas-client-http/response.ts b/packages/probitas-client-http/response.ts index 46f3013..e8fae19 100644 --- a/packages/probitas-client-http/response.ts +++ b/packages/probitas-client-http/response.ts @@ -7,7 +7,8 @@ import { HttpError, type HttpFailureError } from "./errors.ts"; * * Provides common properties and methods shared by Success, Error, and Failure responses. */ -interface HttpResponseBase extends ClientResult { +// deno-lint-ignore no-explicit-any +interface HttpResponseBase extends ClientResult { /** Result kind discriminator. Always `"http"` for HTTP responses. */ readonly kind: "http"; @@ -30,26 +31,24 @@ interface HttpResponseBase extends ClientResult { readonly body: Uint8Array | null; /** Get body as ArrayBuffer (null if no body or failure). */ - arrayBuffer(): ArrayBuffer | null; + readonly arrayBuffer: ArrayBuffer | null; /** Get body as Blob (null if no body or failure). */ - blob(): Blob | null; + readonly blob: Blob | null; /** * Get body as text (null if no body or failure). */ - text(): string | null; + readonly text: string | null; /** * Get body as parsed JSON (null if no body or failure). - * @template T - defaults to any for test convenience * @throws SyntaxError if body is not valid JSON */ - // deno-lint-ignore no-explicit-any - json(): T | null; + readonly json: T | null; - /** Get raw Web standard Response (null for failure). */ - raw(): globalThis.Response | null; + /** Raw Web standard Response (null for failure). */ + readonly raw: globalThis.Response | null; } /** @@ -58,7 +57,8 @@ interface HttpResponseBase extends ClientResult { * Wraps Web standard Response, allowing body to be read synchronously * and multiple times (unlike the streaming-based standard Response). */ -export interface HttpResponseSuccess extends HttpResponseBase { +// deno-lint-ignore no-explicit-any +export interface HttpResponseSuccess extends HttpResponseBase { /** Server processed the request. */ readonly processed: true; @@ -77,8 +77,8 @@ export interface HttpResponseSuccess extends HttpResponseBase { /** Response headers. */ readonly headers: Headers; - /** Get raw Web standard Response. */ - raw(): globalThis.Response; + /** Raw Web standard Response. */ + readonly raw: globalThis.Response; } /** @@ -86,7 +86,8 @@ export interface HttpResponseSuccess extends HttpResponseBase { * * Server received and processed the request, but returned an error status. */ -export interface HttpResponseError extends HttpResponseBase { +// deno-lint-ignore no-explicit-any +export interface HttpResponseError extends HttpResponseBase { /** Server processed the request. */ readonly processed: true; @@ -105,8 +106,8 @@ export interface HttpResponseError extends HttpResponseBase { /** Response headers. */ readonly headers: Headers; - /** Get raw Web standard Response. */ - raw(): globalThis.Response; + /** Raw Web standard Response. */ + readonly raw: globalThis.Response; } /** @@ -115,7 +116,8 @@ export interface HttpResponseError extends HttpResponseBase { * Request could not be processed by the server (network error, DNS failure, * connection refused, timeout, aborted, etc.). */ -export interface HttpResponseFailure extends HttpResponseBase { +// deno-lint-ignore no-explicit-any +export interface HttpResponseFailure extends HttpResponseBase { /** Server did not process the request. */ readonly processed: false; @@ -138,7 +140,7 @@ export interface HttpResponseFailure extends HttpResponseBase { readonly body: null; /** No raw response (request didn't reach server). */ - raw(): null; + readonly raw: null; } /** @@ -178,16 +180,19 @@ export interface HttpResponseFailure extends HttpResponseBase { * } * ``` */ -export type HttpResponse = - | HttpResponseSuccess - | HttpResponseError - | HttpResponseFailure; +// deno-lint-ignore no-explicit-any +export type HttpResponse = + | HttpResponseSuccess + | HttpResponseError + | HttpResponseFailure; /** * Implementation of HttpResponseSuccess. * @internal */ -export class HttpResponseSuccessImpl implements HttpResponseSuccess { +// deno-lint-ignore no-explicit-any +export class HttpResponseSuccessImpl + implements HttpResponseSuccess { readonly kind = "http" as const; readonly processed = true as const; readonly ok = true as const; @@ -219,24 +224,23 @@ export class HttpResponseSuccessImpl implements HttpResponseSuccess { return this.#body.bytes; } - arrayBuffer(): ArrayBuffer | null { - return this.#body.arrayBuffer(); + get arrayBuffer(): ArrayBuffer | null { + return this.#body.arrayBuffer; } - blob(): Blob | null { - return this.#body.blob(); + get blob(): Blob | null { + return this.#body.blob; } - text(): string | null { - return this.#body.text(); + get text(): string | null { + return this.#body.text; } - // deno-lint-ignore no-explicit-any - json(): T | null { - return this.#body.json(); + get json(): T | null { + return this.#body.json as T | null; } - raw(): globalThis.Response { + get raw(): globalThis.Response { return this.#raw; } } @@ -246,7 +250,8 @@ export class HttpResponseSuccessImpl implements HttpResponseSuccess { * Proxies body methods to the HttpError instance. * @internal */ -export class HttpResponseErrorImpl implements HttpResponseError { +// deno-lint-ignore no-explicit-any +export class HttpResponseErrorImpl implements HttpResponseError { readonly kind = "http" as const; readonly processed = true as const; readonly ok = false as const; @@ -278,24 +283,23 @@ export class HttpResponseErrorImpl implements HttpResponseError { return this.error.body; } - arrayBuffer(): ArrayBuffer | null { - return this.error.arrayBuffer(); + get arrayBuffer(): ArrayBuffer | null { + return this.error.arrayBuffer; } - blob(): Blob | null { - return this.error.blob(); + get blob(): Blob | null { + return this.error.blob; } - text(): string | null { - return this.error.text(); + get text(): string | null { + return this.error.text; } - // deno-lint-ignore no-explicit-any - json(): T | null { - return this.error.json(); + get json(): T | null { + return this.error.json as T | null; } - raw(): globalThis.Response { + get raw(): globalThis.Response { return this.#raw; } } @@ -304,7 +308,9 @@ export class HttpResponseErrorImpl implements HttpResponseError { * Implementation of HttpResponseFailure. * @internal */ -export class HttpResponseFailureImpl implements HttpResponseFailure { +// deno-lint-ignore no-explicit-any +export class HttpResponseFailureImpl + implements HttpResponseFailure { readonly kind = "http" as const; readonly processed = false as const; readonly ok = false as const; @@ -316,30 +322,15 @@ export class HttpResponseFailureImpl implements HttpResponseFailure { readonly body = null; readonly duration: number; + readonly arrayBuffer = null; + readonly blob = null; + readonly text = null; + readonly json = null; + readonly raw = null; + constructor(url: string, duration: number, error: HttpFailureError) { this.url = url; this.duration = duration; this.error = error; } - - arrayBuffer(): null { - return null; - } - - blob(): null { - return null; - } - - text(): null { - return null; - } - - // deno-lint-ignore no-explicit-any - json(): T | null { - return null; - } - - raw(): null { - return null; - } } diff --git a/packages/probitas-client-http/response_test.ts b/packages/probitas-client-http/response_test.ts index f58d2e3..df0cd4b 100644 --- a/packages/probitas-client-http/response_test.ts +++ b/packages/probitas-client-http/response_test.ts @@ -88,51 +88,51 @@ Deno.test("HttpResponseSuccessImpl", async (t) => { const raw = new Response("hello world", { status: 200 }); const response = await createSuccessResponse(raw, 10); - assertEquals(response.text(), "hello world"); + assertEquals(response.text, "hello world"); }); await t.step("text() returns null when no body", async () => { const raw = new Response(null, { status: 204 }); const response = await createSuccessResponse(raw, 10); - assertEquals(response.text(), null); + assertEquals(response.text, null); }); await t.step("text() can be called multiple times", async () => { const raw = new Response("hello", { status: 200 }); const response = await createSuccessResponse(raw, 10); - assertEquals(response.text(), "hello"); - assertEquals(response.text(), "hello"); - assertEquals(response.text(), "hello"); + assertEquals(response.text, "hello"); + assertEquals(response.text, "hello"); + assertEquals(response.text, "hello"); }); - await t.step("json() returns parsed JSON", async () => { + await t.step("json returns parsed JSON", async () => { const data = { name: "John", age: 30 }; const raw = new Response(JSON.stringify(data), { status: 200 }); const response = await createSuccessResponse(raw, 10); - assertEquals(response.json(), data); + assertEquals(response.json, data); }); - await t.step("json() returns null when no body", async () => { + await t.step("json returns null when no body", async () => { const raw = new Response(null, { status: 204 }); const response = await createSuccessResponse(raw, 10); - assertEquals(response.json(), null); + assertEquals(response.json, null); }); - await t.step("json() can be called multiple times", async () => { + await t.step("json can be accessed multiple times", async () => { const data = { id: 1 }; const raw = new Response(JSON.stringify(data), { status: 200 }); const response = await createSuccessResponse(raw, 10); - assertEquals(response.json(), data); - assertEquals(response.json(), data); - assertEquals(response.json(), data); + assertEquals(response.json, data); + assertEquals(response.json, data); + assertEquals(response.json, data); }); - await t.step("json() supports generic type hint", async () => { + await t.step("json supports type assertion", async () => { interface User { id: number; name: string; @@ -142,52 +142,52 @@ Deno.test("HttpResponseSuccessImpl", async (t) => { }); const response = await createSuccessResponse(raw, 10); - const user = response.json(); + const user = response.json as User; assertEquals(user?.id, 1); assertEquals(user?.name, "Alice"); }); - await t.step("arrayBuffer() returns ArrayBuffer", async () => { + await t.step("arrayBuffer returns ArrayBuffer", async () => { const bytes = new Uint8Array([1, 2, 3, 4, 5]); const raw = new Response(bytes, { status: 200 }); const response = await createSuccessResponse(raw, 10); - const buffer = response.arrayBuffer(); + const buffer = response.arrayBuffer; assertInstanceOf(buffer, ArrayBuffer); assertEquals(new Uint8Array(buffer!), bytes); }); - await t.step("arrayBuffer() returns null when no body", async () => { + await t.step("arrayBuffer returns null when no body", async () => { const raw = new Response(null, { status: 204 }); const response = await createSuccessResponse(raw, 10); - assertEquals(response.arrayBuffer(), null); + assertEquals(response.arrayBuffer, null); }); - await t.step("blob() returns Blob", async () => { + await t.step("blob returns Blob", async () => { const raw = new Response("hello", { status: 200, headers: { "Content-Type": "text/plain" }, }); const response = await createSuccessResponse(raw, 10); - const blob = response.blob(); + const blob = response.blob; assertInstanceOf(blob, Blob); assertEquals(await blob!.text(), "hello"); }); - await t.step("blob() returns null when no body", async () => { + await t.step("blob returns null when no body", async () => { const raw = new Response(null, { status: 204 }); const response = await createSuccessResponse(raw, 10); - assertEquals(response.blob(), null); + assertEquals(response.blob, null); }); - await t.step("raw() method returns original Response", async () => { + await t.step("raw returns original Response", async () => { const original = new Response("test", { status: 200 }); const response = await createSuccessResponse(original, 10); - assertEquals(response.raw(), original); + assertEquals(response.raw, original); }); await t.step("url property reflects Response url", async () => { @@ -235,15 +235,15 @@ Deno.test("HttpResponseErrorImpl", async (t) => { }); const response = await createErrorResponse(raw, 10); - assertEquals(response.text(), errorBody); - assertEquals(response.json(), { error: "not found" }); + assertEquals(response.text, errorBody); + assertEquals(response.json, { error: "not found" }); assertInstanceOf(response.body, Uint8Array); }); - await t.step("raw() returns original Response", async () => { + await t.step("raw returns original Response", async () => { const original = new Response("error", { status: 500 }); const response = await createErrorResponse(original, 10); - assertEquals(response.raw(), original); + assertEquals(response.raw, original); }); });