@naverpay/nurl 1.0.3 → 1.1.0-canary.251230-7c2e8df

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.
@@ -1,3 +1,5 @@
1
+ import { MaskOptions } from './utils.js';
2
+
1
3
  type Query = Record<string, any>;
2
4
 
3
5
  interface URLOptions extends Partial<Pick<URL, 'href' | 'protocol' | 'host' | 'hostname' | 'port' | 'pathname' | 'search' | 'hash' | 'username' | 'password'>> {
@@ -72,6 +74,8 @@ declare class NURL implements URL {
72
74
  private encodeHostname;
73
75
  get decodedIDN(): string;
74
76
  get decodedHostname(): string;
77
+ static match(url: string, pattern: string): Record<string, string> | null;
78
+ static mask(url: string, options: MaskOptions): string;
75
79
  }
76
80
 
77
81
  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 m=require("./punycode.js"),d=require("./utils/external.js"),r=require("./utils/internal.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="",P="",o="",c="",p="",_="",f=""]=e||[];if(!e||a&&!o&&!p&&!_&&!f)return null;const l=a&&o?`${a}//${o}${c?`:${c}`:""}`:"";return{href:h,protocol:a,host:o,hostname:o,port:c,pathname:t?p||"/":"",search:_,hash:f,origin:l,username:n,password:P,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(".")}static match(t,s){return d.match(t,s)}static mask(t,s){return d.mask(t,s)}}module.exports=i;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const f=require("../nurl.js"),l=require("./internal.js"),g=(r,c)=>{if(!f.canParse(r)||!f.canParse(c))return null;const i=r.split(/[?#]/)[0]?.split("/").filter(Boolean)||[],a=c.split(/[?#]/)[0]?.split("/").filter(Boolean)||[];if(i.length!==a.length)return null;const u={};for(let n=0;n<a.length;n++){const o=a[n],t=i[n];if(l.isDynamicPath(o)){const e=l.extractPathKey(o);u[e]=t}else if(o!==t)return null}return u},P=(r,{patterns:c,sensitiveParams:i,maskChar:a="*",maskLength:u=4,preserveLength:n=!1})=>{const o=[...c].sort((t,e)=>l.getPathPriority(e)>l.getPathPriority(t)?1:-1);for(const t of o){const e=f.create(r),s=g(e.pathname,t);if(s)return i.forEach(h=>{if(h in s){const m=s[h],p=n?m.length:u;s[h]=a.repeat(p)}}),e.pathname=l.refinePathnameWithQuery(t,s),e.toString()}return r};exports.mask=P;exports.match=g;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./external.js");exports.mask=e.mask;exports.match=e.match;
@@ -0,0 +1 @@
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;
@@ -0,0 +1,11 @@
1
+ declare const match: (url: string, pattern: string) => Record<string, string> | null;
2
+ interface MaskOptions {
3
+ patterns: string[];
4
+ sensitiveParams: string[];
5
+ maskChar?: string;
6
+ maskLength?: number;
7
+ preserveLength?: boolean;
8
+ }
9
+ declare const mask: (url: string, { patterns, sensitiveParams, maskChar, maskLength, preserveLength }: MaskOptions) => string;
10
+
11
+ export { type MaskOptions, mask, match };
@@ -1,3 +1,5 @@
1
+ import { MaskOptions } from './utils.mjs';
2
+
1
3
  type Query = Record<string, any>;
2
4
 
3
5
  interface URLOptions extends Partial<Pick<URL, 'href' | 'protocol' | 'host' | 'hostname' | 'port' | 'pathname' | 'search' | 'hash' | 'username' | 'password'>> {
@@ -72,6 +74,8 @@ declare class NURL implements URL {
72
74
  private encodeHostname;
73
75
  get decodedIDN(): string;
74
76
  get decodedHostname(): string;
77
+ static match(url: string, pattern: string): Record<string, string> | null;
78
+ static mask(url: string, options: MaskOptions): string;
75
79
  }
76
80
 
77
81
  export { NURL as default };
package/dist/esm/nurl.mjs CHANGED
@@ -1,13 +1,14 @@
1
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";
2
+ import { match as g, mask as u } from "./utils/external.mjs";
3
+ import { refinePathnameWithQuery as f, refineQueryWithPathname as $, convertQueryToArray as w, isDynamicPath as S, getDynamicPaths as U, extractPathKey as H, isASCIICodeChar as b } from "./utils/internal.mjs";
3
4
  class i {
4
5
  constructor(t) {
5
6
  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
7
  this.href = t.toString();
7
8
  else if (t) {
8
9
  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());
10
+ const s = $(t.pathname ?? "", t.query);
11
+ Object.keys(s).length > 0 && (this.search = new URLSearchParams(w(s)).toString());
11
12
  }
12
13
  this.updateHref();
13
14
  }
@@ -115,7 +116,7 @@ class i {
115
116
  return;
116
117
  }
117
118
  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("/");
119
+ const e = s.split("/").map((h) => S(h) ? h : encodeURI(h)).join("/");
119
120
  this._pathname = e.startsWith("/") ? e : `/${e}`, this.updateHref();
120
121
  }
121
122
  get search() {
@@ -129,7 +130,7 @@ class i {
129
130
  this._search = s.toString() ? `?${s.toString()}` : "", this._searchParams = s, this.updateHref();
130
131
  }
131
132
  appendSearchParams(t) {
132
- const s = new URLSearchParams(this._searchParams), e = w(this._pathname).map(S);
133
+ const s = new URLSearchParams(this._searchParams), e = U(this._pathname).map(H);
133
134
  Object.keys(t).forEach((h) => {
134
135
  e.includes(h) ? this._pathname = f(this._pathname, {
135
136
  [h]: t[h]
@@ -191,7 +192,7 @@ class i {
191
192
  encodeHostname(t) {
192
193
  return t.split(".").map((s) => {
193
194
  for (const e of s)
194
- if (U(e))
195
+ if (b(e))
195
196
  return `${this.punycodePrefix}${l(s)}`;
196
197
  return s;
197
198
  }).join(".");
@@ -205,6 +206,12 @@ class i {
205
206
  get decodedHostname() {
206
207
  return this._hostname.split(".").map((t) => p(t.replace(this.punycodePrefix, ""))).join(".");
207
208
  }
209
+ static match(t, s) {
210
+ return g(t, s);
211
+ }
212
+ static mask(t, s) {
213
+ return u(t, s);
214
+ }
208
215
  }
209
216
  export {
210
217
  i as default
@@ -0,0 +1,42 @@
1
+ import h from "../nurl.mjs";
2
+ import { isDynamicPath as g, extractPathKey as P, getPathPriority as m, refinePathnameWithQuery as y } from "./internal.mjs";
3
+ const S = (r, l) => {
4
+ if (!h.canParse(r) || !h.canParse(l))
5
+ return null;
6
+ const c = r.split(/[?#]/)[0]?.split("/").filter(Boolean) || [], o = l.split(/[?#]/)[0]?.split("/").filter(Boolean) || [];
7
+ if (c.length !== o.length)
8
+ return null;
9
+ const i = {};
10
+ for (let n = 0; n < o.length; n++) {
11
+ const a = o[n], t = c[n];
12
+ if (g(a)) {
13
+ const e = P(a);
14
+ i[e] = t;
15
+ } else if (a !== t)
16
+ return null;
17
+ }
18
+ return i;
19
+ }, x = (r, {
20
+ patterns: l,
21
+ sensitiveParams: c,
22
+ maskChar: o = "*",
23
+ maskLength: i = 4,
24
+ preserveLength: n = !1
25
+ }) => {
26
+ const a = [...l].sort((t, e) => m(e) > m(t) ? 1 : -1);
27
+ for (const t of a) {
28
+ const e = h.create(r), s = S(e.pathname, t);
29
+ if (s)
30
+ return c.forEach((f) => {
31
+ if (f in s) {
32
+ const p = s[f], u = n ? p.length : i;
33
+ s[f] = o.repeat(u);
34
+ }
35
+ }), e.pathname = y(t, s), e.toString();
36
+ }
37
+ return r;
38
+ };
39
+ export {
40
+ x as mask,
41
+ S as match
42
+ };
@@ -0,0 +1,5 @@
1
+ import { mask as o, match as r } from "./external.mjs";
2
+ export {
3
+ o as mask,
4
+ r as match
5
+ };
@@ -0,0 +1,51 @@
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;
27
+ }, r);
28
+ }
29
+ const P = 127;
30
+ function C(t) {
31
+ return t.charCodeAt(0) > P;
32
+ }
33
+ function o(t) {
34
+ return ["string", "number", "boolean"].includes(typeof t);
35
+ }
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)]) : []);
38
+ }
39
+ function d(t) {
40
+ return t.split("/").filter(Boolean).map((e) => c(e) ? "1" : "2").join("");
41
+ }
42
+ export {
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
51
+ };
@@ -0,0 +1,11 @@
1
+ declare const match: (url: string, pattern: string) => Record<string, string> | null;
2
+ interface MaskOptions {
3
+ patterns: string[];
4
+ sensitiveParams: string[];
5
+ maskChar?: string;
6
+ maskLength?: number;
7
+ preserveLength?: boolean;
8
+ }
9
+ declare const mask: (url: string, { patterns, sensitiveParams, maskChar, maskLength, preserveLength }: MaskOptions) => string;
10
+
11
+ export { type MaskOptions, mask, match };
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-7c2e8df",
4
4
  "description": "URL build library",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.mjs",
@@ -16,6 +16,16 @@
16
16
  "default": "./dist/cjs/index.js"
17
17
  }
18
18
  },
19
+ "./utils": {
20
+ "import": {
21
+ "types": "./dist/esm/utils.d.mts",
22
+ "default": "./dist/esm/utils/index.mjs"
23
+ },
24
+ "require": {
25
+ "types": "./dist/cjs/utils.d.ts",
26
+ "default": "./dist/cjs/utils/index.js"
27
+ }
28
+ },
19
29
  "./package.json": "./package.json"
20
30
  },
21
31
  "files": [
@@ -56,6 +66,6 @@
56
66
  "release": "changeset publish",
57
67
  "markdownlint": "markdownlint '**/*.md' '#.changeset' '#**/CHANGELOG.md'",
58
68
  "markdownlint:fix": "markdownlint --fix '**/*.md' '#.changeset' '#**/CHANGELOG.md'",
59
- "release:canary": "changeset publish --no-git-tag --directory dist"
69
+ "release:canary": "changeset publish --no-git-tag --tag=canary --directory dist"
60
70
  }
61
71
  }
package/dist/cjs/utils.js DELETED
@@ -1 +0,0 @@
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,47 +0,0 @@
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;
15
- }, r);
16
- }
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);
28
- }
29
- const C = 127;
30
- function g(r) {
31
- return r.charCodeAt(0) > C;
32
- }
33
- function o(r) {
34
- return ["string", "number", "boolean"].includes(typeof r);
35
- }
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)]) : []);
38
- }
39
- 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
47
- };