Skip to content

Commit 01e66aa

Browse files
committed
feat: update constructor parameters to use context object for Resource and ResourceCollection, enhancing URL detection and auto-send capabilities
1 parent e3a2645 commit 01e66aa

File tree

6 files changed

+155
-20
lines changed

6 files changed

+155
-20
lines changed

docs/api/generic-resource.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,16 @@ Flexible resource wrapper for handling:
1010
## Constructor
1111

1212
```ts
13-
new GenericResource(resource, response?)
13+
new GenericResource(resource, context?)
1414
```
1515
16+
### Parameters
17+
18+
| Parameter | Type | Description |
19+
| --------- | ------------------------------------- | -------------------------------------------- |
20+
| resource | ResourceData | Data to transform |
21+
| context | `{ req, res }` \| Response (optional) | HTTP context for auto-send and URL detection |
22+
1623
---
1724
1825
## Key Differences

docs/api/resource-collection.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,16 @@ Supports:
1313
## Constructor
1414

1515
```ts
16-
new ResourceCollection(resource, response?)
16+
new ResourceCollection(resource, context?)
1717
```
1818
19+
### Parameters
20+
21+
| Parameter | Type | Description |
22+
| --------- | ------------------------------------- | -------------------------------------------- |
23+
| resource | ResourceData[] \| Collectible | Data to transform |
24+
| context | `{ req, res }` \| Response (optional) | HTTP context for auto-send and URL detection |
25+
1926
## Mapping Items
2027
2128
You can define a resource transformer:

docs/api/resource.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ Handles transformation of a single resource.
55
## Constructor
66

77
```ts
8-
new Resource(resource, response?)
8+
new Resource(resource, context?)
99
```
1010
1111
### Parameters
1212
13-
| Parameter | Type | Description |
14-
| --------- | --------------------------- | ----------------- |
15-
| resource | ResourceData | Data to transform |
16-
| response | Express Response (optional) | Auto-send support |
13+
| Parameter | Type | Description |
14+
| --------- | ------------------------------------- | -------------------------------------------- |
15+
| resource | ResourceData | Data to transform |
16+
| context | `{ req, res }` \| Response (optional) | HTTP context for auto-send and URL detection |
1717
1818
---
1919

docs/guide/getting-started.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,24 @@ app.get('/:id', async () => {
4141
});
4242
```
4343

44-
For Express and other framewords implementing Connect-style middleware, you will have to pass the response object into the contructor
44+
For Express and other frameworks implementing Connect-style middleware, pass the `{ req, res }` context into the constructor. This enables both auto-send and [automatic URL detection](/guide/pagination-cursor-recipes#url-detection) for pagination links.
4545

4646
```ts
4747
import { Resource } from 'resora';
4848

4949
app.get('/:id', async (req, res) => {
5050
const user = { id: req.params.id, name: 'John Doe' };
5151

52-
return await new Resource(user, res).additional({ status: 'success' });
52+
return await new Resource(user, { req, res }).additional({
53+
status: 'success',
54+
});
5355
});
5456
```
5557

58+
::: tip
59+
Passing just `res` still works for backward compatibility, but won't enable URL detection.
60+
:::
61+
5662
### Collection
5763

5864
`ResourceCollection` handles arrays and paginated datasets.
@@ -70,7 +76,7 @@ app.get('/', async () => {
7076
});
7177
```
7278

73-
You will also have to pass the response object into the contructor for Express and other framewords implementing Connect-style middleware.
79+
You can also pass the `{ req, res }` context into the constructor for Express and other frameworks implementing Connect-style middleware.
7480

7581
```ts
7682
import { ResourceCollection } from 'resora';
@@ -81,7 +87,7 @@ app.get('/', async (req, res) => {
8187
{ id: 2, name: 'Jane Doe' },
8288
];
8389

84-
return await new ResourceCollection(users, res);
90+
return await new ResourceCollection(users, { req, res });
8591
});
8692
```
8793

docs/guide/pagination-cursor-recipes.md

Lines changed: 120 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ import { defineConfig } from 'resora';
99

1010
export default defineConfig({
1111
paginatedExtras: ['meta', 'links'],
12-
baseUrl: 'https://localhost',
12+
baseUrl: '',
1313
pageName: 'page',
1414
});
1515
```
1616

17-
When `pagination.path` is available on a collection resource, links are generated as absolute URLs.
17+
When `pagination.path` is available on a collection resource, links are generated as path-relative URLs. If a `baseUrl` is configured or the request URL is [auto-detected from context](#url-detection), links become absolute.
1818

1919
Example:
2020

@@ -27,8 +27,8 @@ Example:
2727
"path": "/users"
2828
},
2929
"links": {
30-
"prev": "https://localhost/users?page=1",
31-
"next": "https://localhost/users?page=3"
30+
"prev": "/users?page=1",
31+
"next": "/users?page=3"
3232
}
3333
}
3434
```
@@ -142,8 +142,8 @@ Example output:
142142
"endpoint": "/users"
143143
},
144144
"links": {
145-
"previous": "https://localhost/users?page=1",
146-
"next": "https://localhost/users?page=3"
145+
"previous": "/users?page=1",
146+
"next": "/users?page=3"
147147
}
148148
}
149149
```
@@ -152,4 +152,117 @@ Example output:
152152

153153
- Pagination links are generated from numeric page values (`firstPage`, `lastPage`, `prevPage`, `nextPage`) and `pagination.path`.
154154
- If `paginatedExtras.cursor` is not configured, cursor values are emitted under `meta.cursor` by default.
155-
- `baseUrl` should be set to your public API origin for production responses.
155+
- Set `baseUrl` to your public API origin for absolute URLs in production, or use [URL auto-detection](#url-detection) to derive paths from the request context.
156+
157+
## 6. Automatic URL detection from request context {#url-detection}
158+
159+
When your pagination data does **not** include an explicit `path`, Resora can automatically detect the current request URL from the HTTP context and use it for link generation — including any existing query string parameters.
160+
161+
This works with both **Express** and **H3** because both expose a `{ req, res }` context shape.
162+
163+
### Passing context to the constructor
164+
165+
Pass the full `{ req, res }` context (or the framework event) as the second argument:
166+
167+
::: code-group
168+
169+
```ts [Express]
170+
app.get('/api/users', async (req, res) => {
171+
const users = await getUsers(req.query);
172+
173+
// Pass { req, res } — Resora reads req.originalUrl for the path
174+
return await new ResourceCollection(users, { req, res });
175+
});
176+
```
177+
178+
```ts [H3]
179+
export default defineEventHandler((event) => {
180+
const users = await getUsers(getQuery(event));
181+
182+
// Pass the H3 event — Resora reads event.req.url
183+
return new ResourceCollection(users, event);
184+
});
185+
```
186+
187+
:::
188+
189+
If the user navigates to `/api/users?search=foo&sort=name`, the generated links will preserve the query string:
190+
191+
```json
192+
{
193+
"links": {
194+
"first": "/api/users?search=foo&sort=name&page=1",
195+
"next": "/api/users?search=foo&sort=name&page=2"
196+
}
197+
}
198+
```
199+
200+
With `baseUrl` configured, the links become full URLs:
201+
202+
```json
203+
{
204+
"links": {
205+
"first": "https://api.example.com/api/users?search=foo&sort=name&page=1",
206+
"next": "https://api.example.com/api/users?search=foo&sort=name&page=2"
207+
}
208+
}
209+
```
210+
211+
::: tip Priority
212+
An explicit `pagination.path` always takes precedence over the auto-detected URL. If both are present, the explicit path wins.
213+
:::
214+
215+
### Using `setCtx()` in middleware
216+
217+
If you prefer to set the request context once in a middleware (instead of passing it to each resource), use the static `setCtx()` method available on all serializer classes:
218+
219+
::: code-group
220+
221+
```ts [Express]
222+
import { Resource } from 'resora';
223+
224+
// Register middleware before your routes
225+
app.use((req, res, next) => {
226+
Resource.setCtx({ req, res });
227+
next();
228+
});
229+
230+
// In your route handler — no need to pass context
231+
app.get('/api/users', async (req, res) => {
232+
const users = await getUsers(req.query);
233+
return await new ResourceCollection(users, res);
234+
});
235+
```
236+
237+
```ts [H3]
238+
import { Resource } from 'resora';
239+
240+
app.use((event) => {
241+
Resource.setCtx(event);
242+
});
243+
244+
export default defineEventHandler((event) => {
245+
return new ResourceCollection(users);
246+
});
247+
```
248+
249+
:::
250+
251+
`setCtx()` is inherited by `Resource`, `ResourceCollection`, and `GenericResource` — calling it on any one of them makes the request URL available for all of them.
252+
253+
::: warning Request scoping
254+
`setCtx()` stores the URL globally. If your application handles concurrent requests in a shared process, call `setCtx()` at the start of each request to ensure accuracy. For single-request-per-process runtimes (serverless, edge workers), this is automatic.
255+
:::
256+
257+
### How URL extraction works
258+
259+
Resora inspects the context using the common `{ req, res }` interface:
260+
261+
| Framework | Request URL source | Example value |
262+
| --------- | ---------------------------------- | ------------------------------------- |
263+
| Express | `req.originalUrl` | `/api/users?search=foo` |
264+
| H3 | `req.url` (Web standard `Request`) | `http://localhost:3000/api/users?q=1` |
265+
266+
For H3's Web standard `Request`, the full URL is parsed and only the pathname + search is used for link generation.
267+
268+
If a bare response object is passed (without `req`), URL detection is skipped and the resource falls back to `pagination.path` or the bare `/?page=X` format.

docs/guide/server-response.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ Binds the internal Resource instance to a framework-specific response object.
1313
### Parameters
1414

1515
- `res`
16-
The framework’s native response object (e.g., H3 `res`, Express `res`).
16+
The framework’s native response object (e.g., H3 `res`, Express `res`) or a full context object containing `{ req, res }`.
1717

18-
> For Express and other frameworks implementing the connect-style middleware, this can be skipped, in which case Resora will use the response object provided in the the `Resource` or `ResourceCollection` constructor which is required by default.
18+
> For Express and other frameworks implementing the connect-style middleware, this can be skipped, in which case Resora will use the response object provided in the `Resource` or `ResourceCollection` constructor.
19+
>
20+
> The constructor accepts either a bare response object or a full `{ req, res }` context. When a context is passed, Resora also extracts the request URL for [automatic pagination link generation](/guide/pagination-cursor-recipes#url-detection).
1921
2022
### Returns
2123

0 commit comments

Comments
 (0)