@h3ravel/url 1.0.0 → 1.0.2
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/README.md +56 -16
- package/dist/index.cjs +3 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/package.json +10 -5
package/README.md
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<a href="https://h3ravel.toneflix.net" target="_blank">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/h3ravel/assets/refs/heads/main/logo-full.svg" width="200" alt="H3ravel Logo">
|
|
4
|
+
</a>
|
|
5
|
+
<h1 align="center"><a href="https://h3ravel.toneflix.net/guide/urls">H3ravel Url</a></h1>
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
[![Framework][ix]][lx]
|
|
8
|
+
[![URL Package Version][i1]][l1]
|
|
9
|
+
[![Downloads][d1]][l1]
|
|
10
|
+
[![Tests][tei]][tel]
|
|
11
|
+
[![License][lini]][linl]
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
# About H3ravel/url
|
|
16
|
+
|
|
17
|
+
Request-aware URI builder and URL manipulation utilities for [H3ravel](https://h3ravel.toneflix.net) framework.
|
|
4
18
|
|
|
5
19
|
## Installation
|
|
6
20
|
|
|
@@ -21,19 +35,19 @@ npm install @h3ravel/url
|
|
|
21
35
|
### Basic URL Creation
|
|
22
36
|
|
|
23
37
|
```typescript
|
|
24
|
-
import { Url } from '@h3ravel/url'
|
|
38
|
+
import { Url } from '@h3ravel/url';
|
|
25
39
|
|
|
26
40
|
// From string
|
|
27
|
-
const url = Url.of('https://example.com/path')
|
|
41
|
+
const url = Url.of('https://example.com/path');
|
|
28
42
|
|
|
29
43
|
// From path relative to app URL
|
|
30
|
-
const url = Url.to('/users')
|
|
44
|
+
const url = Url.to('/users');
|
|
31
45
|
|
|
32
46
|
// From named route
|
|
33
|
-
const url = Url.route('users.show', { id: 1 })
|
|
47
|
+
const url = Url.route('users.show', { id: 1 });
|
|
34
48
|
|
|
35
49
|
// From controller action
|
|
36
|
-
const url = Url.action('UserController@index')
|
|
50
|
+
const url = Url.action('UserController@index');
|
|
37
51
|
```
|
|
38
52
|
|
|
39
53
|
### Fluent Builder API
|
|
@@ -46,7 +60,7 @@ const url = Url.of('https://example.com')
|
|
|
46
60
|
.withPath('/users')
|
|
47
61
|
.withQuery({ page: 2 })
|
|
48
62
|
.withFragment('section-1')
|
|
49
|
-
.toString()
|
|
63
|
+
.toString();
|
|
50
64
|
// -> "http://test.com:8000/users?page=2#section-1"
|
|
51
65
|
```
|
|
52
66
|
|
|
@@ -54,29 +68,33 @@ const url = Url.of('https://example.com')
|
|
|
54
68
|
|
|
55
69
|
```typescript
|
|
56
70
|
// Get current URL
|
|
57
|
-
url().current()
|
|
71
|
+
url().current();
|
|
58
72
|
|
|
59
73
|
// Get full URL with query string
|
|
60
|
-
url().full()
|
|
74
|
+
url().full();
|
|
61
75
|
|
|
62
76
|
// Get previous URL
|
|
63
|
-
url().previous()
|
|
77
|
+
url().previous();
|
|
64
78
|
|
|
65
79
|
// Get previous path only
|
|
66
|
-
url().previousPath()
|
|
80
|
+
url().previousPath();
|
|
67
81
|
|
|
68
82
|
// Get current query parameters
|
|
69
|
-
url().query()
|
|
83
|
+
url().query();
|
|
70
84
|
```
|
|
71
85
|
|
|
72
86
|
### Signed URLs
|
|
73
87
|
|
|
74
88
|
```typescript
|
|
75
89
|
// Create signed route URL
|
|
76
|
-
const signedUrl = Url.signedRoute('users.show', { id: 1 })
|
|
90
|
+
const signedUrl = Url.signedRoute('users.show', { id: 1 });
|
|
77
91
|
|
|
78
92
|
// Create temporary signed route URL (expires in 5 minutes)
|
|
79
|
-
const tempUrl = Url.temporarySignedRoute(
|
|
93
|
+
const tempUrl = Url.temporarySignedRoute(
|
|
94
|
+
'users.index',
|
|
95
|
+
{},
|
|
96
|
+
Date.now() + 300000
|
|
97
|
+
);
|
|
80
98
|
```
|
|
81
99
|
|
|
82
100
|
## API Reference
|
|
@@ -108,6 +126,28 @@ const tempUrl = Url.temporarySignedRoute('users.index', {}, Date.now() + 300000)
|
|
|
108
126
|
- `previousPath()` - Get previous request path
|
|
109
127
|
- `query()` - Get current query parameters
|
|
110
128
|
|
|
129
|
+
## Contributing
|
|
130
|
+
|
|
131
|
+
Thank you for considering contributing to the H3ravel framework! The [Contribution Guide](https://h3ravel.toneflix.net/contributing) can be found in the H3ravel documentation and will provide you with all the information you need to get started.
|
|
132
|
+
|
|
133
|
+
## Code of Conduct
|
|
134
|
+
|
|
135
|
+
In order to ensure that the H3ravel community is welcoming to all, please review and abide by the [Code of Conduct](#).
|
|
136
|
+
|
|
137
|
+
## Security Vulnerabilities
|
|
138
|
+
|
|
139
|
+
If you discover a security vulnerability within H3ravel, please send an e-mail to Legacy via hamzas.legacy@toneflix.ng. All security vulnerabilities will be promptly addressed.
|
|
140
|
+
|
|
111
141
|
## License
|
|
112
142
|
|
|
113
|
-
MIT
|
|
143
|
+
The H3ravel framework is open-sourced software licensed under the [MIT license](LICENSE).
|
|
144
|
+
|
|
145
|
+
[ix]: https://img.shields.io/npm/v/%40h3ravel%2Fcore?style=flat-square&label=Framework&color=%230970ce
|
|
146
|
+
[lx]: https://www.npmjs.com/package/@h3ravel/core
|
|
147
|
+
[i1]: https://img.shields.io/npm/v/%40h3ravel%2Furl?style=flat-square&label=@h3ravel/url&color=%230970ce
|
|
148
|
+
[l1]: https://www.npmjs.com/package/@h3ravel/url
|
|
149
|
+
[d1]: https://img.shields.io/npm/dt/%40h3ravel%2Furl?style=flat-square&label=Downloads&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2F%40h3ravel%2Furl
|
|
150
|
+
[linl]: https://github.com/h3ravel/framework/blob/main/LICENSE
|
|
151
|
+
[lini]: https://img.shields.io/github/license/h3ravel/framework
|
|
152
|
+
[tel]: https://github.com/h3ravel/framework/actions/workflows/test.yml
|
|
153
|
+
[tei]: https://github.com/h3ravel/framework/actions/workflows/test.yml/badge.svg
|
package/dist/index.cjs
CHANGED
|
@@ -246,8 +246,7 @@ var Url = class Url {
|
|
|
246
246
|
const url$1 = this.toString();
|
|
247
247
|
const queryParams = { ...this._query };
|
|
248
248
|
if (expiration) queryParams.expires = Math.floor(expiration / 1e3);
|
|
249
|
-
|
|
250
|
-
queryParams.signature = (0, __h3ravel_support.hmac)(payload, key);
|
|
249
|
+
queryParams.signature = (0, __h3ravel_support.hmac)(expiration ? `${url$1}?expires=${queryParams.expires}` : url$1, key);
|
|
251
250
|
return this.withQuery(queryParams);
|
|
252
251
|
}
|
|
253
252
|
/**
|
|
@@ -270,8 +269,7 @@ var Url = class Url {
|
|
|
270
269
|
try {
|
|
271
270
|
key = config("app.key", "default-key");
|
|
272
271
|
} catch {}
|
|
273
|
-
|
|
274
|
-
return signature === expectedSignature;
|
|
272
|
+
return signature === (0, __h3ravel_support.hmac)(payload, key);
|
|
275
273
|
}
|
|
276
274
|
/**
|
|
277
275
|
* Convert the URL to its string representation
|
|
@@ -400,6 +398,7 @@ function createUrlHelpers(app) {
|
|
|
400
398
|
* Service provider for URL utilities
|
|
401
399
|
*/
|
|
402
400
|
var UrlServiceProvider = class extends __h3ravel_core.ServiceProvider {
|
|
401
|
+
static priority = 897;
|
|
403
402
|
/**
|
|
404
403
|
* Register URL services in the container
|
|
405
404
|
*/
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["app: Application","path","url","query: Record<string, unknown>","routes: RouteDefinition[]","route","ConfigException","queryParams: Record<string, unknown>","path","ServiceProvider"],"sources":["../src/RequestAwareHelpers.ts","../src/Url.ts","../src/Helpers.ts","../src/Providers/UrlServiceProvider.ts"],"sourcesContent":["import type { Application } from '@h3ravel/core'\nimport type { IRequest } from '@h3ravel/shared'\nimport { RouteParams } from './Contracts/UrlContract'\n\n/**\n * Request-aware URL helper class\n */\nexport class RequestAwareHelpers {\n private readonly baseUrl: string = ''\n\n constructor(private app: Application) {\n try {\n this.baseUrl = config('app.url', 'http://localhost:3000')\n } catch {/** */ }\n }\n\n /**\n * Get the current request instance\n */\n private getCurrentRequest (): IRequest {\n const request = this.app.make('http.request')\n if (!request) {\n throw new Error('Request instance not available in current context')\n }\n return request\n }\n\n /**\n * Get the current request URL (path only, no query string)\n */\n current (): string {\n const request = this.getCurrentRequest()\n const event = request.getEvent()\n\n // Get the path from the request\n const raw = event.req.url ?? '/'\n const url = new URL(raw, 'http://localhost')\n return url.pathname\n }\n\n /**\n * Get the full current URL with query string\n */\n full (): string {\n const request = this.getCurrentRequest()\n const event = request.getEvent()\n\n // Get the full URL including query string\n const requestUrl = event.req.url ?? '/'\n\n // If requestUrl is already absolute, use it directly, otherwise combine with baseUrl\n if (requestUrl.startsWith('http')) {\n return requestUrl\n }\n\n const fullUrl = new URL(requestUrl, this.baseUrl)\n return fullUrl.toString()\n }\n\n /**\n * Get the previous request URL from session or referrer\n */\n previous (): string {\n const request = this.getCurrentRequest()\n const event = request.getEvent()\n\n // Try to get from session first (if session is available)\n // For now, fallback to HTTP referrer header\n const headers = (event as any)?.node?.req?.headers as Record<string, string | string[] | undefined> | undefined\n // console.log(headers)\n let referrer = headers?.referer ?? headers?.referrer\n if (Array.isArray(referrer)) referrer = referrer[0]\n if (referrer) return referrer\n\n // Fallback to current URL if no referrer\n return this.current()\n }\n\n /**\n * Get the previous request path (without query string)\n */\n previousPath (): string {\n const previousUrl = this.previous()\n\n try {\n const url = new URL(previousUrl)\n return url.pathname\n } catch {\n // If previous URL is not a valid URL, return as-is\n return previousUrl\n }\n }\n\n /**\n * Get the current query parameters\n */\n query (): RouteParams {\n const request = this.getCurrentRequest()\n return request.query || {}\n }\n}\n\n/**\n * Global helper function factory\n */\nexport function createUrlHelper (app: Application): () => RequestAwareHelpers {\n return () => new RequestAwareHelpers(app)\n}\n","import { ConfigException, type Application } from '@h3ravel/core'\nimport { RouteParams } from './Contracts/UrlContract'\nimport { hmac } from '@h3ravel/support'\nimport { RouteDefinition, ExtractControllerMethods } from '@h3ravel/shared'\nimport path from 'node:path'\n\n/**\n * URL builder class with fluent API and request-aware helpers\n */\nexport class Url {\n private readonly _scheme?: string\n private readonly _host?: string\n private readonly _port?: number\n private readonly _path: string\n private readonly _query: Record<string, unknown>\n private readonly _fragment?: string\n private readonly app?: Application\n\n private constructor(\n app?: Application,\n scheme?: string,\n host?: string,\n port?: number,\n path: string = '/',\n query: Record<string, unknown> = {},\n fragment?: string\n ) {\n this.app = app\n this._scheme = scheme\n this._host = host\n this._port = port\n this._path = path.startsWith('/') ? path : `/${path}`\n this._query = { ...query }\n this._fragment = fragment\n }\n\n /**\n * Create a URL from a full URL string\n */\n static of (url: string, app?: Application): Url {\n try {\n const parsed = new URL(url)\n const query: Record<string, unknown> = {}\n\n // Parse query parameters\n parsed.searchParams.forEach((value, key) => {\n query[key] = value\n })\n\n return new Url(\n app,\n parsed.protocol.replace(':', ''),\n parsed.hostname,\n parsed.port ? parseInt(parsed.port) : undefined,\n parsed.pathname || '/',\n query,\n parsed.hash ? parsed.hash.substring(1) : undefined\n )\n } catch {\n throw new Error(`Invalid URL: ${url}`)\n }\n }\n\n /**\n * Create a URL from a path relative to the app URL\n */\n static to (path: string, app?: Application): Url {\n let baseUrl = ''\n try {\n baseUrl = config('app.url', 'http://localhost:3000')\n } catch {/** */ }\n\n const fullUrl = new URL(path, baseUrl).toString()\n\n return Url.of(fullUrl, app)\n }\n\n /**\n * Create a URL from a named route\n */\n // Route parameter map (declaration-mergeable by consumers)\n static route<TName extends string = string, TParams extends RouteParams = RouteParams> (\n name: TName,\n params: TParams = {} as TParams,\n app?: Application\n ): Url {\n if (!app) {\n throw new Error('Application instance required for route generation')\n }\n\n // Use (app as any).make to avoid TS error if make is not typed on Application\n const router = app.make('router')\n if (!router || typeof router.route !== 'function') {\n throw new Error('Router not available or does not support route generation')\n }\n\n if (typeof router.route !== 'function') {\n throw new Error('Router does not support route generation')\n }\n\n const routeUrl = router.route(name, params)\n if (!routeUrl) {\n throw new Error(`Route \"${name}\" not found`)\n }\n\n return Url.to(routeUrl, app)\n }\n\n /**\n * Create a signed URL from a named route\n */\n static signedRoute<TName extends string = string, TParams extends RouteParams = RouteParams> (\n name: TName,\n params: TParams = {} as TParams,\n app?: Application\n ): Url {\n const url = Url.route<TName, TParams>(name, params, app)\n return url.withSignature(app)\n }\n\n /**\n * Create a temporary signed URL from a named route\n */\n static temporarySignedRoute<TName extends string = string, TParams extends RouteParams = RouteParams> (\n name: TName,\n params: TParams = {} as TParams,\n expiration: number,\n app?: Application\n ): Url {\n const url = Url.route<TName, TParams>(name, params, app)\n return url.withSignature(app, expiration)\n }\n\n /**\n * Create a URL from a controller action\n */\n static action<C extends new (...args: any) => any> (\n controller: string | [C, methodName: ExtractControllerMethods<InstanceType<C>>],\n params?: Record<string, any>,\n app?: Application\n ): Url {\n if (!app) throw new Error('Application instance required for action URL generation')\n\n const [controllerName, methodName = 'index'] = typeof controller === 'string'\n ? controller.split('@')\n : controller\n\n const cname = typeof controllerName === 'string' ? controllerName : controllerName.name\n\n const routes: RouteDefinition[] = app.make('app.routes')\n\n if (!Array.isArray(routes)) {\n // Backward-compatible message expected by existing tests\n throw new Error('Action URL generation requires router integration - not yet implemented')\n }\n\n if (routes.length < 1) throw new Error(`No routes available to resolve action: ${controller}`)\n\n // Search for for the \n const found = routes.find(route => {\n return route.signature?.[0] === cname && (route.signature?.[1] || 'index') === methodName\n })\n\n if (!found) throw new Error(`No route found for ${cname}`)\n\n // Build the route parameters\n const _params = Object.values(params ?? {}).join('/')\n\n if (_params) {\n return Url.to(path.join(found.path, _params))\n }\n\n return Url.to(found.path, app)\n }\n\n /**\n * Set the scheme (protocol) of the URL\n */\n withScheme (scheme: string): Url {\n return new Url(\n this.app,\n scheme,\n this._host,\n this._port,\n this._path,\n this._query,\n this._fragment\n )\n }\n\n /**\n * Set the host of the URL\n */\n withHost (host: string): Url {\n return new Url(\n this.app,\n this._scheme,\n host,\n this._port,\n this._path,\n this._query,\n this._fragment\n )\n }\n\n /**\n * Set the port of the URL\n */\n withPort (port: number): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n port,\n this._path,\n this._query,\n this._fragment\n )\n }\n\n /**\n * Set the path of the URL\n */\n withPath (path: string): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n path,\n this._query,\n this._fragment\n )\n }\n\n /**\n * Set the query parameters of the URL\n */\n withQuery (query: Record<string, unknown>): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n this._path,\n { ...query },\n this._fragment\n )\n }\n\n /**\n * Merge additional query parameters\n */\n withQueryParams (params: Record<string, unknown>): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n this._path,\n { ...this._query, ...params },\n this._fragment\n )\n }\n\n /**\n * Set the fragment (hash) of the URL\n */\n withFragment (fragment: string): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n this._path,\n this._query,\n fragment\n )\n }\n\n /**\n * Add a signature to the URL for security\n */\n withSignature (app?: Application, expiration?: number): Url {\n const appInstance = app || this.app\n if (!appInstance) {\n throw new Error('Application instance required for URL signing')\n }\n\n let key = ''\n try {\n key = config('app.key')\n } catch {/** */ }\n\n if (!key) {\n throw new ConfigException('APP_KEY and app.key', 'any', this)\n }\n const url = this.toString()\n const queryParams: Record<string, unknown> = { ...this._query }\n\n if (expiration) {\n queryParams.expires = Math.floor(expiration / 1000)\n }\n\n // Create signature payload\n const payload = expiration\n ? `${url}?expires=${queryParams.expires}`\n : url\n\n const signature = hmac(payload, key)\n queryParams.signature = signature\n\n return this.withQuery(queryParams)\n }\n\n /**\n * Verify if a URL signature is valid\n */\n hasValidSignature (app?: Application): boolean {\n const appInstance = app || this.app\n if (!appInstance) {\n return false\n }\n\n const signature = this._query.signature\n if (!signature) {\n return false\n }\n\n // Check expiration if present\n if (this._query.expires !== undefined && this._query.expires !== null) {\n const expiresStr = String(this._query.expires)\n const expirationTime = parseInt(expiresStr, 10) * 1000\n if (isNaN(expirationTime) || Date.now() > expirationTime) {\n return false\n }\n }\n\n // Recreate URL without signature for verification\n const queryWithoutSignature = { ...this._query }\n delete queryWithoutSignature.signature\n\n const urlWithoutSignature = new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n this._path,\n queryWithoutSignature,\n this._fragment\n ).toString()\n\n const payload = this._query.expires\n ? `${urlWithoutSignature}?expires=${this._query.expires}`\n : urlWithoutSignature\n\n let key = ''\n try {\n key = config('app.key', 'default-key')\n } catch {/** */ }\n const expectedSignature = hmac(payload, key)\n\n return signature === expectedSignature\n }\n\n /**\n * Convert the URL to its string representation\n */\n toString (): string {\n let url = ''\n\n // Add scheme and host\n if (this._scheme && this._host) {\n url += `${this._scheme}://${this._host}`\n\n // Add port if specified and not default\n if (this._port &&\n !((this._scheme === 'http' && this._port === 80) ||\n (this._scheme === 'https' && this._port === 443))) {\n url += `:${this._port}`\n }\n }\n\n // Add path\n if (this._path) {\n if (!this._path.startsWith('/')) {\n url += '/'\n }\n url += this._path\n }\n\n // Add query parameters\n const queryEntries = Object.entries(this._query)\n if (queryEntries.length > 0) {\n const queryString = queryEntries\n .map(([key, value]) => {\n if (Array.isArray(value)) {\n return value.map(v => `${encodeURIComponent(key)}%5B%5D=${encodeURIComponent(v)}`).join('&')\n }\n return `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`\n })\n .join('&')\n url += `?${queryString}`\n }\n\n // Add fragment\n if (this._fragment) {\n url += `#${this._fragment}`\n }\n\n return url\n }\n\n /**\n * Get the scheme\n */\n getScheme (): string | undefined {\n return this._scheme\n }\n\n /**\n * Get the host\n */\n getHost (): string | undefined {\n return this._host\n }\n\n /**\n * Get the port\n */\n getPort (): number | undefined {\n return this._port\n }\n\n /**\n * Get the path\n */\n getPath (): string {\n return this._path\n }\n\n /**\n * Get the query parameters\n */\n getQuery (): Record<string, unknown> {\n return { ...this._query }\n }\n\n /**\n * Get the fragment\n */\n getFragment (): string | undefined {\n return this._fragment\n }\n}\n","import { Application } from '@h3ravel/core'\nimport { HelpersContract } from './Contracts/UrlContract'\nimport { RequestAwareHelpers } from './RequestAwareHelpers'\nimport { Url } from './Url'\nimport { ExtractControllerMethods } from '@h3ravel/shared'\n\n/**\n * Global helper functions for URL manipulation\n */\n\n/**\n * Create a URL from a path relative to the app URL\n */\nexport function to (\n path: string,\n app?: Application\n): Url {\n return Url.to(path, app)\n}\n\n/**\n * Create a URL from a named route\n */\nexport function route<TName extends string, TParams extends Record<string, string> = Record<string, string>> (\n name: TName,\n params: TParams = {} as TParams,\n app?: Application\n): Url {\n return Url.route<TName, TParams>(name, params, app)\n}\n\n/**\n * Create a signed URL from a named route\n */\nexport function signedRoute<TName extends string, TParams extends Record<string, string> = Record<string, string>> (\n name: TName,\n params: TParams = {} as TParams,\n app?: Application\n): Url {\n return Url.signedRoute<TName, TParams>(name, params, app)\n}\n\n/**\n * Create a temporary signed URL from a named route\n */\nexport function temporarySignedRoute (\n name: string,\n params: Record<string, string> = {},\n expiration: number,\n app?: Application\n): Url {\n return Url.temporarySignedRoute(name, params, expiration, app)\n}\n\n/**\n * Create a URL from a controller action\n */\nexport function action (\n controller: string,\n app?: Application\n): Url {\n return Url.action(controller, app)\n}\n\n/**\n * Get request-aware URL helpers\n */\nexport function url (app?: Application): RequestAwareHelpers {\n if (!app) throw new Error('Application instance required for request-aware URL helpers')\n return new RequestAwareHelpers(app)\n}\n\n/**\n * Create URL helpers that are bound to an application instance\n */\nexport function createUrlHelpers (app: Application): HelpersContract {\n return {\n /**\n * Create a URL from a path relative to the app URL\n */\n to: (path: string) => Url.to(path, app),\n\n /**\n * Create a URL from a named route\n */\n route: (\n name: string,\n params: Record<string, any> = {}\n ) => Url.route(name, params, app).toString(),\n\n /**\n * Create a signed URL from a named route\n */\n signedRoute: (\n name: string,\n params: Record<string, any> = {}\n ) => Url.signedRoute(name, params, app),\n\n /**\n * Create a temporary signed URL from a named route\n */\n temporarySignedRoute: (\n name: string,\n params: Record<string, any> = {},\n expiration: number\n ) => Url.temporarySignedRoute(name, params, expiration, app),\n\n /**\n * Create a URL from a controller action\n */\n action: <C extends new (...args: any) => any> (\n controller: string | [C, methodName: ExtractControllerMethods<InstanceType<C>>],\n params?: Record<string, any>\n ) => Url.action(controller, params, app).toString(),\n\n /**\n * Get request-aware URL helpers\n */\n url: (path?: string) => {\n if (path) {\n return Url.to(path).toString() as never\n }\n return new RequestAwareHelpers(app) as never\n }\n }\n}\n","/// <reference path=\"../app.globals.d.ts\" />\nimport { ServiceProvider } from '@h3ravel/core'\nimport { Url } from '../Url'\nimport { createUrlHelper } from '../RequestAwareHelpers'\nimport { createUrlHelpers } from '../Helpers'\n\n/**\n * Service provider for URL utilities\n */\nexport class UrlServiceProvider extends ServiceProvider {\n /**\n * Register URL services in the container\n */\n register (): void {\n // Register the Url class\n this.app.singleton('app.url', () => Url)\n // Register the url() helper function\n this.app.singleton('app.url.helper', () => createUrlHelper(this.app))\n\n // Register bound URL helpers\n this.app.singleton('app.url.helpers', () => createUrlHelpers(this.app))\n\n // Make url() globally available\n if (typeof globalThis !== 'undefined') {\n const helpers = createUrlHelpers(this.app)\n\n Object.assign(globalThis, {\n url: helpers.url,\n route: helpers.route,\n action: helpers.action,\n })\n }\n }\n\n /**\n * Boot URL services\n */\n boot (): void {\n // Any additional setup can be done here\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["app: Application","path","url","query: Record<string, unknown>","routes: RouteDefinition[]","route","ConfigException","queryParams: Record<string, unknown>","path","ServiceProvider"],"sources":["../src/RequestAwareHelpers.ts","../src/Url.ts","../src/Helpers.ts","../src/Providers/UrlServiceProvider.ts"],"sourcesContent":["import type { Application } from '@h3ravel/core'\nimport type { IRequest } from '@h3ravel/shared'\nimport { RouteParams } from './Contracts/UrlContract'\n\n/**\n * Request-aware URL helper class\n */\nexport class RequestAwareHelpers {\n private readonly baseUrl: string = ''\n\n constructor(private app: Application) {\n try {\n this.baseUrl = config('app.url', 'http://localhost:3000')\n } catch {/** */ }\n }\n\n /**\n * Get the current request instance\n */\n private getCurrentRequest (): IRequest {\n const request = this.app.make('http.request')\n if (!request) {\n throw new Error('Request instance not available in current context')\n }\n return request\n }\n\n /**\n * Get the current request URL (path only, no query string)\n */\n current (): string {\n const request = this.getCurrentRequest()\n const event = request.getEvent()\n\n // Get the path from the request\n const raw = event.req.url ?? '/'\n const url = new URL(raw, 'http://localhost')\n return url.pathname\n }\n\n /**\n * Get the full current URL with query string\n */\n full (): string {\n const request = this.getCurrentRequest()\n const event = request.getEvent()\n\n // Get the full URL including query string\n const requestUrl = event.req.url ?? '/'\n\n // If requestUrl is already absolute, use it directly, otherwise combine with baseUrl\n if (requestUrl.startsWith('http')) {\n return requestUrl\n }\n\n const fullUrl = new URL(requestUrl, this.baseUrl)\n return fullUrl.toString()\n }\n\n /**\n * Get the previous request URL from session or referrer\n */\n previous (): string {\n const request = this.getCurrentRequest()\n const event = request.getEvent()\n\n // Try to get from session first (if session is available)\n // For now, fallback to HTTP referrer header\n const headers = (event as any)?.node?.req?.headers as Record<string, string | string[] | undefined> | undefined\n // console.log(headers)\n let referrer = headers?.referer ?? headers?.referrer\n if (Array.isArray(referrer)) referrer = referrer[0]\n if (referrer) return referrer\n\n // Fallback to current URL if no referrer\n return this.current()\n }\n\n /**\n * Get the previous request path (without query string)\n */\n previousPath (): string {\n const previousUrl = this.previous()\n\n try {\n const url = new URL(previousUrl)\n return url.pathname\n } catch {\n // If previous URL is not a valid URL, return as-is\n return previousUrl\n }\n }\n\n /**\n * Get the current query parameters\n */\n query (): RouteParams {\n const request = this.getCurrentRequest()\n return request.query || {}\n }\n}\n\n/**\n * Global helper function factory\n */\nexport function createUrlHelper (app: Application): () => RequestAwareHelpers {\n return () => new RequestAwareHelpers(app)\n}\n","import { ConfigException, type Application } from '@h3ravel/core'\nimport { RouteParams } from './Contracts/UrlContract'\nimport { hmac } from '@h3ravel/support'\nimport { RouteDefinition, ExtractControllerMethods } from '@h3ravel/shared'\nimport path from 'node:path'\n\n/**\n * URL builder class with fluent API and request-aware helpers\n */\nexport class Url {\n private readonly _scheme?: string\n private readonly _host?: string\n private readonly _port?: number\n private readonly _path: string\n private readonly _query: Record<string, unknown>\n private readonly _fragment?: string\n private readonly app?: Application\n\n private constructor(\n app?: Application,\n scheme?: string,\n host?: string,\n port?: number,\n path: string = '/',\n query: Record<string, unknown> = {},\n fragment?: string\n ) {\n this.app = app\n this._scheme = scheme\n this._host = host\n this._port = port\n this._path = path.startsWith('/') ? path : `/${path}`\n this._query = { ...query }\n this._fragment = fragment\n }\n\n /**\n * Create a URL from a full URL string\n */\n static of (url: string, app?: Application): Url {\n try {\n const parsed = new URL(url)\n const query: Record<string, unknown> = {}\n\n // Parse query parameters\n parsed.searchParams.forEach((value, key) => {\n query[key] = value\n })\n\n return new Url(\n app,\n parsed.protocol.replace(':', ''),\n parsed.hostname,\n parsed.port ? parseInt(parsed.port) : undefined,\n parsed.pathname || '/',\n query,\n parsed.hash ? parsed.hash.substring(1) : undefined\n )\n } catch {\n throw new Error(`Invalid URL: ${url}`)\n }\n }\n\n /**\n * Create a URL from a path relative to the app URL\n */\n static to (path: string, app?: Application): Url {\n let baseUrl = ''\n try {\n baseUrl = config('app.url', 'http://localhost:3000')\n } catch {/** */ }\n\n const fullUrl = new URL(path, baseUrl).toString()\n\n return Url.of(fullUrl, app)\n }\n\n /**\n * Create a URL from a named route\n */\n // Route parameter map (declaration-mergeable by consumers)\n static route<TName extends string = string, TParams extends RouteParams = RouteParams> (\n name: TName,\n params: TParams = {} as TParams,\n app?: Application\n ): Url {\n if (!app) {\n throw new Error('Application instance required for route generation')\n }\n\n // Use (app as any).make to avoid TS error if make is not typed on Application\n const router = app.make('router')\n if (!router || typeof router.route !== 'function') {\n throw new Error('Router not available or does not support route generation')\n }\n\n if (typeof router.route !== 'function') {\n throw new Error('Router does not support route generation')\n }\n\n const routeUrl = router.route(name, params)\n if (!routeUrl) {\n throw new Error(`Route \"${name}\" not found`)\n }\n\n return Url.to(routeUrl, app)\n }\n\n /**\n * Create a signed URL from a named route\n */\n static signedRoute<TName extends string = string, TParams extends RouteParams = RouteParams> (\n name: TName,\n params: TParams = {} as TParams,\n app?: Application\n ): Url {\n const url = Url.route<TName, TParams>(name, params, app)\n return url.withSignature(app)\n }\n\n /**\n * Create a temporary signed URL from a named route\n */\n static temporarySignedRoute<TName extends string = string, TParams extends RouteParams = RouteParams> (\n name: TName,\n params: TParams = {} as TParams,\n expiration: number,\n app?: Application\n ): Url {\n const url = Url.route<TName, TParams>(name, params, app)\n return url.withSignature(app, expiration)\n }\n\n /**\n * Create a URL from a controller action\n */\n static action<C extends new (...args: any) => any> (\n controller: string | [C, methodName: ExtractControllerMethods<InstanceType<C>>],\n params?: Record<string, any>,\n app?: Application\n ): Url {\n if (!app) throw new Error('Application instance required for action URL generation')\n\n const [controllerName, methodName = 'index'] = typeof controller === 'string'\n ? controller.split('@')\n : controller\n\n const cname = typeof controllerName === 'string' ? controllerName : controllerName.name\n\n const routes: RouteDefinition[] = app.make('app.routes')\n\n if (!Array.isArray(routes)) {\n // Backward-compatible message expected by existing tests\n throw new Error('Action URL generation requires router integration - not yet implemented')\n }\n\n if (routes.length < 1) throw new Error(`No routes available to resolve action: ${controller}`)\n\n // Search for for the \n const found = routes.find(route => {\n return route.signature?.[0] === cname && (route.signature?.[1] || 'index') === methodName\n })\n\n if (!found) throw new Error(`No route found for ${cname}`)\n\n // Build the route parameters\n const _params = Object.values(params ?? {}).join('/')\n\n if (_params) {\n return Url.to(path.join(found.path, _params))\n }\n\n return Url.to(found.path, app)\n }\n\n /**\n * Set the scheme (protocol) of the URL\n */\n withScheme (scheme: string): Url {\n return new Url(\n this.app,\n scheme,\n this._host,\n this._port,\n this._path,\n this._query,\n this._fragment\n )\n }\n\n /**\n * Set the host of the URL\n */\n withHost (host: string): Url {\n return new Url(\n this.app,\n this._scheme,\n host,\n this._port,\n this._path,\n this._query,\n this._fragment\n )\n }\n\n /**\n * Set the port of the URL\n */\n withPort (port: number): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n port,\n this._path,\n this._query,\n this._fragment\n )\n }\n\n /**\n * Set the path of the URL\n */\n withPath (path: string): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n path,\n this._query,\n this._fragment\n )\n }\n\n /**\n * Set the query parameters of the URL\n */\n withQuery (query: Record<string, unknown>): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n this._path,\n { ...query },\n this._fragment\n )\n }\n\n /**\n * Merge additional query parameters\n */\n withQueryParams (params: Record<string, unknown>): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n this._path,\n { ...this._query, ...params },\n this._fragment\n )\n }\n\n /**\n * Set the fragment (hash) of the URL\n */\n withFragment (fragment: string): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n this._path,\n this._query,\n fragment\n )\n }\n\n /**\n * Add a signature to the URL for security\n */\n withSignature (app?: Application, expiration?: number): Url {\n const appInstance = app || this.app\n if (!appInstance) {\n throw new Error('Application instance required for URL signing')\n }\n\n let key = ''\n try {\n key = config('app.key')\n } catch {/** */ }\n\n if (!key) {\n throw new ConfigException('APP_KEY and app.key', 'any', this)\n }\n const url = this.toString()\n const queryParams: Record<string, unknown> = { ...this._query }\n\n if (expiration) {\n queryParams.expires = Math.floor(expiration / 1000)\n }\n\n // Create signature payload\n const payload = expiration\n ? `${url}?expires=${queryParams.expires}`\n : url\n\n const signature = hmac(payload, key)\n queryParams.signature = signature\n\n return this.withQuery(queryParams)\n }\n\n /**\n * Verify if a URL signature is valid\n */\n hasValidSignature (app?: Application): boolean {\n const appInstance = app || this.app\n if (!appInstance) {\n return false\n }\n\n const signature = this._query.signature\n if (!signature) {\n return false\n }\n\n // Check expiration if present\n if (this._query.expires !== undefined && this._query.expires !== null) {\n const expiresStr = String(this._query.expires)\n const expirationTime = parseInt(expiresStr, 10) * 1000\n if (isNaN(expirationTime) || Date.now() > expirationTime) {\n return false\n }\n }\n\n // Recreate URL without signature for verification\n const queryWithoutSignature = { ...this._query }\n delete queryWithoutSignature.signature\n\n const urlWithoutSignature = new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n this._path,\n queryWithoutSignature,\n this._fragment\n ).toString()\n\n const payload = this._query.expires\n ? `${urlWithoutSignature}?expires=${this._query.expires}`\n : urlWithoutSignature\n\n let key = ''\n try {\n key = config('app.key', 'default-key')\n } catch {/** */ }\n const expectedSignature = hmac(payload, key)\n\n return signature === expectedSignature\n }\n\n /**\n * Convert the URL to its string representation\n */\n toString (): string {\n let url = ''\n\n // Add scheme and host\n if (this._scheme && this._host) {\n url += `${this._scheme}://${this._host}`\n\n // Add port if specified and not default\n if (this._port &&\n !((this._scheme === 'http' && this._port === 80) ||\n (this._scheme === 'https' && this._port === 443))) {\n url += `:${this._port}`\n }\n }\n\n // Add path\n if (this._path) {\n if (!this._path.startsWith('/')) {\n url += '/'\n }\n url += this._path\n }\n\n // Add query parameters\n const queryEntries = Object.entries(this._query)\n if (queryEntries.length > 0) {\n const queryString = queryEntries\n .map(([key, value]) => {\n if (Array.isArray(value)) {\n return value.map(v => `${encodeURIComponent(key)}%5B%5D=${encodeURIComponent(v)}`).join('&')\n }\n return `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`\n })\n .join('&')\n url += `?${queryString}`\n }\n\n // Add fragment\n if (this._fragment) {\n url += `#${this._fragment}`\n }\n\n return url\n }\n\n /**\n * Get the scheme\n */\n getScheme (): string | undefined {\n return this._scheme\n }\n\n /**\n * Get the host\n */\n getHost (): string | undefined {\n return this._host\n }\n\n /**\n * Get the port\n */\n getPort (): number | undefined {\n return this._port\n }\n\n /**\n * Get the path\n */\n getPath (): string {\n return this._path\n }\n\n /**\n * Get the query parameters\n */\n getQuery (): Record<string, unknown> {\n return { ...this._query }\n }\n\n /**\n * Get the fragment\n */\n getFragment (): string | undefined {\n return this._fragment\n }\n}\n","import { Application } from '@h3ravel/core'\nimport { HelpersContract } from './Contracts/UrlContract'\nimport { RequestAwareHelpers } from './RequestAwareHelpers'\nimport { Url } from './Url'\nimport { ExtractControllerMethods } from '@h3ravel/shared'\n\n/**\n * Global helper functions for URL manipulation\n */\n\n/**\n * Create a URL from a path relative to the app URL\n */\nexport function to (\n path: string,\n app?: Application\n): Url {\n return Url.to(path, app)\n}\n\n/**\n * Create a URL from a named route\n */\nexport function route<TName extends string, TParams extends Record<string, string> = Record<string, string>> (\n name: TName,\n params: TParams = {} as TParams,\n app?: Application\n): Url {\n return Url.route<TName, TParams>(name, params, app)\n}\n\n/**\n * Create a signed URL from a named route\n */\nexport function signedRoute<TName extends string, TParams extends Record<string, string> = Record<string, string>> (\n name: TName,\n params: TParams = {} as TParams,\n app?: Application\n): Url {\n return Url.signedRoute<TName, TParams>(name, params, app)\n}\n\n/**\n * Create a temporary signed URL from a named route\n */\nexport function temporarySignedRoute (\n name: string,\n params: Record<string, string> = {},\n expiration: number,\n app?: Application\n): Url {\n return Url.temporarySignedRoute(name, params, expiration, app)\n}\n\n/**\n * Create a URL from a controller action\n */\nexport function action (\n controller: string,\n app?: Application\n): Url {\n return Url.action(controller, app)\n}\n\n/**\n * Get request-aware URL helpers\n */\nexport function url (app?: Application): RequestAwareHelpers {\n if (!app) throw new Error('Application instance required for request-aware URL helpers')\n return new RequestAwareHelpers(app)\n}\n\n/**\n * Create URL helpers that are bound to an application instance\n */\nexport function createUrlHelpers (app: Application): HelpersContract {\n return {\n /**\n * Create a URL from a path relative to the app URL\n */\n to: (path: string) => Url.to(path, app),\n\n /**\n * Create a URL from a named route\n */\n route: (\n name: string,\n params: Record<string, any> = {}\n ) => Url.route(name, params, app).toString(),\n\n /**\n * Create a signed URL from a named route\n */\n signedRoute: (\n name: string,\n params: Record<string, any> = {}\n ) => Url.signedRoute(name, params, app),\n\n /**\n * Create a temporary signed URL from a named route\n */\n temporarySignedRoute: (\n name: string,\n params: Record<string, any> = {},\n expiration: number\n ) => Url.temporarySignedRoute(name, params, expiration, app),\n\n /**\n * Create a URL from a controller action\n */\n action: <C extends new (...args: any) => any> (\n controller: string | [C, methodName: ExtractControllerMethods<InstanceType<C>>],\n params?: Record<string, any>\n ) => Url.action(controller, params, app).toString(),\n\n /**\n * Get request-aware URL helpers\n */\n url: (path?: string) => {\n if (path) {\n return Url.to(path).toString() as never\n }\n return new RequestAwareHelpers(app) as never\n }\n }\n}\n","/// <reference path=\"../app.globals.d.ts\" />\nimport { ServiceProvider } from '@h3ravel/core'\nimport { Url } from '../Url'\nimport { createUrlHelper } from '../RequestAwareHelpers'\nimport { createUrlHelpers } from '../Helpers'\n\n/**\n * Service provider for URL utilities\n */\nexport class UrlServiceProvider extends ServiceProvider {\n public static priority = 897\n\n /**\n * Register URL services in the container\n */\n register (): void {\n // Register the Url class\n this.app.singleton('app.url', () => Url)\n // Register the url() helper function\n this.app.singleton('app.url.helper', () => createUrlHelper(this.app))\n\n // Register bound URL helpers\n this.app.singleton('app.url.helpers', () => createUrlHelpers(this.app))\n\n // Make url() globally available\n if (typeof globalThis !== 'undefined') {\n const helpers = createUrlHelpers(this.app)\n\n Object.assign(globalThis, {\n url: helpers.url,\n route: helpers.route,\n action: helpers.action,\n })\n }\n }\n\n /**\n * Boot URL services\n */\n boot (): void {\n // Any additional setup can be done here\n }\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -284,6 +284,7 @@ declare function createUrlHelpers(app: Application): HelpersContract;
|
|
|
284
284
|
* Service provider for URL utilities
|
|
285
285
|
*/
|
|
286
286
|
declare class UrlServiceProvider extends ServiceProvider {
|
|
287
|
+
static priority: number;
|
|
287
288
|
/**
|
|
288
289
|
* Register URL services in the container
|
|
289
290
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -284,6 +284,7 @@ declare function createUrlHelpers(app: Application): HelpersContract;
|
|
|
284
284
|
* Service provider for URL utilities
|
|
285
285
|
*/
|
|
286
286
|
declare class UrlServiceProvider extends ServiceProvider {
|
|
287
|
+
static priority: number;
|
|
287
288
|
/**
|
|
288
289
|
* Register URL services in the container
|
|
289
290
|
*/
|
package/dist/index.js
CHANGED
|
@@ -220,8 +220,7 @@ var Url = class Url {
|
|
|
220
220
|
const url$1 = this.toString();
|
|
221
221
|
const queryParams = { ...this._query };
|
|
222
222
|
if (expiration) queryParams.expires = Math.floor(expiration / 1e3);
|
|
223
|
-
|
|
224
|
-
queryParams.signature = hmac(payload, key);
|
|
223
|
+
queryParams.signature = hmac(expiration ? `${url$1}?expires=${queryParams.expires}` : url$1, key);
|
|
225
224
|
return this.withQuery(queryParams);
|
|
226
225
|
}
|
|
227
226
|
/**
|
|
@@ -244,8 +243,7 @@ var Url = class Url {
|
|
|
244
243
|
try {
|
|
245
244
|
key = config("app.key", "default-key");
|
|
246
245
|
} catch {}
|
|
247
|
-
|
|
248
|
-
return signature === expectedSignature;
|
|
246
|
+
return signature === hmac(payload, key);
|
|
249
247
|
}
|
|
250
248
|
/**
|
|
251
249
|
* Convert the URL to its string representation
|
|
@@ -374,6 +372,7 @@ function createUrlHelpers(app) {
|
|
|
374
372
|
* Service provider for URL utilities
|
|
375
373
|
*/
|
|
376
374
|
var UrlServiceProvider = class extends ServiceProvider {
|
|
375
|
+
static priority = 897;
|
|
377
376
|
/**
|
|
378
377
|
* Register URL services in the container
|
|
379
378
|
*/
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["app: Application","path","url","query: Record<string, unknown>","routes: RouteDefinition[]","route","queryParams: Record<string, unknown>","path"],"sources":["../src/RequestAwareHelpers.ts","../src/Url.ts","../src/Helpers.ts","../src/Providers/UrlServiceProvider.ts"],"sourcesContent":["import type { Application } from '@h3ravel/core'\nimport type { IRequest } from '@h3ravel/shared'\nimport { RouteParams } from './Contracts/UrlContract'\n\n/**\n * Request-aware URL helper class\n */\nexport class RequestAwareHelpers {\n private readonly baseUrl: string = ''\n\n constructor(private app: Application) {\n try {\n this.baseUrl = config('app.url', 'http://localhost:3000')\n } catch {/** */ }\n }\n\n /**\n * Get the current request instance\n */\n private getCurrentRequest (): IRequest {\n const request = this.app.make('http.request')\n if (!request) {\n throw new Error('Request instance not available in current context')\n }\n return request\n }\n\n /**\n * Get the current request URL (path only, no query string)\n */\n current (): string {\n const request = this.getCurrentRequest()\n const event = request.getEvent()\n\n // Get the path from the request\n const raw = event.req.url ?? '/'\n const url = new URL(raw, 'http://localhost')\n return url.pathname\n }\n\n /**\n * Get the full current URL with query string\n */\n full (): string {\n const request = this.getCurrentRequest()\n const event = request.getEvent()\n\n // Get the full URL including query string\n const requestUrl = event.req.url ?? '/'\n\n // If requestUrl is already absolute, use it directly, otherwise combine with baseUrl\n if (requestUrl.startsWith('http')) {\n return requestUrl\n }\n\n const fullUrl = new URL(requestUrl, this.baseUrl)\n return fullUrl.toString()\n }\n\n /**\n * Get the previous request URL from session or referrer\n */\n previous (): string {\n const request = this.getCurrentRequest()\n const event = request.getEvent()\n\n // Try to get from session first (if session is available)\n // For now, fallback to HTTP referrer header\n const headers = (event as any)?.node?.req?.headers as Record<string, string | string[] | undefined> | undefined\n // console.log(headers)\n let referrer = headers?.referer ?? headers?.referrer\n if (Array.isArray(referrer)) referrer = referrer[0]\n if (referrer) return referrer\n\n // Fallback to current URL if no referrer\n return this.current()\n }\n\n /**\n * Get the previous request path (without query string)\n */\n previousPath (): string {\n const previousUrl = this.previous()\n\n try {\n const url = new URL(previousUrl)\n return url.pathname\n } catch {\n // If previous URL is not a valid URL, return as-is\n return previousUrl\n }\n }\n\n /**\n * Get the current query parameters\n */\n query (): RouteParams {\n const request = this.getCurrentRequest()\n return request.query || {}\n }\n}\n\n/**\n * Global helper function factory\n */\nexport function createUrlHelper (app: Application): () => RequestAwareHelpers {\n return () => new RequestAwareHelpers(app)\n}\n","import { ConfigException, type Application } from '@h3ravel/core'\nimport { RouteParams } from './Contracts/UrlContract'\nimport { hmac } from '@h3ravel/support'\nimport { RouteDefinition, ExtractControllerMethods } from '@h3ravel/shared'\nimport path from 'node:path'\n\n/**\n * URL builder class with fluent API and request-aware helpers\n */\nexport class Url {\n private readonly _scheme?: string\n private readonly _host?: string\n private readonly _port?: number\n private readonly _path: string\n private readonly _query: Record<string, unknown>\n private readonly _fragment?: string\n private readonly app?: Application\n\n private constructor(\n app?: Application,\n scheme?: string,\n host?: string,\n port?: number,\n path: string = '/',\n query: Record<string, unknown> = {},\n fragment?: string\n ) {\n this.app = app\n this._scheme = scheme\n this._host = host\n this._port = port\n this._path = path.startsWith('/') ? path : `/${path}`\n this._query = { ...query }\n this._fragment = fragment\n }\n\n /**\n * Create a URL from a full URL string\n */\n static of (url: string, app?: Application): Url {\n try {\n const parsed = new URL(url)\n const query: Record<string, unknown> = {}\n\n // Parse query parameters\n parsed.searchParams.forEach((value, key) => {\n query[key] = value\n })\n\n return new Url(\n app,\n parsed.protocol.replace(':', ''),\n parsed.hostname,\n parsed.port ? parseInt(parsed.port) : undefined,\n parsed.pathname || '/',\n query,\n parsed.hash ? parsed.hash.substring(1) : undefined\n )\n } catch {\n throw new Error(`Invalid URL: ${url}`)\n }\n }\n\n /**\n * Create a URL from a path relative to the app URL\n */\n static to (path: string, app?: Application): Url {\n let baseUrl = ''\n try {\n baseUrl = config('app.url', 'http://localhost:3000')\n } catch {/** */ }\n\n const fullUrl = new URL(path, baseUrl).toString()\n\n return Url.of(fullUrl, app)\n }\n\n /**\n * Create a URL from a named route\n */\n // Route parameter map (declaration-mergeable by consumers)\n static route<TName extends string = string, TParams extends RouteParams = RouteParams> (\n name: TName,\n params: TParams = {} as TParams,\n app?: Application\n ): Url {\n if (!app) {\n throw new Error('Application instance required for route generation')\n }\n\n // Use (app as any).make to avoid TS error if make is not typed on Application\n const router = app.make('router')\n if (!router || typeof router.route !== 'function') {\n throw new Error('Router not available or does not support route generation')\n }\n\n if (typeof router.route !== 'function') {\n throw new Error('Router does not support route generation')\n }\n\n const routeUrl = router.route(name, params)\n if (!routeUrl) {\n throw new Error(`Route \"${name}\" not found`)\n }\n\n return Url.to(routeUrl, app)\n }\n\n /**\n * Create a signed URL from a named route\n */\n static signedRoute<TName extends string = string, TParams extends RouteParams = RouteParams> (\n name: TName,\n params: TParams = {} as TParams,\n app?: Application\n ): Url {\n const url = Url.route<TName, TParams>(name, params, app)\n return url.withSignature(app)\n }\n\n /**\n * Create a temporary signed URL from a named route\n */\n static temporarySignedRoute<TName extends string = string, TParams extends RouteParams = RouteParams> (\n name: TName,\n params: TParams = {} as TParams,\n expiration: number,\n app?: Application\n ): Url {\n const url = Url.route<TName, TParams>(name, params, app)\n return url.withSignature(app, expiration)\n }\n\n /**\n * Create a URL from a controller action\n */\n static action<C extends new (...args: any) => any> (\n controller: string | [C, methodName: ExtractControllerMethods<InstanceType<C>>],\n params?: Record<string, any>,\n app?: Application\n ): Url {\n if (!app) throw new Error('Application instance required for action URL generation')\n\n const [controllerName, methodName = 'index'] = typeof controller === 'string'\n ? controller.split('@')\n : controller\n\n const cname = typeof controllerName === 'string' ? controllerName : controllerName.name\n\n const routes: RouteDefinition[] = app.make('app.routes')\n\n if (!Array.isArray(routes)) {\n // Backward-compatible message expected by existing tests\n throw new Error('Action URL generation requires router integration - not yet implemented')\n }\n\n if (routes.length < 1) throw new Error(`No routes available to resolve action: ${controller}`)\n\n // Search for for the \n const found = routes.find(route => {\n return route.signature?.[0] === cname && (route.signature?.[1] || 'index') === methodName\n })\n\n if (!found) throw new Error(`No route found for ${cname}`)\n\n // Build the route parameters\n const _params = Object.values(params ?? {}).join('/')\n\n if (_params) {\n return Url.to(path.join(found.path, _params))\n }\n\n return Url.to(found.path, app)\n }\n\n /**\n * Set the scheme (protocol) of the URL\n */\n withScheme (scheme: string): Url {\n return new Url(\n this.app,\n scheme,\n this._host,\n this._port,\n this._path,\n this._query,\n this._fragment\n )\n }\n\n /**\n * Set the host of the URL\n */\n withHost (host: string): Url {\n return new Url(\n this.app,\n this._scheme,\n host,\n this._port,\n this._path,\n this._query,\n this._fragment\n )\n }\n\n /**\n * Set the port of the URL\n */\n withPort (port: number): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n port,\n this._path,\n this._query,\n this._fragment\n )\n }\n\n /**\n * Set the path of the URL\n */\n withPath (path: string): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n path,\n this._query,\n this._fragment\n )\n }\n\n /**\n * Set the query parameters of the URL\n */\n withQuery (query: Record<string, unknown>): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n this._path,\n { ...query },\n this._fragment\n )\n }\n\n /**\n * Merge additional query parameters\n */\n withQueryParams (params: Record<string, unknown>): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n this._path,\n { ...this._query, ...params },\n this._fragment\n )\n }\n\n /**\n * Set the fragment (hash) of the URL\n */\n withFragment (fragment: string): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n this._path,\n this._query,\n fragment\n )\n }\n\n /**\n * Add a signature to the URL for security\n */\n withSignature (app?: Application, expiration?: number): Url {\n const appInstance = app || this.app\n if (!appInstance) {\n throw new Error('Application instance required for URL signing')\n }\n\n let key = ''\n try {\n key = config('app.key')\n } catch {/** */ }\n\n if (!key) {\n throw new ConfigException('APP_KEY and app.key', 'any', this)\n }\n const url = this.toString()\n const queryParams: Record<string, unknown> = { ...this._query }\n\n if (expiration) {\n queryParams.expires = Math.floor(expiration / 1000)\n }\n\n // Create signature payload\n const payload = expiration\n ? `${url}?expires=${queryParams.expires}`\n : url\n\n const signature = hmac(payload, key)\n queryParams.signature = signature\n\n return this.withQuery(queryParams)\n }\n\n /**\n * Verify if a URL signature is valid\n */\n hasValidSignature (app?: Application): boolean {\n const appInstance = app || this.app\n if (!appInstance) {\n return false\n }\n\n const signature = this._query.signature\n if (!signature) {\n return false\n }\n\n // Check expiration if present\n if (this._query.expires !== undefined && this._query.expires !== null) {\n const expiresStr = String(this._query.expires)\n const expirationTime = parseInt(expiresStr, 10) * 1000\n if (isNaN(expirationTime) || Date.now() > expirationTime) {\n return false\n }\n }\n\n // Recreate URL without signature for verification\n const queryWithoutSignature = { ...this._query }\n delete queryWithoutSignature.signature\n\n const urlWithoutSignature = new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n this._path,\n queryWithoutSignature,\n this._fragment\n ).toString()\n\n const payload = this._query.expires\n ? `${urlWithoutSignature}?expires=${this._query.expires}`\n : urlWithoutSignature\n\n let key = ''\n try {\n key = config('app.key', 'default-key')\n } catch {/** */ }\n const expectedSignature = hmac(payload, key)\n\n return signature === expectedSignature\n }\n\n /**\n * Convert the URL to its string representation\n */\n toString (): string {\n let url = ''\n\n // Add scheme and host\n if (this._scheme && this._host) {\n url += `${this._scheme}://${this._host}`\n\n // Add port if specified and not default\n if (this._port &&\n !((this._scheme === 'http' && this._port === 80) ||\n (this._scheme === 'https' && this._port === 443))) {\n url += `:${this._port}`\n }\n }\n\n // Add path\n if (this._path) {\n if (!this._path.startsWith('/')) {\n url += '/'\n }\n url += this._path\n }\n\n // Add query parameters\n const queryEntries = Object.entries(this._query)\n if (queryEntries.length > 0) {\n const queryString = queryEntries\n .map(([key, value]) => {\n if (Array.isArray(value)) {\n return value.map(v => `${encodeURIComponent(key)}%5B%5D=${encodeURIComponent(v)}`).join('&')\n }\n return `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`\n })\n .join('&')\n url += `?${queryString}`\n }\n\n // Add fragment\n if (this._fragment) {\n url += `#${this._fragment}`\n }\n\n return url\n }\n\n /**\n * Get the scheme\n */\n getScheme (): string | undefined {\n return this._scheme\n }\n\n /**\n * Get the host\n */\n getHost (): string | undefined {\n return this._host\n }\n\n /**\n * Get the port\n */\n getPort (): number | undefined {\n return this._port\n }\n\n /**\n * Get the path\n */\n getPath (): string {\n return this._path\n }\n\n /**\n * Get the query parameters\n */\n getQuery (): Record<string, unknown> {\n return { ...this._query }\n }\n\n /**\n * Get the fragment\n */\n getFragment (): string | undefined {\n return this._fragment\n }\n}\n","import { Application } from '@h3ravel/core'\nimport { HelpersContract } from './Contracts/UrlContract'\nimport { RequestAwareHelpers } from './RequestAwareHelpers'\nimport { Url } from './Url'\nimport { ExtractControllerMethods } from '@h3ravel/shared'\n\n/**\n * Global helper functions for URL manipulation\n */\n\n/**\n * Create a URL from a path relative to the app URL\n */\nexport function to (\n path: string,\n app?: Application\n): Url {\n return Url.to(path, app)\n}\n\n/**\n * Create a URL from a named route\n */\nexport function route<TName extends string, TParams extends Record<string, string> = Record<string, string>> (\n name: TName,\n params: TParams = {} as TParams,\n app?: Application\n): Url {\n return Url.route<TName, TParams>(name, params, app)\n}\n\n/**\n * Create a signed URL from a named route\n */\nexport function signedRoute<TName extends string, TParams extends Record<string, string> = Record<string, string>> (\n name: TName,\n params: TParams = {} as TParams,\n app?: Application\n): Url {\n return Url.signedRoute<TName, TParams>(name, params, app)\n}\n\n/**\n * Create a temporary signed URL from a named route\n */\nexport function temporarySignedRoute (\n name: string,\n params: Record<string, string> = {},\n expiration: number,\n app?: Application\n): Url {\n return Url.temporarySignedRoute(name, params, expiration, app)\n}\n\n/**\n * Create a URL from a controller action\n */\nexport function action (\n controller: string,\n app?: Application\n): Url {\n return Url.action(controller, app)\n}\n\n/**\n * Get request-aware URL helpers\n */\nexport function url (app?: Application): RequestAwareHelpers {\n if (!app) throw new Error('Application instance required for request-aware URL helpers')\n return new RequestAwareHelpers(app)\n}\n\n/**\n * Create URL helpers that are bound to an application instance\n */\nexport function createUrlHelpers (app: Application): HelpersContract {\n return {\n /**\n * Create a URL from a path relative to the app URL\n */\n to: (path: string) => Url.to(path, app),\n\n /**\n * Create a URL from a named route\n */\n route: (\n name: string,\n params: Record<string, any> = {}\n ) => Url.route(name, params, app).toString(),\n\n /**\n * Create a signed URL from a named route\n */\n signedRoute: (\n name: string,\n params: Record<string, any> = {}\n ) => Url.signedRoute(name, params, app),\n\n /**\n * Create a temporary signed URL from a named route\n */\n temporarySignedRoute: (\n name: string,\n params: Record<string, any> = {},\n expiration: number\n ) => Url.temporarySignedRoute(name, params, expiration, app),\n\n /**\n * Create a URL from a controller action\n */\n action: <C extends new (...args: any) => any> (\n controller: string | [C, methodName: ExtractControllerMethods<InstanceType<C>>],\n params?: Record<string, any>\n ) => Url.action(controller, params, app).toString(),\n\n /**\n * Get request-aware URL helpers\n */\n url: (path?: string) => {\n if (path) {\n return Url.to(path).toString() as never\n }\n return new RequestAwareHelpers(app) as never\n }\n }\n}\n","/// <reference path=\"../app.globals.d.ts\" />\nimport { ServiceProvider } from '@h3ravel/core'\nimport { Url } from '../Url'\nimport { createUrlHelper } from '../RequestAwareHelpers'\nimport { createUrlHelpers } from '../Helpers'\n\n/**\n * Service provider for URL utilities\n */\nexport class UrlServiceProvider extends ServiceProvider {\n /**\n * Register URL services in the container\n */\n register (): void {\n // Register the Url class\n this.app.singleton('app.url', () => Url)\n // Register the url() helper function\n this.app.singleton('app.url.helper', () => createUrlHelper(this.app))\n\n // Register bound URL helpers\n this.app.singleton('app.url.helpers', () => createUrlHelpers(this.app))\n\n // Make url() globally available\n if (typeof globalThis !== 'undefined') {\n const helpers = createUrlHelpers(this.app)\n\n Object.assign(globalThis, {\n url: helpers.url,\n route: helpers.route,\n action: helpers.action,\n })\n }\n }\n\n /**\n * Boot URL services\n */\n boot (): void {\n // Any additional setup can be done here\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["app: Application","path","url","query: Record<string, unknown>","routes: RouteDefinition[]","route","queryParams: Record<string, unknown>","path"],"sources":["../src/RequestAwareHelpers.ts","../src/Url.ts","../src/Helpers.ts","../src/Providers/UrlServiceProvider.ts"],"sourcesContent":["import type { Application } from '@h3ravel/core'\nimport type { IRequest } from '@h3ravel/shared'\nimport { RouteParams } from './Contracts/UrlContract'\n\n/**\n * Request-aware URL helper class\n */\nexport class RequestAwareHelpers {\n private readonly baseUrl: string = ''\n\n constructor(private app: Application) {\n try {\n this.baseUrl = config('app.url', 'http://localhost:3000')\n } catch {/** */ }\n }\n\n /**\n * Get the current request instance\n */\n private getCurrentRequest (): IRequest {\n const request = this.app.make('http.request')\n if (!request) {\n throw new Error('Request instance not available in current context')\n }\n return request\n }\n\n /**\n * Get the current request URL (path only, no query string)\n */\n current (): string {\n const request = this.getCurrentRequest()\n const event = request.getEvent()\n\n // Get the path from the request\n const raw = event.req.url ?? '/'\n const url = new URL(raw, 'http://localhost')\n return url.pathname\n }\n\n /**\n * Get the full current URL with query string\n */\n full (): string {\n const request = this.getCurrentRequest()\n const event = request.getEvent()\n\n // Get the full URL including query string\n const requestUrl = event.req.url ?? '/'\n\n // If requestUrl is already absolute, use it directly, otherwise combine with baseUrl\n if (requestUrl.startsWith('http')) {\n return requestUrl\n }\n\n const fullUrl = new URL(requestUrl, this.baseUrl)\n return fullUrl.toString()\n }\n\n /**\n * Get the previous request URL from session or referrer\n */\n previous (): string {\n const request = this.getCurrentRequest()\n const event = request.getEvent()\n\n // Try to get from session first (if session is available)\n // For now, fallback to HTTP referrer header\n const headers = (event as any)?.node?.req?.headers as Record<string, string | string[] | undefined> | undefined\n // console.log(headers)\n let referrer = headers?.referer ?? headers?.referrer\n if (Array.isArray(referrer)) referrer = referrer[0]\n if (referrer) return referrer\n\n // Fallback to current URL if no referrer\n return this.current()\n }\n\n /**\n * Get the previous request path (without query string)\n */\n previousPath (): string {\n const previousUrl = this.previous()\n\n try {\n const url = new URL(previousUrl)\n return url.pathname\n } catch {\n // If previous URL is not a valid URL, return as-is\n return previousUrl\n }\n }\n\n /**\n * Get the current query parameters\n */\n query (): RouteParams {\n const request = this.getCurrentRequest()\n return request.query || {}\n }\n}\n\n/**\n * Global helper function factory\n */\nexport function createUrlHelper (app: Application): () => RequestAwareHelpers {\n return () => new RequestAwareHelpers(app)\n}\n","import { ConfigException, type Application } from '@h3ravel/core'\nimport { RouteParams } from './Contracts/UrlContract'\nimport { hmac } from '@h3ravel/support'\nimport { RouteDefinition, ExtractControllerMethods } from '@h3ravel/shared'\nimport path from 'node:path'\n\n/**\n * URL builder class with fluent API and request-aware helpers\n */\nexport class Url {\n private readonly _scheme?: string\n private readonly _host?: string\n private readonly _port?: number\n private readonly _path: string\n private readonly _query: Record<string, unknown>\n private readonly _fragment?: string\n private readonly app?: Application\n\n private constructor(\n app?: Application,\n scheme?: string,\n host?: string,\n port?: number,\n path: string = '/',\n query: Record<string, unknown> = {},\n fragment?: string\n ) {\n this.app = app\n this._scheme = scheme\n this._host = host\n this._port = port\n this._path = path.startsWith('/') ? path : `/${path}`\n this._query = { ...query }\n this._fragment = fragment\n }\n\n /**\n * Create a URL from a full URL string\n */\n static of (url: string, app?: Application): Url {\n try {\n const parsed = new URL(url)\n const query: Record<string, unknown> = {}\n\n // Parse query parameters\n parsed.searchParams.forEach((value, key) => {\n query[key] = value\n })\n\n return new Url(\n app,\n parsed.protocol.replace(':', ''),\n parsed.hostname,\n parsed.port ? parseInt(parsed.port) : undefined,\n parsed.pathname || '/',\n query,\n parsed.hash ? parsed.hash.substring(1) : undefined\n )\n } catch {\n throw new Error(`Invalid URL: ${url}`)\n }\n }\n\n /**\n * Create a URL from a path relative to the app URL\n */\n static to (path: string, app?: Application): Url {\n let baseUrl = ''\n try {\n baseUrl = config('app.url', 'http://localhost:3000')\n } catch {/** */ }\n\n const fullUrl = new URL(path, baseUrl).toString()\n\n return Url.of(fullUrl, app)\n }\n\n /**\n * Create a URL from a named route\n */\n // Route parameter map (declaration-mergeable by consumers)\n static route<TName extends string = string, TParams extends RouteParams = RouteParams> (\n name: TName,\n params: TParams = {} as TParams,\n app?: Application\n ): Url {\n if (!app) {\n throw new Error('Application instance required for route generation')\n }\n\n // Use (app as any).make to avoid TS error if make is not typed on Application\n const router = app.make('router')\n if (!router || typeof router.route !== 'function') {\n throw new Error('Router not available or does not support route generation')\n }\n\n if (typeof router.route !== 'function') {\n throw new Error('Router does not support route generation')\n }\n\n const routeUrl = router.route(name, params)\n if (!routeUrl) {\n throw new Error(`Route \"${name}\" not found`)\n }\n\n return Url.to(routeUrl, app)\n }\n\n /**\n * Create a signed URL from a named route\n */\n static signedRoute<TName extends string = string, TParams extends RouteParams = RouteParams> (\n name: TName,\n params: TParams = {} as TParams,\n app?: Application\n ): Url {\n const url = Url.route<TName, TParams>(name, params, app)\n return url.withSignature(app)\n }\n\n /**\n * Create a temporary signed URL from a named route\n */\n static temporarySignedRoute<TName extends string = string, TParams extends RouteParams = RouteParams> (\n name: TName,\n params: TParams = {} as TParams,\n expiration: number,\n app?: Application\n ): Url {\n const url = Url.route<TName, TParams>(name, params, app)\n return url.withSignature(app, expiration)\n }\n\n /**\n * Create a URL from a controller action\n */\n static action<C extends new (...args: any) => any> (\n controller: string | [C, methodName: ExtractControllerMethods<InstanceType<C>>],\n params?: Record<string, any>,\n app?: Application\n ): Url {\n if (!app) throw new Error('Application instance required for action URL generation')\n\n const [controllerName, methodName = 'index'] = typeof controller === 'string'\n ? controller.split('@')\n : controller\n\n const cname = typeof controllerName === 'string' ? controllerName : controllerName.name\n\n const routes: RouteDefinition[] = app.make('app.routes')\n\n if (!Array.isArray(routes)) {\n // Backward-compatible message expected by existing tests\n throw new Error('Action URL generation requires router integration - not yet implemented')\n }\n\n if (routes.length < 1) throw new Error(`No routes available to resolve action: ${controller}`)\n\n // Search for for the \n const found = routes.find(route => {\n return route.signature?.[0] === cname && (route.signature?.[1] || 'index') === methodName\n })\n\n if (!found) throw new Error(`No route found for ${cname}`)\n\n // Build the route parameters\n const _params = Object.values(params ?? {}).join('/')\n\n if (_params) {\n return Url.to(path.join(found.path, _params))\n }\n\n return Url.to(found.path, app)\n }\n\n /**\n * Set the scheme (protocol) of the URL\n */\n withScheme (scheme: string): Url {\n return new Url(\n this.app,\n scheme,\n this._host,\n this._port,\n this._path,\n this._query,\n this._fragment\n )\n }\n\n /**\n * Set the host of the URL\n */\n withHost (host: string): Url {\n return new Url(\n this.app,\n this._scheme,\n host,\n this._port,\n this._path,\n this._query,\n this._fragment\n )\n }\n\n /**\n * Set the port of the URL\n */\n withPort (port: number): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n port,\n this._path,\n this._query,\n this._fragment\n )\n }\n\n /**\n * Set the path of the URL\n */\n withPath (path: string): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n path,\n this._query,\n this._fragment\n )\n }\n\n /**\n * Set the query parameters of the URL\n */\n withQuery (query: Record<string, unknown>): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n this._path,\n { ...query },\n this._fragment\n )\n }\n\n /**\n * Merge additional query parameters\n */\n withQueryParams (params: Record<string, unknown>): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n this._path,\n { ...this._query, ...params },\n this._fragment\n )\n }\n\n /**\n * Set the fragment (hash) of the URL\n */\n withFragment (fragment: string): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n this._path,\n this._query,\n fragment\n )\n }\n\n /**\n * Add a signature to the URL for security\n */\n withSignature (app?: Application, expiration?: number): Url {\n const appInstance = app || this.app\n if (!appInstance) {\n throw new Error('Application instance required for URL signing')\n }\n\n let key = ''\n try {\n key = config('app.key')\n } catch {/** */ }\n\n if (!key) {\n throw new ConfigException('APP_KEY and app.key', 'any', this)\n }\n const url = this.toString()\n const queryParams: Record<string, unknown> = { ...this._query }\n\n if (expiration) {\n queryParams.expires = Math.floor(expiration / 1000)\n }\n\n // Create signature payload\n const payload = expiration\n ? `${url}?expires=${queryParams.expires}`\n : url\n\n const signature = hmac(payload, key)\n queryParams.signature = signature\n\n return this.withQuery(queryParams)\n }\n\n /**\n * Verify if a URL signature is valid\n */\n hasValidSignature (app?: Application): boolean {\n const appInstance = app || this.app\n if (!appInstance) {\n return false\n }\n\n const signature = this._query.signature\n if (!signature) {\n return false\n }\n\n // Check expiration if present\n if (this._query.expires !== undefined && this._query.expires !== null) {\n const expiresStr = String(this._query.expires)\n const expirationTime = parseInt(expiresStr, 10) * 1000\n if (isNaN(expirationTime) || Date.now() > expirationTime) {\n return false\n }\n }\n\n // Recreate URL without signature for verification\n const queryWithoutSignature = { ...this._query }\n delete queryWithoutSignature.signature\n\n const urlWithoutSignature = new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n this._path,\n queryWithoutSignature,\n this._fragment\n ).toString()\n\n const payload = this._query.expires\n ? `${urlWithoutSignature}?expires=${this._query.expires}`\n : urlWithoutSignature\n\n let key = ''\n try {\n key = config('app.key', 'default-key')\n } catch {/** */ }\n const expectedSignature = hmac(payload, key)\n\n return signature === expectedSignature\n }\n\n /**\n * Convert the URL to its string representation\n */\n toString (): string {\n let url = ''\n\n // Add scheme and host\n if (this._scheme && this._host) {\n url += `${this._scheme}://${this._host}`\n\n // Add port if specified and not default\n if (this._port &&\n !((this._scheme === 'http' && this._port === 80) ||\n (this._scheme === 'https' && this._port === 443))) {\n url += `:${this._port}`\n }\n }\n\n // Add path\n if (this._path) {\n if (!this._path.startsWith('/')) {\n url += '/'\n }\n url += this._path\n }\n\n // Add query parameters\n const queryEntries = Object.entries(this._query)\n if (queryEntries.length > 0) {\n const queryString = queryEntries\n .map(([key, value]) => {\n if (Array.isArray(value)) {\n return value.map(v => `${encodeURIComponent(key)}%5B%5D=${encodeURIComponent(v)}`).join('&')\n }\n return `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`\n })\n .join('&')\n url += `?${queryString}`\n }\n\n // Add fragment\n if (this._fragment) {\n url += `#${this._fragment}`\n }\n\n return url\n }\n\n /**\n * Get the scheme\n */\n getScheme (): string | undefined {\n return this._scheme\n }\n\n /**\n * Get the host\n */\n getHost (): string | undefined {\n return this._host\n }\n\n /**\n * Get the port\n */\n getPort (): number | undefined {\n return this._port\n }\n\n /**\n * Get the path\n */\n getPath (): string {\n return this._path\n }\n\n /**\n * Get the query parameters\n */\n getQuery (): Record<string, unknown> {\n return { ...this._query }\n }\n\n /**\n * Get the fragment\n */\n getFragment (): string | undefined {\n return this._fragment\n }\n}\n","import { Application } from '@h3ravel/core'\nimport { HelpersContract } from './Contracts/UrlContract'\nimport { RequestAwareHelpers } from './RequestAwareHelpers'\nimport { Url } from './Url'\nimport { ExtractControllerMethods } from '@h3ravel/shared'\n\n/**\n * Global helper functions for URL manipulation\n */\n\n/**\n * Create a URL from a path relative to the app URL\n */\nexport function to (\n path: string,\n app?: Application\n): Url {\n return Url.to(path, app)\n}\n\n/**\n * Create a URL from a named route\n */\nexport function route<TName extends string, TParams extends Record<string, string> = Record<string, string>> (\n name: TName,\n params: TParams = {} as TParams,\n app?: Application\n): Url {\n return Url.route<TName, TParams>(name, params, app)\n}\n\n/**\n * Create a signed URL from a named route\n */\nexport function signedRoute<TName extends string, TParams extends Record<string, string> = Record<string, string>> (\n name: TName,\n params: TParams = {} as TParams,\n app?: Application\n): Url {\n return Url.signedRoute<TName, TParams>(name, params, app)\n}\n\n/**\n * Create a temporary signed URL from a named route\n */\nexport function temporarySignedRoute (\n name: string,\n params: Record<string, string> = {},\n expiration: number,\n app?: Application\n): Url {\n return Url.temporarySignedRoute(name, params, expiration, app)\n}\n\n/**\n * Create a URL from a controller action\n */\nexport function action (\n controller: string,\n app?: Application\n): Url {\n return Url.action(controller, app)\n}\n\n/**\n * Get request-aware URL helpers\n */\nexport function url (app?: Application): RequestAwareHelpers {\n if (!app) throw new Error('Application instance required for request-aware URL helpers')\n return new RequestAwareHelpers(app)\n}\n\n/**\n * Create URL helpers that are bound to an application instance\n */\nexport function createUrlHelpers (app: Application): HelpersContract {\n return {\n /**\n * Create a URL from a path relative to the app URL\n */\n to: (path: string) => Url.to(path, app),\n\n /**\n * Create a URL from a named route\n */\n route: (\n name: string,\n params: Record<string, any> = {}\n ) => Url.route(name, params, app).toString(),\n\n /**\n * Create a signed URL from a named route\n */\n signedRoute: (\n name: string,\n params: Record<string, any> = {}\n ) => Url.signedRoute(name, params, app),\n\n /**\n * Create a temporary signed URL from a named route\n */\n temporarySignedRoute: (\n name: string,\n params: Record<string, any> = {},\n expiration: number\n ) => Url.temporarySignedRoute(name, params, expiration, app),\n\n /**\n * Create a URL from a controller action\n */\n action: <C extends new (...args: any) => any> (\n controller: string | [C, methodName: ExtractControllerMethods<InstanceType<C>>],\n params?: Record<string, any>\n ) => Url.action(controller, params, app).toString(),\n\n /**\n * Get request-aware URL helpers\n */\n url: (path?: string) => {\n if (path) {\n return Url.to(path).toString() as never\n }\n return new RequestAwareHelpers(app) as never\n }\n }\n}\n","/// <reference path=\"../app.globals.d.ts\" />\nimport { ServiceProvider } from '@h3ravel/core'\nimport { Url } from '../Url'\nimport { createUrlHelper } from '../RequestAwareHelpers'\nimport { createUrlHelpers } from '../Helpers'\n\n/**\n * Service provider for URL utilities\n */\nexport class UrlServiceProvider extends ServiceProvider {\n public static priority = 897\n\n /**\n * Register URL services in the container\n */\n register (): void {\n // Register the Url class\n this.app.singleton('app.url', () => Url)\n // Register the url() helper function\n this.app.singleton('app.url.helper', () => createUrlHelper(this.app))\n\n // Register bound URL helpers\n this.app.singleton('app.url.helpers', () => createUrlHelpers(this.app))\n\n // Make url() globally available\n if (typeof globalThis !== 'undefined') {\n const helpers = createUrlHelpers(this.app)\n\n Object.assign(globalThis, {\n url: helpers.url,\n route: helpers.route,\n action: helpers.action,\n })\n }\n }\n\n /**\n * Boot URL services\n */\n boot (): void {\n // Any additional setup can be done here\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h3ravel/url",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Request-aware URI builder and URL manipulation utilities for H3ravel.",
|
|
5
|
+
"h3ravel": {
|
|
6
|
+
"providers": [
|
|
7
|
+
"UrlServiceProvider"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
5
10
|
"type": "module",
|
|
6
11
|
"main": "./dist/index.js",
|
|
7
12
|
"types": "./dist/index.d.ts",
|
|
@@ -39,12 +44,12 @@
|
|
|
39
44
|
"builder"
|
|
40
45
|
],
|
|
41
46
|
"dependencies": {
|
|
42
|
-
"@h3ravel/support": "^0.
|
|
43
|
-
"@h3ravel/shared": "^0.
|
|
47
|
+
"@h3ravel/support": "^0.14.1",
|
|
48
|
+
"@h3ravel/shared": "^0.22.2"
|
|
44
49
|
},
|
|
45
50
|
"peerDependencies": {
|
|
46
|
-
"@h3ravel/core": "^1.
|
|
47
|
-
"@h3ravel/http": "^11.3.
|
|
51
|
+
"@h3ravel/core": "^1.16.0",
|
|
52
|
+
"@h3ravel/http": "^11.3.3"
|
|
48
53
|
},
|
|
49
54
|
"devDependencies": {
|
|
50
55
|
"typescript": "^5.4.0"
|