@gjsify/url 0.0.4 → 0.1.0

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
@@ -1,6 +1,32 @@
1
1
  # @gjsify/url
2
2
 
3
- Node.js url module for Gjs
4
- ## Inspirations
3
+ GJS implementation of the Node.js `url` module using GLib.Uri. Provides URL and URLSearchParams.
4
+
5
+ Part of the [gjsify](https://github.com/gjsify/gjsify) project — Node.js and Web APIs for GJS (GNOME JavaScript).
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @gjsify/url
11
+ # or
12
+ yarn add @gjsify/url
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```typescript
18
+ import { URL, URLSearchParams } from '@gjsify/url';
19
+
20
+ const url = new URL('https://example.com/path?foo=bar');
21
+ console.log(url.hostname); // 'example.com'
22
+ console.log(url.searchParams.get('foo')); // 'bar'
23
+ ```
24
+
25
+ ## Inspirations and credits
26
+
5
27
  - https://github.com/defunctzombie/node-url
6
- - https://github.com/denoland/deno_std/blob/main/node/url.ts
28
+ - https://github.com/denoland/deno_std/blob/main/node/url.ts
29
+
30
+ ## License
31
+
32
+ MIT
package/lib/esm/index.js CHANGED
@@ -1,6 +1,444 @@
1
- export * from "@gjsify/deno_std/node/url";
2
- import _default from "@gjsify/deno_std/node/url";
3
- var src_default = _default;
1
+ import GLib from "@girs/glib-2.0";
2
+ const PARSE_FLAGS = GLib.UriFlags.HAS_PASSWORD | GLib.UriFlags.ENCODED | GLib.UriFlags.SCHEME_NORMALIZE;
3
+ class URLSearchParams {
4
+ _entries = [];
5
+ constructor(init) {
6
+ if (!init) return;
7
+ if (typeof init === "string") {
8
+ const s = init.startsWith("?") ? init.slice(1) : init;
9
+ if (s) {
10
+ for (const pair of s.split("&")) {
11
+ const eqIdx = pair.indexOf("=");
12
+ if (eqIdx === -1) {
13
+ this._entries.push([decodeComponent(pair), ""]);
14
+ } else {
15
+ this._entries.push([decodeComponent(pair.slice(0, eqIdx)), decodeComponent(pair.slice(eqIdx + 1))]);
16
+ }
17
+ }
18
+ }
19
+ } else if (Array.isArray(init)) {
20
+ for (const [k, v] of init) {
21
+ this._entries.push([String(k), String(v)]);
22
+ }
23
+ } else if (init instanceof URLSearchParams) {
24
+ this._entries = init._entries.map(([k, v]) => [k, v]);
25
+ } else {
26
+ for (const key of Object.keys(init)) {
27
+ this._entries.push([key, String(init[key])]);
28
+ }
29
+ }
30
+ }
31
+ get(name) {
32
+ for (const [k, v] of this._entries) {
33
+ if (k === name) return v;
34
+ }
35
+ return null;
36
+ }
37
+ getAll(name) {
38
+ return this._entries.filter(([k]) => k === name).map(([, v]) => v);
39
+ }
40
+ set(name, value) {
41
+ let found = false;
42
+ this._entries = this._entries.filter(([k]) => {
43
+ if (k === name) {
44
+ if (!found) {
45
+ found = true;
46
+ return true;
47
+ }
48
+ return false;
49
+ }
50
+ return true;
51
+ });
52
+ if (found) {
53
+ for (let i = 0; i < this._entries.length; i++) {
54
+ if (this._entries[i][0] === name) {
55
+ this._entries[i][1] = value;
56
+ break;
57
+ }
58
+ }
59
+ } else {
60
+ this._entries.push([name, value]);
61
+ }
62
+ }
63
+ has(name) {
64
+ return this._entries.some(([k]) => k === name);
65
+ }
66
+ delete(name) {
67
+ this._entries = this._entries.filter(([k]) => k !== name);
68
+ }
69
+ append(name, value) {
70
+ this._entries.push([name, value]);
71
+ }
72
+ sort() {
73
+ this._entries.sort((a, b) => {
74
+ if (a[0] < b[0]) return -1;
75
+ if (a[0] > b[0]) return 1;
76
+ return 0;
77
+ });
78
+ }
79
+ toString() {
80
+ return this._entries.map(([k, v]) => encodeComponent(k) + "=" + encodeComponent(v)).join("&");
81
+ }
82
+ forEach(callback) {
83
+ for (const [k, v] of this._entries) {
84
+ callback(v, k, this);
85
+ }
86
+ }
87
+ *entries() {
88
+ yield* this._entries;
89
+ }
90
+ *keys() {
91
+ for (const [k] of this._entries) yield k;
92
+ }
93
+ *values() {
94
+ for (const [, v] of this._entries) yield v;
95
+ }
96
+ [Symbol.iterator]() {
97
+ return this.entries();
98
+ }
99
+ get size() {
100
+ return this._entries.length;
101
+ }
102
+ }
103
+ function decodeComponent(s) {
104
+ try {
105
+ return decodeURIComponent(s.replace(/\+/g, " "));
106
+ } catch {
107
+ return s;
108
+ }
109
+ }
110
+ function encodeComponent(s) {
111
+ return encodeURIComponent(s).replace(/%20/g, "+");
112
+ }
113
+ class URL {
114
+ #uri;
115
+ // GLib.Uri
116
+ #searchParams;
117
+ constructor(url, base) {
118
+ const urlStr = url instanceof URL ? url.href : String(url);
119
+ try {
120
+ if (base !== void 0) {
121
+ const baseStr = base instanceof URL ? base.href : String(base);
122
+ const baseUri = GLib.Uri.parse(baseStr, PARSE_FLAGS);
123
+ this.#uri = baseUri.parse_relative(urlStr, PARSE_FLAGS);
124
+ } else {
125
+ this.#uri = GLib.Uri.parse(urlStr, PARSE_FLAGS);
126
+ }
127
+ } catch (e) {
128
+ throw new TypeError(`Invalid URL: ${urlStr}`);
129
+ }
130
+ if (!this.#uri) {
131
+ throw new TypeError(`Invalid URL: ${urlStr}`);
132
+ }
133
+ this.#searchParams = new URLSearchParams(this.#uri.get_query() || "");
134
+ }
135
+ get protocol() {
136
+ return this.#uri.get_scheme() + ":";
137
+ }
138
+ get hostname() {
139
+ return (this.#uri.get_host() || "").toLowerCase();
140
+ }
141
+ get port() {
142
+ const p = this.#uri.get_port();
143
+ if (p === -1) return "";
144
+ const scheme = this.#uri.get_scheme();
145
+ if ((scheme === "http" || scheme === "ws") && p === 80) return "";
146
+ if ((scheme === "https" || scheme === "wss") && p === 443) return "";
147
+ if (scheme === "ftp" && p === 21) return "";
148
+ return String(p);
149
+ }
150
+ get host() {
151
+ const hostname = this.hostname;
152
+ const port = this.port;
153
+ return port ? `${hostname}:${port}` : hostname;
154
+ }
155
+ get pathname() {
156
+ return this.#uri.get_path() || "/";
157
+ }
158
+ get search() {
159
+ const q = this.#uri.get_query();
160
+ return q ? "?" + q : "";
161
+ }
162
+ get hash() {
163
+ const f = this.#uri.get_fragment();
164
+ return f ? "#" + f : "";
165
+ }
166
+ get origin() {
167
+ const p = this.protocol;
168
+ if (p === "http:" || p === "https:" || p === "ftp:") {
169
+ return `${p}//${this.host}`;
170
+ }
171
+ return "null";
172
+ }
173
+ get username() {
174
+ return this.#uri.get_user() || "";
175
+ }
176
+ get password() {
177
+ return this.#uri.get_password() || "";
178
+ }
179
+ get href() {
180
+ let result = this.protocol;
181
+ const scheme = this.#uri.get_scheme();
182
+ const isSpecial = scheme === "http" || scheme === "https" || scheme === "ftp" || scheme === "file" || scheme === "ws" || scheme === "wss";
183
+ if (isSpecial || this.hostname) {
184
+ result += "//";
185
+ }
186
+ const user = this.username;
187
+ const pass = this.password;
188
+ if (user) {
189
+ result += user;
190
+ if (pass) result += ":" + pass;
191
+ result += "@";
192
+ }
193
+ result += this.hostname;
194
+ if (this.port) result += ":" + this.port;
195
+ const pathname = this.pathname;
196
+ result += pathname;
197
+ result += this.search;
198
+ result += this.hash;
199
+ return result;
200
+ }
201
+ get searchParams() {
202
+ return this.#searchParams;
203
+ }
204
+ toString() {
205
+ return this.href;
206
+ }
207
+ toJSON() {
208
+ return this.href;
209
+ }
210
+ }
211
+ function parse(urlString, parseQueryString, slashesDenoteHost) {
212
+ if (typeof urlString !== "string") {
213
+ throw new TypeError('The "url" argument must be of type string. Received type ' + typeof urlString);
214
+ }
215
+ const result = {
216
+ protocol: null,
217
+ slashes: null,
218
+ auth: null,
219
+ host: null,
220
+ port: null,
221
+ hostname: null,
222
+ hash: null,
223
+ search: null,
224
+ query: null,
225
+ pathname: null,
226
+ path: null,
227
+ href: urlString
228
+ };
229
+ let rest = urlString.trim();
230
+ const hashIdx = rest.indexOf("#");
231
+ if (hashIdx !== -1) {
232
+ result.hash = rest.slice(hashIdx);
233
+ rest = rest.slice(0, hashIdx);
234
+ }
235
+ const qIdx = rest.indexOf("?");
236
+ if (qIdx !== -1) {
237
+ result.search = rest.slice(qIdx);
238
+ result.query = parseQueryString ? Object.fromEntries(new URLSearchParams(rest.slice(qIdx + 1))) : rest.slice(qIdx + 1);
239
+ rest = rest.slice(0, qIdx);
240
+ }
241
+ const protoMatch = /^([a-z][a-z0-9.+-]*:)/i.exec(rest);
242
+ if (protoMatch) {
243
+ result.protocol = protoMatch[1].toLowerCase();
244
+ rest = rest.slice(result.protocol.length);
245
+ }
246
+ if (slashesDenoteHost || result.protocol) {
247
+ const hasSlashes = rest.startsWith("//");
248
+ if (hasSlashes) {
249
+ result.slashes = true;
250
+ rest = rest.slice(2);
251
+ }
252
+ }
253
+ if (result.slashes || result.protocol && !["javascript:", "data:", "mailto:"].includes(result.protocol)) {
254
+ let hostEnd = -1;
255
+ for (let i = 0; i < rest.length; i++) {
256
+ const ch = rest[i];
257
+ if (ch === "/" || ch === "\\") {
258
+ hostEnd = i;
259
+ break;
260
+ }
261
+ }
262
+ const hostPart = hostEnd === -1 ? rest : rest.slice(0, hostEnd);
263
+ rest = hostEnd === -1 ? "" : rest.slice(hostEnd);
264
+ const atIdx = hostPart.lastIndexOf("@");
265
+ if (atIdx !== -1) {
266
+ result.auth = decodeURIComponent(hostPart.slice(0, atIdx));
267
+ const hostWithPort = hostPart.slice(atIdx + 1);
268
+ parseHostPort(hostWithPort, result);
269
+ } else {
270
+ parseHostPort(hostPart, result);
271
+ }
272
+ }
273
+ result.pathname = rest || (result.slashes ? "/" : null);
274
+ if (result.pathname !== null || result.search !== null) {
275
+ result.path = (result.pathname || "") + (result.search || "");
276
+ }
277
+ result.href = format(result);
278
+ return result;
279
+ }
280
+ function parseHostPort(hostPart, result) {
281
+ if (!hostPart) return;
282
+ const bracketIdx = hostPart.indexOf("[");
283
+ if (bracketIdx !== -1) {
284
+ const bracketEnd = hostPart.indexOf("]", bracketIdx);
285
+ if (bracketEnd !== -1) {
286
+ const portStr = hostPart.slice(bracketEnd + 1);
287
+ if (portStr.startsWith(":")) {
288
+ result.port = portStr.slice(1);
289
+ }
290
+ result.hostname = hostPart.slice(bracketIdx, bracketEnd + 1);
291
+ result.host = result.hostname + (result.port ? ":" + result.port : "");
292
+ return;
293
+ }
294
+ }
295
+ const colonIdx = hostPart.lastIndexOf(":");
296
+ if (colonIdx !== -1) {
297
+ const portCandidate = hostPart.slice(colonIdx + 1);
298
+ if (/^\d*$/.test(portCandidate)) {
299
+ result.port = portCandidate || null;
300
+ result.hostname = hostPart.slice(0, colonIdx).toLowerCase();
301
+ } else {
302
+ result.hostname = hostPart.toLowerCase();
303
+ }
304
+ } else {
305
+ result.hostname = hostPart.toLowerCase();
306
+ }
307
+ result.host = result.hostname + (result.port ? ":" + result.port : "");
308
+ }
309
+ function format(urlObject) {
310
+ if (typeof urlObject === "string") {
311
+ return urlObject;
312
+ }
313
+ if (urlObject instanceof URL) {
314
+ return urlObject.href;
315
+ }
316
+ const obj = urlObject;
317
+ let result = "";
318
+ if (obj.protocol) {
319
+ result += obj.protocol;
320
+ }
321
+ if (obj.slashes || obj.protocol && !["javascript:", "data:", "mailto:"].includes(obj.protocol || "")) {
322
+ result += "//";
323
+ }
324
+ if (obj.auth) {
325
+ result += encodeURIComponent(obj.auth) + "@";
326
+ }
327
+ if (obj.host) {
328
+ result += obj.host;
329
+ } else {
330
+ if (obj.hostname) {
331
+ result += obj.hostname;
332
+ }
333
+ if (obj.port) {
334
+ result += ":" + obj.port;
335
+ }
336
+ }
337
+ if (obj.pathname) {
338
+ result += obj.pathname;
339
+ }
340
+ if (obj.search) {
341
+ result += obj.search;
342
+ } else if (obj.query && typeof obj.query === "object") {
343
+ const qs = new URLSearchParams(obj.query).toString();
344
+ if (qs) result += "?" + qs;
345
+ }
346
+ if (obj.hash) {
347
+ result += obj.hash;
348
+ }
349
+ return result;
350
+ }
351
+ function resolve(from, to) {
352
+ return new URL(to, new URL(from, "resolve://")).href.replace(/^resolve:\/\//, "");
353
+ }
354
+ function fileURLToPath(url) {
355
+ if (typeof url === "string") {
356
+ url = new URL(url);
357
+ }
358
+ if (!(url instanceof URL)) {
359
+ throw new TypeError('The "url" argument must be of type string or URL. Received type ' + typeof url);
360
+ }
361
+ if (url.protocol !== "file:") {
362
+ throw new TypeError("The URL must be of scheme file");
363
+ }
364
+ if (url.hostname !== "" && url.hostname !== "localhost") {
365
+ throw new TypeError(
366
+ `File URL host must be "localhost" or empty on linux`
367
+ );
368
+ }
369
+ const pathname = url.pathname;
370
+ for (let i = 0; i < pathname.length; i++) {
371
+ if (pathname[i] === "%") {
372
+ const third = pathname.codePointAt(i + 2) | 32;
373
+ if (pathname[i + 1] === "2" && third === 102) {
374
+ throw new TypeError("File URL path must not include encoded / characters");
375
+ }
376
+ }
377
+ }
378
+ return decodeURIComponent(pathname);
379
+ }
380
+ function pathToFileURL(filepath) {
381
+ let resolved = filepath;
382
+ if (filepath[0] !== "/") {
383
+ if (typeof globalThis.process?.cwd === "function") {
384
+ resolved = globalThis.process.cwd() + "/" + filepath;
385
+ } else {
386
+ try {
387
+ if (GLib?.get_current_dir) {
388
+ resolved = GLib.get_current_dir() + "/" + filepath;
389
+ }
390
+ } catch {
391
+ }
392
+ }
393
+ }
394
+ return new URL("file://" + encodePathForURL(resolved));
395
+ }
396
+ function encodePathForURL(filepath) {
397
+ let result = "";
398
+ for (let i = 0; i < filepath.length; i++) {
399
+ const ch = filepath[i];
400
+ if (ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch >= "0" && ch <= "9" || ch === "/" || ch === "-" || ch === "_" || ch === "." || ch === "~" || ch === ":" || ch === "@" || ch === "!") {
401
+ result += ch;
402
+ } else {
403
+ result += encodeURIComponent(ch);
404
+ }
405
+ }
406
+ return result;
407
+ }
408
+ function domainToASCII(domain) {
409
+ try {
410
+ return new URL(`http://${domain}`).hostname;
411
+ } catch {
412
+ return "";
413
+ }
414
+ }
415
+ function domainToUnicode(domain) {
416
+ try {
417
+ return new URL(`http://${domain}`).hostname;
418
+ } catch {
419
+ return "";
420
+ }
421
+ }
422
+ var index_default = {
423
+ URL,
424
+ URLSearchParams,
425
+ parse,
426
+ format,
427
+ resolve,
428
+ fileURLToPath,
429
+ pathToFileURL,
430
+ domainToASCII,
431
+ domainToUnicode
432
+ };
4
433
  export {
5
- src_default as default
434
+ URL,
435
+ URLSearchParams,
436
+ index_default as default,
437
+ domainToASCII,
438
+ domainToUnicode,
439
+ fileURLToPath,
440
+ format,
441
+ parse,
442
+ pathToFileURL,
443
+ resolve
6
444
  };
@@ -1,3 +1,72 @@
1
- export type * from 'node:url';
2
- import type url from 'node:url';
3
- export default url;
1
+ export declare class URLSearchParams {
2
+ _entries: [string, string][];
3
+ constructor(init?: string | Record<string, string> | [string, string][] | URLSearchParams);
4
+ get(name: string): string | null;
5
+ getAll(name: string): string[];
6
+ set(name: string, value: string): void;
7
+ has(name: string): boolean;
8
+ delete(name: string): void;
9
+ append(name: string, value: string): void;
10
+ sort(): void;
11
+ toString(): string;
12
+ forEach(callback: (value: string, key: string, parent: URLSearchParams) => void): void;
13
+ entries(): IterableIterator<[string, string]>;
14
+ keys(): IterableIterator<string>;
15
+ values(): IterableIterator<string>;
16
+ [Symbol.iterator](): IterableIterator<[string, string]>;
17
+ get size(): number;
18
+ }
19
+ export declare class URL {
20
+ #private;
21
+ constructor(url: string | URL, base?: string | URL);
22
+ get protocol(): string;
23
+ get hostname(): string;
24
+ get port(): string;
25
+ get host(): string;
26
+ get pathname(): string;
27
+ get search(): string;
28
+ get hash(): string;
29
+ get origin(): string;
30
+ get username(): string;
31
+ get password(): string;
32
+ get href(): string;
33
+ get searchParams(): URLSearchParams;
34
+ toString(): string;
35
+ toJSON(): string;
36
+ }
37
+ export interface UrlObject {
38
+ protocol?: string | null;
39
+ slashes?: boolean | null;
40
+ auth?: string | null;
41
+ host?: string | null;
42
+ port?: string | null;
43
+ hostname?: string | null;
44
+ hash?: string | null;
45
+ search?: string | null;
46
+ query?: string | Record<string, string> | null;
47
+ pathname?: string | null;
48
+ path?: string | null;
49
+ href?: string;
50
+ }
51
+ export interface Url extends UrlObject {
52
+ href: string;
53
+ }
54
+ export declare function parse(urlString: string, parseQueryString?: boolean, slashesDenoteHost?: boolean): Url;
55
+ export declare function format(urlObject: UrlObject | string | URL): string;
56
+ export declare function resolve(from: string, to: string): string;
57
+ export declare function fileURLToPath(url: string | URL): string;
58
+ export declare function pathToFileURL(filepath: string): URL;
59
+ export declare function domainToASCII(domain: string): string;
60
+ export declare function domainToUnicode(domain: string): string;
61
+ declare const _default: {
62
+ URL: typeof URL;
63
+ URLSearchParams: typeof URLSearchParams;
64
+ parse: typeof parse;
65
+ format: typeof format;
66
+ resolve: typeof resolve;
67
+ fileURLToPath: typeof fileURLToPath;
68
+ pathToFileURL: typeof pathToFileURL;
69
+ domainToASCII: typeof domainToASCII;
70
+ domainToUnicode: typeof domainToUnicode;
71
+ };
72
+ export default _default;
package/package.json CHANGED
@@ -1,33 +1,26 @@
1
1
  {
2
2
  "name": "@gjsify/url",
3
- "version": "0.0.4",
3
+ "version": "0.1.0",
4
4
  "description": "Node.js url module for Gjs",
5
5
  "type": "module",
6
- "main": "lib/cjs/index.js",
7
6
  "module": "lib/esm/index.js",
7
+ "types": "lib/types/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "import": {
11
- "types": "./lib/types/index.d.ts",
12
- "default": "./lib/esm/index.js"
13
- },
14
- "require": {
15
- "types": "./lib/types/index.d.ts",
16
- "default": "./lib/cjs/index.js"
17
- }
10
+ "types": "./lib/types/index.d.ts",
11
+ "default": "./lib/esm/index.js"
18
12
  }
19
13
  },
20
14
  "scripts": {
21
- "clear": "rm -rf lib/cjs lib/esm tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo || exit 0",
22
- "print:name": "echo '@gjsify/url'",
23
- "build": "yarn print:name && yarn build:gjsify && yarn build:types",
15
+ "clear": "rm -rf lib tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo test.gjs.mjs test.node.mjs || exit 0",
16
+ "check": "tsc --noEmit",
17
+ "build": "yarn build:gjsify && yarn build:types",
24
18
  "build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.ts' 'src/test.ts'",
25
- "build:types": "echo 'The types are currently handwritten'",
26
- "build:real_types": "tsc --project tsconfig.types.json || exit 0",
19
+ "build:types": "tsc",
27
20
  "build:test": "yarn build:test:gjs && yarn build:test:node",
28
21
  "build:test:gjs": "gjsify build src/test.ts --app gjs --outfile test.gjs.mjs",
29
22
  "build:test:node": "gjsify build src/test.ts --app node --outfile test.node.mjs",
30
- "test": "yarn print:name && yarn build:gjsify && yarn build:test && yarn test:node && yarn test:gjs",
23
+ "test": "yarn build:gjsify && yarn build:test && yarn test:node && yarn test:gjs",
31
24
  "test:gjs": "gjs -m test.gjs.mjs",
32
25
  "test:node": "node test.node.mjs"
33
26
  },
@@ -36,13 +29,13 @@
36
29
  "node",
37
30
  "fs"
38
31
  ],
39
- "devDependencies": {
40
- "@gjsify/cli": "^0.0.4",
41
- "@gjsify/unit": "^0.0.4",
42
- "typescript": "^5.3.3"
43
- },
44
32
  "dependencies": {
45
- "@gjsify/deno_std": "^0.0.4",
46
- "@types/node": "^20.10.5"
33
+ "@girs/glib-2.0": "^2.88.0-4.0.0-beta.42"
34
+ },
35
+ "devDependencies": {
36
+ "@gjsify/cli": "^0.1.0",
37
+ "@gjsify/unit": "^0.1.0",
38
+ "@types/node": "^25.5.0",
39
+ "typescript": "^6.0.2"
47
40
  }
48
41
  }