@naverpay/nurl 1.0.3 → 1.1.0-canary.251230-92343a8

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 CHANGED
@@ -25,7 +25,7 @@ NURL is a powerful URL manipulation library that extends the standard URL class.
25
25
  ### Basic Usage
26
26
 
27
27
  ```javascript
28
- import { NURL } from 'nurl'
28
+ import {NURL} from 'nurl'
29
29
 
30
30
  // Create URL from string
31
31
  const url1 = new NURL('https://example.com/users/123?name=John')
@@ -36,9 +36,9 @@ const url2 = new NURL(standardUrl)
36
36
 
37
37
  // Create URL from custom options object
38
38
  const url3 = new NURL({
39
- baseUrl: 'https://example.com',
40
- pathname: '/users/:id',
41
- query: { id: '123', name: 'John' }
39
+ baseUrl: 'https://example.com',
40
+ pathname: '/users/:id',
41
+ query: {id: '123', name: 'John'},
42
42
  })
43
43
 
44
44
  // Create empty URL
@@ -49,9 +49,9 @@ const url5 = NURL.create('https://example.com')
49
49
 
50
50
  // The factory function also works with options object
51
51
  const url6 = NURL.create({
52
- baseUrl: 'https://example.com',
53
- pathname: '/users/:id',
54
- query: { id: '123', name: 'John' }
52
+ baseUrl: 'https://example.com',
53
+ pathname: '/users/:id',
54
+ query: {id: '123', name: 'John'},
55
55
  })
56
56
  ```
57
57
 
@@ -61,13 +61,13 @@ NURL processes dynamic segments in the pathname and replaces them with values fr
61
61
 
62
62
  ```javascript
63
63
  const url = new NURL({
64
- baseUrl: 'https://api.example.com',
65
- pathname: '/users/:a/posts/[b]/[c]',
66
- query: {
67
- a: '123',
68
- b: '456',
69
- format: 'json'
70
- }
64
+ baseUrl: 'https://api.example.com',
65
+ pathname: '/users/:a/posts/[b]/[c]',
66
+ query: {
67
+ a: '123',
68
+ b: '456',
69
+ format: 'json',
70
+ },
71
71
  })
72
72
 
73
73
  console.log(url.href)
@@ -84,6 +84,59 @@ console.log(url.hostname) // xn--bj0bj06e.xn--hq1bm8jm9l
84
84
  console.log(url.decodedHostname) // 한글.도메인 (in human-readable format)
85
85
  ```
86
86
 
87
+ ### URL Pattern Matching
88
+
89
+ NURL supports `NURL.match(url, pattern)` static method to match a URL path against a pattern with dynamic segments:
90
+
91
+ ```tsx
92
+ NURL.match('/v1/user/12345/info', '/v1/user/:userId/info')
93
+ // → { userId: '12345' }
94
+
95
+ NURL.match('/v1/friends/SENDMONEY/block/111/222', '/v1/friends/:serviceCode/block/:nidNo/:friendNidNo')
96
+ // → { serviceCode: 'SENDMONEY', nidNo: '111', friendNidNo: '222' }
97
+
98
+ NURL.match('/v1/user/12345', '/v1/admin/:id')
99
+ // → null (no match)
100
+ ```
101
+
102
+ ### Masking Path Parameters
103
+
104
+ NURL provides `NURL.mask(url, options)` static method to mask sensitive path parameters in a URL for logging purposes:
105
+
106
+ ```tsx
107
+ // Default masking (**** with length 4)
108
+ NURL.mask('/v1/user/12345/info', {
109
+ patterns: ['/v1/user/:userId/info'],
110
+ sensitiveParams: ['userId'],
111
+ })
112
+ // → '/v1/user/****/info'
113
+
114
+ // Custom mask character and length
115
+ NURL.mask('/v1/user/12345/info', {
116
+ patterns: ['/v1/user/[userId]/info'],
117
+ sensitiveParams: ['userId'],
118
+ maskChar: 'X',
119
+ maskLength: 6,
120
+ })
121
+ // → '/v1/user/XXXXXX/info'
122
+
123
+ // Preserve original value length
124
+ NURL.mask('/v1/user/12345/info', {
125
+ patterns: ['/v1/user/:userId/info'],
126
+ sensitiveParams: ['userId'],
127
+ preserveLength: true,
128
+ })
129
+ // → '/v1/user/*****/info' (5 chars, same as '12345')
130
+
131
+ // Multiple sensitive params
132
+ NURL.mask('/v1/friends/SENDMONEY/block/12345/67890', {
133
+ patterns: ['/v1/friends/:serviceCode/block/[nidNo]/:friendNidNo'],
134
+ sensitiveParams: ['nidNo', 'friendNidNo'],
135
+ preserveLength: true,
136
+ })
137
+ // → '/v1/friends/SENDMONEY/block/*****/*****' (5 and 5 chars)
138
+ ```
139
+
87
140
  ## API
88
141
 
89
142
  ### `constructor(input?: string | URL | URLOptions)`
@@ -113,6 +166,23 @@ NURL inherits all properties from the standard URL class:
113
166
  - `toString()`: Returns the URL as a string
114
167
  - `toJSON()`: Returns the URL as a JSON representation
115
168
 
169
+ ### Static Methods
170
+
171
+ - `NURL.create(input?: string | URL | URLOptions): NURL`
172
+ - Factory function to create a NURL instance without the `new` keyword.
173
+ - `NURL.canParse(url: string): boolean`
174
+ - Checks if the given string can be parsed as a valid URL.
175
+ - `NURL.match(url: string, pattern: string): Record<string, string> | null`
176
+ - Matches a URL path against a pattern with dynamic segments and returns an object with extracted parameters or `null` if no match.
177
+ - `NURL.mask(url: string, options: MaskOptions): string`
178
+ - Masks sensitive path parameters in a URL based on the provided options.
179
+ - `MaskOptions`:
180
+ - `patterns: string[]`: Array of URL patterns with dynamic segments.
181
+ - `sensitiveParams: string[]`: Array of path parameters to be masked.
182
+ - `maskChar?: string`: Character used for masking (default: `'*'`).
183
+ - `maskLength?: number`: Length of the mask (default: `4`).
184
+ - `preserveLength?: boolean`: If true, mask length matches original value length (overrides `maskLength`).
185
+
116
186
  ## Important Notes
117
187
 
118
188
  1. NURL's setter methods behave differently from the standard URL. They are designed to consider dynamic segment and query parameter replacement functionality.
@@ -5,6 +5,13 @@ interface URLOptions extends Partial<Pick<URL, 'href' | 'protocol' | 'host' | 'h
5
5
  query?: Query;
6
6
  basePath?: string;
7
7
  }
8
+ interface MaskOptions {
9
+ patterns: string[];
10
+ sensitiveParams: string[];
11
+ maskChar?: string;
12
+ maskLength?: number;
13
+ preserveLength?: boolean;
14
+ }
8
15
  declare class NURL implements URL {
9
16
  private _href;
10
17
  private _protocol;
@@ -72,6 +79,8 @@ declare class NURL implements URL {
72
79
  private encodeHostname;
73
80
  get decodedIDN(): string;
74
81
  get decodedHostname(): string;
82
+ static match(url: string, pattern: string): Record<string, string> | null;
83
+ static mask(url: string, { patterns, sensitiveParams, maskChar, maskLength, preserveLength }: MaskOptions): string;
75
84
  }
76
85
 
77
86
  export { NURL as default };
package/dist/cjs/nurl.js CHANGED
@@ -1 +1 @@
1
- "use strict";const m=require("./punycode.js"),r=require("./utils.js");class i{constructor(t){if(this._href="",this._protocol="",this._host="",this._hostname="",this._port="",this._pathname="",this._search="",this._hash="",this._origin="",this._username="",this._password="",this._baseUrl="",this._searchParams=new URLSearchParams,this._basePath="",this.punycodePrefix="xn--",this._searchParams=new URLSearchParams,typeof t=="string"||t instanceof URL)this.href=t.toString();else if(t){if(t.basePath&&(this._basePath=t.basePath.startsWith("/")?t.basePath:`/${t.basePath}`),t.baseUrl&&(this.baseUrl=t.baseUrl),t.href&&(this.href=t.href),t.protocol&&(this.protocol=t.protocol),t.host&&(this.host=t.host),t.hostname&&(this.hostname=t.hostname),t.port&&(this.port=t.port),t.pathname!==void 0&&(t.pathname===""?this._pathname="":this.pathname=r.refinePathnameWithQuery(t.pathname,t.query??{})),t.search&&(this.search=t.search),t.hash&&(this.hash=t.hash),t.username&&(this.username=t.username),t.password&&(this.password=t.password),t.query){const s=r.refineQueryWithPathname(t.pathname??"",t.query);Object.keys(s).length>0&&(this.search=new URLSearchParams(r.convertQueryToArray(s)).toString())}this.updateHref()}}static withBasePath(t){return s=>typeof s=="string"||s instanceof URL?new i({href:s.toString(),basePath:t}):new i({...s,basePath:t})}static create(t){return new i(t)}static canParse(t){if(t.startsWith("/"))return/^\/[^?#]*(\?[^#]*)?(#.*)?$/.test(t);try{return new URL(t),!0}catch{return/^[^:/?#]+(\.[^:/?#]+)+(\/[^?#]*(\?[^#]*)?(#.*)?)?$/.test(t)}}get baseUrl(){return this._baseUrl}set baseUrl(t){this._baseUrl=t;try{const s=new URL(t);this._protocol=s.protocol,this._host=s.host,this._hostname=s.hostname,this._port=s.port,this._origin=s.origin,this._username=s.username,this._password=s.password,s.pathname!=="/"&&(this._pathname=s.pathname),s.search&&(this._search=s.search,this._searchParams=new URLSearchParams(s.search)),s.hash&&(this._hash=s.hash),this.updateHref()}catch(s){console.warn(`Invalid baseUrl: ${t}`,s)}}get href(){return this._href}set href(t){try{const s=new URL(t);this._href=s.href,this._protocol=s.protocol,this._host=s.host,this._hostname=s.hostname,this._port=s.port,this._pathname=s.pathname,this._search=s.search,this._hash=s.hash,this._origin=s.origin,this._username=s.username,this._password=s.password,this._searchParams=s.searchParams}catch(s){const e=i.parseStringToURLLike(t);if(!e)throw console.warn(`Can not parse ${t}`),s;this._href=e.href,this._protocol=e.protocol,this._host=e.hostname,this._hostname=e.hostname,this._port=e.port,this._pathname=e.pathname,this._search=e.search,this._hash=e.hash,this._origin=e.origin,this._username=e.username,this._password=e.password,this._searchParams=e.searchParams}}static parseStringToURLLike(t){const s=/^(?:(https?:\/\/)(?:([^:@]+)(?::([^@]+))?@)?((?:[^/:?#]+)(?::(\d+))?)?)?([/][^?#]*)?(\?[^#]*)?(#.*)?$/,e=t.match(s),[h=t,a="",n="",d="",o="",c="",p="",_="",f=""]=e||[];if(!e||a&&!o&&!p&&!_&&!f)return null;const P=a&&o?`${a}//${o}${c?`:${c}`:""}`:"";return{href:h,protocol:a,host:o,hostname:o,port:c,pathname:t?p||"/":"",search:_,hash:f,origin:P,username:n,password:d,searchParams:new URLSearchParams(_)}}get protocol(){return this._protocol}set protocol(t){this._protocol=t,this.updateHref()}get host(){return this._host}set host(t){const[s,e]=t.split(":"),h=this.encodeHostname(s);this._host=e?`${h}:${e}`:h,this._hostname=h,this._port=e||"",this.updateHref()}get hostname(){return this._hostname}set hostname(t){const s=this.encodeHostname(t);this._hostname=s,this._host=this._port?`${s}:${this._port}`:s,this.updateHref()}get port(){return this._port}set port(t){this._port=t,this._host=`${this._hostname}${t?":"+t:""}`,this.updateHref()}get pathname(){return this._pathname}set pathname(t){let s=t;if(t===""){s="/",this._pathname=s,this.updateHref();return}this._basePath&&!s.startsWith(this._basePath)&&(s=`${this._basePath}${s.startsWith("/")?"":"/"}${s}`);const e=s.split("/").map(h=>r.isDynamicPath(h)?h:encodeURI(h)).join("/");this._pathname=e.startsWith("/")?e:`/${e}`,this.updateHref()}get search(){return this._search}set search(t){this._search=t.startsWith("?")?t:`?${t}`,this._searchParams=new URLSearchParams(t),this.updateHref()}setSearchParams(t){const s=new URLSearchParams(t);this._search=s.toString()?`?${s.toString()}`:"",this._searchParams=s,this.updateHref()}appendSearchParams(t){const s=new URLSearchParams(this._searchParams),e=r.getDynamicPaths(this._pathname).map(r.extractPathKey);Object.keys(t).forEach(h=>{e.includes(h)?this._pathname=r.refinePathnameWithQuery(this._pathname,{[h]:t[h]}):s.append(h,t[h])}),this._search=s.toString()?`?${s.toString()}`:"",this._searchParams=s,this.updateHref()}removeSearchParams(...t){const s=new URLSearchParams(this._searchParams);t.forEach(e=>{s.delete(e)}),this._search=s.toString()?`?${s.toString()}`:"",this._searchParams=s,this.updateHref()}get searchParams(){return new Proxy(this._searchParams,{get:(t,s,e)=>{const h=Reflect.get(t,s,e);return typeof h=="function"?(...a)=>{const n=h.apply(t,a);return this._search=this._searchParams.toString()?`?${this._searchParams.toString()}`:"",this.updateHref(),n}:h}})}get hash(){return this._hash}set hash(t){this._hash=t.startsWith("#")?t:`#${t}`,this.updateHref()}get origin(){return this._origin}get username(){return this._username}set username(t){this._username=t,this.updateHref()}get password(){return this._password}set password(t){this._password=t,this.updateHref()}updateHref(){const t=this._pathname;if(this._baseUrl){const s=new URL(this._baseUrl);s.pathname=t,s.search=this._search,s.hash=this._hash,this._href=s.href,this._origin=s.origin}else this._href=`${this._protocol}${this._protocol&&"//"}${this._username}${this._password?":"+this._password:""}${this._username||this._password?"@":""}${this._hostname}${this._port?":"+this._port:""}${t}${this._search}${this._hash}`,this._origin=`${this._protocol}//${this._hostname}${this._port?":"+this._port:""}`}toString(){return this.href}toJSON(){return this.href}encodeHostname(t){return t.split(".").map(s=>{for(const e of s)if(r.isASCIICodeChar(e))return`${this.punycodePrefix}${m.encode(s)}`;return s}).join(".")}get decodedIDN(){let t=this._href;return this._hostname.split(".").forEach(s=>{t=t.replace(s,m.decode(s.replace(this.punycodePrefix,"")))}),t}get decodedHostname(){return this._hostname.split(".").map(t=>m.decode(t.replace(this.punycodePrefix,""))).join(".")}}module.exports=i;
1
+ "use strict";const l=require("./punycode.js"),a=require("./utils.js");class c{constructor(t){if(this._href="",this._protocol="",this._host="",this._hostname="",this._port="",this._pathname="",this._search="",this._hash="",this._origin="",this._username="",this._password="",this._baseUrl="",this._searchParams=new URLSearchParams,this._basePath="",this.punycodePrefix="xn--",this._searchParams=new URLSearchParams,typeof t=="string"||t instanceof URL)this.href=t.toString();else if(t){if(t.basePath&&(this._basePath=t.basePath.startsWith("/")?t.basePath:`/${t.basePath}`),t.baseUrl&&(this.baseUrl=t.baseUrl),t.href&&(this.href=t.href),t.protocol&&(this.protocol=t.protocol),t.host&&(this.host=t.host),t.hostname&&(this.hostname=t.hostname),t.port&&(this.port=t.port),t.pathname!==void 0&&(t.pathname===""?this._pathname="":this.pathname=a.refinePathnameWithQuery(t.pathname,t.query??{})),t.search&&(this.search=t.search),t.hash&&(this.hash=t.hash),t.username&&(this.username=t.username),t.password&&(this.password=t.password),t.query){const s=a.refineQueryWithPathname(t.pathname??"",t.query);Object.keys(s).length>0&&(this.search=new URLSearchParams(a.convertQueryToArray(s)).toString())}this.updateHref()}}static withBasePath(t){return s=>typeof s=="string"||s instanceof URL?new c({href:s.toString(),basePath:t}):new c({...s,basePath:t})}static create(t){return new c(t)}static canParse(t){if(t.startsWith("/"))return/^\/[^?#]*(\?[^#]*)?(#.*)?$/.test(t);try{return new URL(t),!0}catch{return/^[^:/?#]+(\.[^:/?#]+)+(\/[^?#]*(\?[^#]*)?(#.*)?)?$/.test(t)}}get baseUrl(){return this._baseUrl}set baseUrl(t){this._baseUrl=t;try{const s=new URL(t);this._protocol=s.protocol,this._host=s.host,this._hostname=s.hostname,this._port=s.port,this._origin=s.origin,this._username=s.username,this._password=s.password,s.pathname!=="/"&&(this._pathname=s.pathname),s.search&&(this._search=s.search,this._searchParams=new URLSearchParams(s.search)),s.hash&&(this._hash=s.hash),this.updateHref()}catch(s){console.warn(`Invalid baseUrl: ${t}`,s)}}get href(){return this._href}set href(t){try{const s=new URL(t);this._href=s.href,this._protocol=s.protocol,this._host=s.host,this._hostname=s.hostname,this._port=s.port,this._pathname=s.pathname,this._search=s.search,this._hash=s.hash,this._origin=s.origin,this._username=s.username,this._password=s.password,this._searchParams=s.searchParams}catch(s){const e=c.parseStringToURLLike(t);if(!e)throw console.warn(`Can not parse ${t}`),s;this._href=e.href,this._protocol=e.protocol,this._host=e.hostname,this._hostname=e.hostname,this._port=e.port,this._pathname=e.pathname,this._search=e.search,this._hash=e.hash,this._origin=e.origin,this._username=e.username,this._password=e.password,this._searchParams=e.searchParams}}static parseStringToURLLike(t){const s=/^(?:(https?:\/\/)(?:([^:@]+)(?::([^@]+))?@)?((?:[^/:?#]+)(?::(\d+))?)?)?([/][^?#]*)?(\?[^#]*)?(#.*)?$/,e=t.match(s),[r=t,i="",n="",_="",h="",o="",m="",p="",f=""]=e||[];if(!e||i&&!h&&!m&&!p&&!f)return null;const d=i&&h?`${i}//${h}${o?`:${o}`:""}`:"";return{href:r,protocol:i,host:h,hostname:h,port:o,pathname:t?m||"/":"",search:p,hash:f,origin:d,username:n,password:_,searchParams:new URLSearchParams(p)}}get protocol(){return this._protocol}set protocol(t){this._protocol=t,this.updateHref()}get host(){return this._host}set host(t){const[s,e]=t.split(":"),r=this.encodeHostname(s);this._host=e?`${r}:${e}`:r,this._hostname=r,this._port=e||"",this.updateHref()}get hostname(){return this._hostname}set hostname(t){const s=this.encodeHostname(t);this._hostname=s,this._host=this._port?`${s}:${this._port}`:s,this.updateHref()}get port(){return this._port}set port(t){this._port=t,this._host=`${this._hostname}${t?":"+t:""}`,this.updateHref()}get pathname(){return this._pathname}set pathname(t){let s=t;if(t===""){s="/",this._pathname=s,this.updateHref();return}this._basePath&&!s.startsWith(this._basePath)&&(s=`${this._basePath}${s.startsWith("/")?"":"/"}${s}`);const e=s.split("/").map(r=>a.isDynamicPath(r)?r:encodeURI(r)).join("/");this._pathname=e.startsWith("/")?e:`/${e}`,this.updateHref()}get search(){return this._search}set search(t){this._search=t.startsWith("?")?t:`?${t}`,this._searchParams=new URLSearchParams(t),this.updateHref()}setSearchParams(t){const s=new URLSearchParams(t);this._search=s.toString()?`?${s.toString()}`:"",this._searchParams=s,this.updateHref()}appendSearchParams(t){const s=new URLSearchParams(this._searchParams),e=a.getDynamicPaths(this._pathname).map(a.extractPathKey);Object.keys(t).forEach(r=>{e.includes(r)?this._pathname=a.refinePathnameWithQuery(this._pathname,{[r]:t[r]}):s.append(r,t[r])}),this._search=s.toString()?`?${s.toString()}`:"",this._searchParams=s,this.updateHref()}removeSearchParams(...t){const s=new URLSearchParams(this._searchParams);t.forEach(e=>{s.delete(e)}),this._search=s.toString()?`?${s.toString()}`:"",this._searchParams=s,this.updateHref()}get searchParams(){return new Proxy(this._searchParams,{get:(t,s,e)=>{const r=Reflect.get(t,s,e);return typeof r=="function"?(...i)=>{const n=r.apply(t,i);return this._search=this._searchParams.toString()?`?${this._searchParams.toString()}`:"",this.updateHref(),n}:r}})}get hash(){return this._hash}set hash(t){this._hash=t.startsWith("#")?t:`#${t}`,this.updateHref()}get origin(){return this._origin}get username(){return this._username}set username(t){this._username=t,this.updateHref()}get password(){return this._password}set password(t){this._password=t,this.updateHref()}updateHref(){const t=this._pathname;if(this._baseUrl){const s=new URL(this._baseUrl);s.pathname=t,s.search=this._search,s.hash=this._hash,this._href=s.href,this._origin=s.origin}else this._href=`${this._protocol}${this._protocol&&"//"}${this._username}${this._password?":"+this._password:""}${this._username||this._password?"@":""}${this._hostname}${this._port?":"+this._port:""}${t}${this._search}${this._hash}`,this._origin=`${this._protocol}//${this._hostname}${this._port?":"+this._port:""}`}toString(){return this.href}toJSON(){return this.href}encodeHostname(t){return t.split(".").map(s=>{for(const e of s)if(a.isASCIICodeChar(e))return`${this.punycodePrefix}${l.encode(s)}`;return s}).join(".")}get decodedIDN(){let t=this._href;return this._hostname.split(".").forEach(s=>{t=t.replace(s,l.decode(s.replace(this.punycodePrefix,"")))}),t}get decodedHostname(){return this._hostname.split(".").map(t=>l.decode(t.replace(this.punycodePrefix,""))).join(".")}static match(t,s){if(!c.canParse(t)||!c.canParse(s))return null;const e=t.split(/[?#]/)[0]?.split("/").filter(Boolean)||[],r=s.split(/[?#]/)[0]?.split("/").filter(Boolean)||[];if(e.length!==r.length)return null;const i={};for(let n=0;n<r.length;n++){const _=r[n],h=e[n];if(a.isDynamicPath(_)){const o=a.extractPathKey(_);i[o]=h}else if(_!==h)return null}return i}static mask(t,{patterns:s,sensitiveParams:e,maskChar:r="*",maskLength:i=4,preserveLength:n=!1}){const _=[...s].sort((h,o)=>a.getPathPriority(o)>a.getPathPriority(h)?1:-1);for(const h of _){const o=c.create(t),m=c.match(o.pathname,h);if(m)return e.forEach(p=>{if(p in m){const f=m[p],d=n?f.length:i;m[p]=r.repeat(d)}}),o.pathname=a.refinePathnameWithQuery(h,m),o.toString()}return t}}module.exports=c;
package/dist/cjs/utils.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=/^:/,P=/^\[.*\]$/;function y(e){return c.test(e)||P.test(e)}function o(e){return e.split("/").filter(y)}function a(e){return e.slice(1,c.test(e)?void 0:-1)}function h(e,r){return o(e).reduce((t,n)=>{const i=a(n),u=r[i];return u&&typeof u=="string"?t.replace(n,u):t},e)}function A(e,r){return o(e).reduce((t,n)=>{const i=a(n);if(typeof t[i]!="string")return t;const{[i]:g,...f}=t;return f},r)}const C=127;function m(e){return e.charCodeAt(0)>C}function s(e){return["string","number","boolean"].includes(typeof e)}function d(e){return Object.entries(e).flatMap(([r,t])=>s(t)?[[r,String(t)]]:Array.isArray(t)&&t.every(s)?t.map(n=>[r,String(n)]):[])}exports.convertQueryToArray=d;exports.extractPathKey=a;exports.getDynamicPaths=o;exports.isASCIICodeChar=m;exports.isDynamicPath=y;exports.refinePathnameWithQuery=h;exports.refineQueryWithPathname=A;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const y=/^:/,P=/^\[.*\]$/;function o(t){return y.test(t)||P.test(t)}function s(t){return t.split("/").filter(o)}function a(t){return t.slice(1,y.test(t)?void 0:-1)}function h(t,r){return s(t).reduce((e,n)=>{const i=a(n),u=r[i];return u&&typeof u=="string"?e.replace(n,u):e},t)}function m(t,r){return s(t).reduce((e,n)=>{const i=a(n);if(typeof e[i]!="string")return e;const{[i]:d,...f}=e;return f},r)}const g=127;function A(t){return t.charCodeAt(0)>g}function c(t){return["string","number","boolean"].includes(typeof t)}function l(t){return Object.entries(t).flatMap(([r,e])=>c(e)?[[r,String(e)]]:Array.isArray(e)&&e.every(c)?e.map(n=>[r,String(n)]):[])}function C(t){return t.split("/").filter(Boolean).map(e=>o(e)?"1":"2").join("")}exports.convertQueryToArray=l;exports.extractPathKey=a;exports.getDynamicPaths=s;exports.getPathPriority=C;exports.isASCIICodeChar=A;exports.isDynamicPath=o;exports.refinePathnameWithQuery=h;exports.refineQueryWithPathname=m;
@@ -5,6 +5,13 @@ interface URLOptions extends Partial<Pick<URL, 'href' | 'protocol' | 'host' | 'h
5
5
  query?: Query;
6
6
  basePath?: string;
7
7
  }
8
+ interface MaskOptions {
9
+ patterns: string[];
10
+ sensitiveParams: string[];
11
+ maskChar?: string;
12
+ maskLength?: number;
13
+ preserveLength?: boolean;
14
+ }
8
15
  declare class NURL implements URL {
9
16
  private _href;
10
17
  private _protocol;
@@ -72,6 +79,8 @@ declare class NURL implements URL {
72
79
  private encodeHostname;
73
80
  get decodedIDN(): string;
74
81
  get decodedHostname(): string;
82
+ static match(url: string, pattern: string): Record<string, string> | null;
83
+ static mask(url: string, { patterns, sensitiveParams, maskChar, maskLength, preserveLength }: MaskOptions): string;
75
84
  }
76
85
 
77
86
  export { NURL as default };
package/dist/esm/nurl.mjs CHANGED
@@ -1,28 +1,28 @@
1
- import { encode as l, decode as p } from "./punycode.mjs";
2
- import { refinePathnameWithQuery as f, refineQueryWithPathname as g, convertQueryToArray as $, isDynamicPath as u, getDynamicPaths as w, extractPathKey as S, isASCIICodeChar as U } from "./utils.mjs";
3
- class i {
1
+ import { encode as $, decode as d } from "./punycode.mjs";
2
+ import { refinePathnameWithQuery as l, refineQueryWithPathname as w, convertQueryToArray as S, isDynamicPath as P, getDynamicPaths as y, extractPathKey as g, isASCIICodeChar as b, getPathPriority as u } from "./utils.mjs";
3
+ class n {
4
4
  constructor(t) {
5
5
  if (this._href = "", this._protocol = "", this._host = "", this._hostname = "", this._port = "", this._pathname = "", this._search = "", this._hash = "", this._origin = "", this._username = "", this._password = "", this._baseUrl = "", this._searchParams = new URLSearchParams(), this._basePath = "", this.punycodePrefix = "xn--", this._searchParams = new URLSearchParams(), typeof t == "string" || t instanceof URL)
6
6
  this.href = t.toString();
7
7
  else if (t) {
8
- if (t.basePath && (this._basePath = t.basePath.startsWith("/") ? t.basePath : `/${t.basePath}`), t.baseUrl && (this.baseUrl = t.baseUrl), t.href && (this.href = t.href), t.protocol && (this.protocol = t.protocol), t.host && (this.host = t.host), t.hostname && (this.hostname = t.hostname), t.port && (this.port = t.port), t.pathname !== void 0 && (t.pathname === "" ? this._pathname = "" : this.pathname = f(t.pathname, t.query ?? {})), t.search && (this.search = t.search), t.hash && (this.hash = t.hash), t.username && (this.username = t.username), t.password && (this.password = t.password), t.query) {
9
- const s = g(t.pathname ?? "", t.query);
10
- Object.keys(s).length > 0 && (this.search = new URLSearchParams($(s)).toString());
8
+ if (t.basePath && (this._basePath = t.basePath.startsWith("/") ? t.basePath : `/${t.basePath}`), t.baseUrl && (this.baseUrl = t.baseUrl), t.href && (this.href = t.href), t.protocol && (this.protocol = t.protocol), t.host && (this.host = t.host), t.hostname && (this.hostname = t.hostname), t.port && (this.port = t.port), t.pathname !== void 0 && (t.pathname === "" ? this._pathname = "" : this.pathname = l(t.pathname, t.query ?? {})), t.search && (this.search = t.search), t.hash && (this.hash = t.hash), t.username && (this.username = t.username), t.password && (this.password = t.password), t.query) {
9
+ const s = w(t.pathname ?? "", t.query);
10
+ Object.keys(s).length > 0 && (this.search = new URLSearchParams(S(s)).toString());
11
11
  }
12
12
  this.updateHref();
13
13
  }
14
14
  }
15
15
  static withBasePath(t) {
16
- return (s) => typeof s == "string" || s instanceof URL ? new i({
16
+ return (s) => typeof s == "string" || s instanceof URL ? new n({
17
17
  href: s.toString(),
18
18
  basePath: t
19
- }) : new i({
19
+ }) : new n({
20
20
  ...s,
21
21
  basePath: t
22
22
  });
23
23
  }
24
24
  static create(t) {
25
- return new i(t);
25
+ return new n(t);
26
26
  }
27
27
  static canParse(t) {
28
28
  if (t.startsWith("/"))
@@ -53,30 +53,30 @@ class i {
53
53
  const s = new URL(t);
54
54
  this._href = s.href, this._protocol = s.protocol, this._host = s.host, this._hostname = s.hostname, this._port = s.port, this._pathname = s.pathname, this._search = s.search, this._hash = s.hash, this._origin = s.origin, this._username = s.username, this._password = s.password, this._searchParams = s.searchParams;
55
55
  } catch (s) {
56
- const e = i.parseStringToURLLike(t);
56
+ const e = n.parseStringToURLLike(t);
57
57
  if (!e)
58
58
  throw console.warn(`Can not parse ${t}`), s;
59
59
  this._href = e.href, this._protocol = e.protocol, this._host = e.hostname, this._hostname = e.hostname, this._port = e.port, this._pathname = e.pathname, this._search = e.search, this._hash = e.hash, this._origin = e.origin, this._username = e.username, this._password = e.password, this._searchParams = e.searchParams;
60
60
  }
61
61
  }
62
62
  static parseStringToURLLike(t) {
63
- const s = /^(?:(https?:\/\/)(?:([^:@]+)(?::([^@]+))?@)?((?:[^/:?#]+)(?::(\d+))?)?)?([/][^?#]*)?(\?[^#]*)?(#.*)?$/, e = t.match(s), [h = t, r = "", o = "", d = "", a = "", n = "", _ = "", c = "", m = ""] = e || [];
64
- if (!e || r && !a && !_ && !c && !m)
63
+ const s = /^(?:(https?:\/\/)(?:([^:@]+)(?::([^@]+))?@)?((?:[^/:?#]+)(?::(\d+))?)?)?([/][^?#]*)?(\?[^#]*)?(#.*)?$/, e = t.match(s), [r = t, a = "", i = "", m = "", h = "", o = "", c = "", _ = "", p = ""] = e || [];
64
+ if (!e || a && !h && !c && !_ && !p)
65
65
  return null;
66
- const P = r && a ? `${r}//${a}${n ? `:${n}` : ""}` : "";
66
+ const f = a && h ? `${a}//${h}${o ? `:${o}` : ""}` : "";
67
67
  return {
68
- href: h,
69
- protocol: r,
70
- host: a,
71
- hostname: a,
72
- port: n,
73
- pathname: t ? _ || "/" : "",
74
- search: c,
75
- hash: m,
76
- origin: P,
77
- username: o,
78
- password: d,
79
- searchParams: new URLSearchParams(c)
68
+ href: r,
69
+ protocol: a,
70
+ host: h,
71
+ hostname: h,
72
+ port: o,
73
+ pathname: t ? c || "/" : "",
74
+ search: _,
75
+ hash: p,
76
+ origin: f,
77
+ username: i,
78
+ password: m,
79
+ searchParams: new URLSearchParams(_)
80
80
  };
81
81
  }
82
82
  get protocol() {
@@ -89,8 +89,8 @@ class i {
89
89
  return this._host;
90
90
  }
91
91
  set host(t) {
92
- const [s, e] = t.split(":"), h = this.encodeHostname(s);
93
- this._host = e ? `${h}:${e}` : h, this._hostname = h, this._port = e || "", this.updateHref();
92
+ const [s, e] = t.split(":"), r = this.encodeHostname(s);
93
+ this._host = e ? `${r}:${e}` : r, this._hostname = r, this._port = e || "", this.updateHref();
94
94
  }
95
95
  get hostname() {
96
96
  return this._hostname;
@@ -115,7 +115,7 @@ class i {
115
115
  return;
116
116
  }
117
117
  this._basePath && !s.startsWith(this._basePath) && (s = `${this._basePath}${s.startsWith("/") ? "" : "/"}${s}`);
118
- const e = s.split("/").map((h) => u(h) ? h : encodeURI(h)).join("/");
118
+ const e = s.split("/").map((r) => P(r) ? r : encodeURI(r)).join("/");
119
119
  this._pathname = e.startsWith("/") ? e : `/${e}`, this.updateHref();
120
120
  }
121
121
  get search() {
@@ -129,11 +129,11 @@ class i {
129
129
  this._search = s.toString() ? `?${s.toString()}` : "", this._searchParams = s, this.updateHref();
130
130
  }
131
131
  appendSearchParams(t) {
132
- const s = new URLSearchParams(this._searchParams), e = w(this._pathname).map(S);
133
- Object.keys(t).forEach((h) => {
134
- e.includes(h) ? this._pathname = f(this._pathname, {
135
- [h]: t[h]
136
- }) : s.append(h, t[h]);
132
+ const s = new URLSearchParams(this._searchParams), e = y(this._pathname).map(g);
133
+ Object.keys(t).forEach((r) => {
134
+ e.includes(r) ? this._pathname = l(this._pathname, {
135
+ [r]: t[r]
136
+ }) : s.append(r, t[r]);
137
137
  }), this._search = s.toString() ? `?${s.toString()}` : "", this._searchParams = s, this.updateHref();
138
138
  }
139
139
  removeSearchParams(...t) {
@@ -145,11 +145,11 @@ class i {
145
145
  get searchParams() {
146
146
  return new Proxy(this._searchParams, {
147
147
  get: (t, s, e) => {
148
- const h = Reflect.get(t, s, e);
149
- return typeof h == "function" ? (...r) => {
150
- const o = h.apply(t, r);
151
- return this._search = this._searchParams.toString() ? `?${this._searchParams.toString()}` : "", this.updateHref(), o;
152
- } : h;
148
+ const r = Reflect.get(t, s, e);
149
+ return typeof r == "function" ? (...a) => {
150
+ const i = r.apply(t, a);
151
+ return this._search = this._searchParams.toString() ? `?${this._searchParams.toString()}` : "", this.updateHref(), i;
152
+ } : r;
153
153
  }
154
154
  });
155
155
  }
@@ -191,21 +191,58 @@ class i {
191
191
  encodeHostname(t) {
192
192
  return t.split(".").map((s) => {
193
193
  for (const e of s)
194
- if (U(e))
195
- return `${this.punycodePrefix}${l(s)}`;
194
+ if (b(e))
195
+ return `${this.punycodePrefix}${$(s)}`;
196
196
  return s;
197
197
  }).join(".");
198
198
  }
199
199
  get decodedIDN() {
200
200
  let t = this._href;
201
201
  return this._hostname.split(".").forEach((s) => {
202
- t = t.replace(s, p(s.replace(this.punycodePrefix, "")));
202
+ t = t.replace(s, d(s.replace(this.punycodePrefix, "")));
203
203
  }), t;
204
204
  }
205
205
  get decodedHostname() {
206
- return this._hostname.split(".").map((t) => p(t.replace(this.punycodePrefix, ""))).join(".");
206
+ return this._hostname.split(".").map((t) => d(t.replace(this.punycodePrefix, ""))).join(".");
207
+ }
208
+ static match(t, s) {
209
+ if (!n.canParse(t) || !n.canParse(s))
210
+ return null;
211
+ const e = t.split(/[?#]/)[0]?.split("/").filter(Boolean) || [], r = s.split(/[?#]/)[0]?.split("/").filter(Boolean) || [];
212
+ if (e.length !== r.length)
213
+ return null;
214
+ const a = {};
215
+ for (let i = 0; i < r.length; i++) {
216
+ const m = r[i], h = e[i];
217
+ if (P(m)) {
218
+ const o = g(m);
219
+ a[o] = h;
220
+ } else if (m !== h)
221
+ return null;
222
+ }
223
+ return a;
224
+ }
225
+ static mask(t, {
226
+ patterns: s,
227
+ sensitiveParams: e,
228
+ maskChar: r = "*",
229
+ maskLength: a = 4,
230
+ preserveLength: i = !1
231
+ }) {
232
+ const m = [...s].sort((h, o) => u(o) > u(h) ? 1 : -1);
233
+ for (const h of m) {
234
+ const o = n.create(t), c = n.match(o.pathname, h);
235
+ if (c)
236
+ return e.forEach((_) => {
237
+ if (_ in c) {
238
+ const p = c[_], f = i ? p.length : a;
239
+ c[_] = r.repeat(f);
240
+ }
241
+ }), o.pathname = l(h, c), o.toString();
242
+ }
243
+ return t;
207
244
  }
208
245
  }
209
246
  export {
210
- i as default
247
+ n as default
211
248
  };
@@ -1,47 +1,51 @@
1
- const s = /^:/, a = /^\[.*\]$/;
2
- function A(r) {
3
- return s.test(r) || a.test(r);
4
- }
5
- function c(r) {
6
- return r.split("/").filter(A);
7
- }
8
- function f(r) {
9
- return r.slice(1, s.test(r) ? void 0 : -1);
10
- }
11
- function _(r, e) {
12
- return c(r).reduce((t, n) => {
13
- const i = f(n), u = e[i];
14
- return u && typeof u == "string" ? t.replace(n, u) : t;
1
+ const s = /^:/, A = /^\[.*\]$/;
2
+ function c(t) {
3
+ return s.test(t) || A.test(t);
4
+ }
5
+ function f(t) {
6
+ return t.split("/").filter(c);
7
+ }
8
+ function y(t) {
9
+ return t.slice(1, s.test(t) ? void 0 : -1);
10
+ }
11
+ function l(t, r) {
12
+ return f(t).reduce((e, n) => {
13
+ const i = y(n), u = r[i];
14
+ return u && typeof u == "string" ? e.replace(n, u) : e;
15
+ }, t);
16
+ }
17
+ function m(t, r) {
18
+ return f(t).reduce((e, n) => {
19
+ const i = y(n);
20
+ if (typeof e[i] != "string")
21
+ return e;
22
+ const {
23
+ [i]: g,
24
+ ...a
25
+ } = e;
26
+ return a;
15
27
  }, r);
16
28
  }
17
- function d(r, e) {
18
- return c(r).reduce((t, n) => {
19
- const i = f(n);
20
- if (typeof t[i] != "string")
21
- return t;
22
- const {
23
- [i]: P,
24
- ...y
25
- } = t;
26
- return y;
27
- }, e);
29
+ const P = 127;
30
+ function C(t) {
31
+ return t.charCodeAt(0) > P;
28
32
  }
29
- const C = 127;
30
- function g(r) {
31
- return r.charCodeAt(0) > C;
33
+ function o(t) {
34
+ return ["string", "number", "boolean"].includes(typeof t);
32
35
  }
33
- function o(r) {
34
- return ["string", "number", "boolean"].includes(typeof r);
36
+ function _(t) {
37
+ return Object.entries(t).flatMap(([r, e]) => o(e) ? [[r, String(e)]] : Array.isArray(e) && e.every(o) ? e.map((n) => [r, String(n)]) : []);
35
38
  }
36
- function h(r) {
37
- return Object.entries(r).flatMap(([e, t]) => o(t) ? [[e, String(t)]] : Array.isArray(t) && t.every(o) ? t.map((n) => [e, String(n)]) : []);
39
+ function d(t) {
40
+ return t.split("/").filter(Boolean).map((e) => c(e) ? "1" : "2").join("");
38
41
  }
39
42
  export {
40
- h as convertQueryToArray,
41
- f as extractPathKey,
42
- c as getDynamicPaths,
43
- g as isASCIICodeChar,
44
- A as isDynamicPath,
45
- _ as refinePathnameWithQuery,
46
- d as refineQueryWithPathname
43
+ _ as convertQueryToArray,
44
+ y as extractPathKey,
45
+ f as getDynamicPaths,
46
+ d as getPathPriority,
47
+ C as isASCIICodeChar,
48
+ c as isDynamicPath,
49
+ l as refinePathnameWithQuery,
50
+ m as refineQueryWithPathname
47
51
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naverpay/nurl",
3
- "version": "1.0.3",
3
+ "version": "1.1.0-canary.251230-92343a8",
4
4
  "description": "URL build library",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.mjs",
@@ -56,6 +56,6 @@
56
56
  "release": "changeset publish",
57
57
  "markdownlint": "markdownlint '**/*.md' '#.changeset' '#**/CHANGELOG.md'",
58
58
  "markdownlint:fix": "markdownlint --fix '**/*.md' '#.changeset' '#**/CHANGELOG.md'",
59
- "release:canary": "changeset publish --no-git-tag --directory dist"
59
+ "release:canary": "changeset publish --no-git-tag --tag=canary --directory dist"
60
60
  }
61
61
  }