Skip to content

Conversation

@myparsleycat
Copy link

@myparsleycat myparsleycat commented Jan 3, 2026

This PR adds a .url() method, allowing you to easily retrieve the full URL string for a route.

const client = treaty(app)

// Basic usage
const url = client.ping.url()

// With chaining and query
const userUrl = client.user({ id: 1 }).profile.url(null, { 
    query: { detail: 'full' } 
})
// Result: "http://domain/user/1/profile?detail=full"

@coderabbitai
Copy link

coderabbitai bot commented Jan 3, 2026

Walkthrough

This PR introduces a URL-building capability to the Treaty 2 proxy system. The implementation adds a url property that constructs full URLs by combining the current path, domain, and optional query parameters, along with corresponding type definitions and test coverage.

Changes

Cohort / File(s) Summary
URL Builder Implementation
src/treaty2/index.ts
Adds url method to proxy that constructs full URLs by concatenating adjusted path segments with domain and encoding query parameters (handles arrays, objects as JSON, skips undefined/null values).
Type Definitions
src/treaty2/types.ts
Extends Sign type to include url member alongside route methods; adds dedicated URL builder signature (options?: { query?: ... }) => string for each route key; refines parameter optionality on non-subscribe route branches.
URL Generation Tests
test/treaty2.test.ts
Adds test suite validating URL construction for base routes, parameterized routes (path variables), routes with query parameters, and chained method calls (e.g., .get.url()).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A URL springs forth from query's dance,
With paths and domains all enhanced,
No question marks left to chance,
The rabbit hops—our builders prance!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'add .url() method to retrieve full route URLs' directly and accurately summarizes the main change: introducing a new .url() method for Treaty clients to get full route URLs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
test/treaty2.test.ts (1)

1226-1257: Good test coverage for the new url() method.

Tests cover key scenarios: base URL, path parameters, query parameters, and accessing url() from method proxies. Consider adding edge case tests for:

  1. Query parameters with special characters (encoding)
  2. Query parameters with arrays
  3. Query parameters with null/undefined values (should be skipped per implementation)
🔎 Optional: Additional edge case tests
it('should encode special characters in query parameters', () => {
    expect(client.data.url({ query: { name: 'hello world' } })).toBe('http://e.ly/data?name=hello%20world')
})

it('should handle array query parameters', () => {
    expect(client.data.url({ query: { name: ['a', 'b'] } })).toBe('http://e.ly/data?name=a&name=b')
})

it('should skip null and undefined query values', () => {
    expect(client.data.url({ query: { name: undefined } })).toBe('http://e.ly/data')
})
src/treaty2/index.ts (1)

207-245: Implementation looks correct; consider extracting shared query-building logic.

The url property correctly:

  1. Strips HTTP method names from the path when accessed via method proxy
  2. Handles query parameter encoding with proper support for arrays, objects, and null/undefined skipping

However, the query string building logic (lines 217-241) is duplicated from the apply handler (lines 283-309). Consider extracting to a shared helper.

🔎 Proposed refactor to extract query building
+const buildQueryString = (query: Record<string, any> | undefined): string => {
+    if (!query) return ''
+    
+    let q = ''
+    const append = (key: string, value: string) => {
+        q +=
+            (q ? '&' : '?') +
+            `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
+    }
+
+    for (const [key, value] of Object.entries(query)) {
+        if (Array.isArray(value)) {
+            for (const v of value) append(key, v)
+            continue
+        }
+
+        if (value === undefined || value === null) continue
+
+        if (typeof value === 'object') {
+            append(key, JSON.stringify(value))
+            continue
+        }
+        append(key, `${value}`)
+    }
+    
+    return q
+}

 const createProxy = (
     // ...
 ): any =>
     new Proxy(() => {}, {
         get(_, param: string): any {
             if (param === 'url') {
                 return (options?: { query?: Record<string, any> }) => {
                     const methodPaths = [...paths]
                     if (method.includes(methodPaths.at(-1) as any))
                         methodPaths.pop()

                     const path = '/' + methodPaths.join('/')
-
-                    const query = options?.query
-
-                    let q = ''
-                    if (query) {
-                        const append = (key: string, value: string) => {
-                            q +=
-                                (q ? '&' : '?') +
-                                `${encodeURIComponent(key)}=${encodeURIComponent(
-                                    value
-                                )}`
-                        }
-
-                        for (const [key, value] of Object.entries(query)) {
-                            if (Array.isArray(value)) {
-                                for (const v of value) append(key, v)
-                                continue
-                            }
-
-                            if (value === undefined || value === null) continue
-
-                            if (typeof value === 'object') {
-                                append(key, JSON.stringify(value))
-                                continue
-                            }
-                            append(key, `${value}`)
-                        }
-                    }
+                    const q = buildQueryString(options?.query)

                     return domain + path + q
                 }
             }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 675e61a and dbc2bd0.

📒 Files selected for processing (3)
  • src/treaty2/index.ts
  • src/treaty2/types.ts
  • test/treaty2.test.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/treaty2/types.ts (3)
src/types.ts (2)
  • MaybeEmptyObject (64-78)
  • Prettify (88-90)
src/treaty2/ws.ts (1)
  • EdenWS (5-91)
src/treaty/index.ts (1)
  • EdenWS (59-147)
🔇 Additional comments (3)
src/treaty2/types.ts (2)

97-114: LGTM! Type extension for url property on routes.

The type correctly:

  1. Adds 'url' to the mapped type keys while filtering out dynamic params (:${string})
  2. Provides a standalone url signature with loose Record<string, any> query typing
  3. Attaches a typed url builder to the subscribe route using SerializeQueryParams for query type safety

115-187: LGTM! Type extension for HTTP routes with url builder.

The type correctly intersects route method signatures with a url builder that uses SerializeQueryParams<Query> for type-safe query parameters, consistent with the route's query schema.

src/treaty2/index.ts (1)

681-681: LGTM!

Standard type re-export.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant