@adviser/cement 0.4.32 → 0.4.34

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.
Files changed (141) hide show
  1. package/cjs/cli/patch-version-cmd.cjs +25 -9
  2. package/cjs/cli/patch-version-cmd.cjs.map +1 -1
  3. package/cjs/cli/patch-version-cmd.d.ts +1 -1
  4. package/cjs/cli/patch-version-cmd.d.ts.map +1 -1
  5. package/cjs/index.cjs +1 -0
  6. package/cjs/index.cjs.map +1 -1
  7. package/cjs/index.d.ts +1 -0
  8. package/cjs/index.d.ts.map +1 -1
  9. package/cjs/load-asset.cjs +6 -5
  10. package/cjs/load-asset.cjs.map +1 -1
  11. package/cjs/load-asset.d.ts.map +1 -1
  12. package/cjs/logger.cjs +2 -2
  13. package/cjs/logger.cjs.map +1 -1
  14. package/cjs/logger.d.ts.map +1 -1
  15. package/cjs/logger.test.cjs +1 -1
  16. package/cjs/logger.test.cjs.map +1 -1
  17. package/cjs/mutable-url.cjs +281 -0
  18. package/cjs/mutable-url.cjs.map +1 -0
  19. package/cjs/mutable-url.d.ts +72 -0
  20. package/cjs/mutable-url.d.ts.map +1 -0
  21. package/cjs/mutable-url.test.cjs +452 -0
  22. package/cjs/mutable-url.test.cjs.map +1 -0
  23. package/cjs/mutable-url.test.d.ts +2 -0
  24. package/cjs/mutable-url.test.d.ts.map +1 -0
  25. package/cjs/uri.cjs +32 -161
  26. package/cjs/uri.cjs.map +1 -1
  27. package/cjs/uri.d.ts +4 -30
  28. package/cjs/uri.d.ts.map +1 -1
  29. package/cjs/uri.test.cjs +24 -36
  30. package/cjs/uri.test.cjs.map +1 -1
  31. package/cjs/utils/coerce-uint8.cjs.map +1 -1
  32. package/cjs/utils/coerce-uint8.d.ts.map +1 -1
  33. package/cjs/version.cjs +1 -1
  34. package/deno.json +1 -1
  35. package/esm/cli/patch-version-cmd.d.ts +1 -1
  36. package/esm/cli/patch-version-cmd.d.ts.map +1 -1
  37. package/esm/cli/patch-version-cmd.js +25 -9
  38. package/esm/cli/patch-version-cmd.js.map +1 -1
  39. package/esm/index.d.ts +1 -0
  40. package/esm/index.d.ts.map +1 -1
  41. package/esm/index.js +1 -0
  42. package/esm/index.js.map +1 -1
  43. package/esm/load-asset.d.ts.map +1 -1
  44. package/esm/load-asset.js +7 -6
  45. package/esm/load-asset.js.map +1 -1
  46. package/esm/logger.d.ts.map +1 -1
  47. package/esm/logger.js +2 -2
  48. package/esm/logger.js.map +1 -1
  49. package/esm/logger.test.js +2 -2
  50. package/esm/logger.test.js.map +1 -1
  51. package/esm/mutable-url.d.ts +72 -0
  52. package/esm/mutable-url.d.ts.map +1 -0
  53. package/esm/mutable-url.js +275 -0
  54. package/esm/mutable-url.js.map +1 -0
  55. package/esm/mutable-url.test.d.ts +2 -0
  56. package/esm/mutable-url.test.d.ts.map +1 -0
  57. package/esm/mutable-url.test.js +450 -0
  58. package/esm/mutable-url.test.js.map +1 -0
  59. package/esm/uri.d.ts +4 -30
  60. package/esm/uri.d.ts.map +1 -1
  61. package/esm/uri.js +26 -154
  62. package/esm/uri.js.map +1 -1
  63. package/esm/uri.test.js +25 -37
  64. package/esm/uri.test.js.map +1 -1
  65. package/esm/utils/coerce-uint8.d.ts.map +1 -1
  66. package/esm/utils/coerce-uint8.js.map +1 -1
  67. package/esm/version.js +1 -1
  68. package/package.json +2 -1
  69. package/src/cli/patch-version-cmd.ts +26 -9
  70. package/src/index.ts +1 -0
  71. package/src/load-asset.ts +7 -6
  72. package/src/logger.ts +3 -2
  73. package/src/mutable-url.ts +365 -0
  74. package/src/uri.ts +51 -195
  75. package/src/utils/coerce-uint8.ts +1 -2
  76. package/ts/cjs/cli/patch-version-cmd.d.ts +1 -1
  77. package/ts/cjs/cli/patch-version-cmd.d.ts.map +1 -1
  78. package/ts/cjs/cli/patch-version-cmd.js +25 -9
  79. package/ts/cjs/cli/patch-version-cmd.js.map +1 -1
  80. package/ts/cjs/index.d.ts +1 -0
  81. package/ts/cjs/index.d.ts.map +1 -1
  82. package/ts/cjs/index.js +1 -0
  83. package/ts/cjs/index.js.map +1 -1
  84. package/ts/cjs/load-asset.d.ts.map +1 -1
  85. package/ts/cjs/load-asset.js +6 -5
  86. package/ts/cjs/load-asset.js.map +1 -1
  87. package/ts/cjs/logger.d.ts.map +1 -1
  88. package/ts/cjs/logger.js +2 -2
  89. package/ts/cjs/logger.js.map +1 -1
  90. package/ts/cjs/logger.test.js +1 -1
  91. package/ts/cjs/logger.test.js.map +1 -1
  92. package/ts/cjs/mutable-url.d.ts +72 -0
  93. package/ts/cjs/mutable-url.d.ts.map +1 -0
  94. package/ts/cjs/mutable-url.js +281 -0
  95. package/ts/cjs/mutable-url.js.map +1 -0
  96. package/ts/cjs/mutable-url.test.d.ts +2 -0
  97. package/ts/cjs/mutable-url.test.d.ts.map +1 -0
  98. package/ts/cjs/mutable-url.test.js +452 -0
  99. package/ts/cjs/mutable-url.test.js.map +1 -0
  100. package/ts/cjs/uri.d.ts +4 -30
  101. package/ts/cjs/uri.d.ts.map +1 -1
  102. package/ts/cjs/uri.js +32 -161
  103. package/ts/cjs/uri.js.map +1 -1
  104. package/ts/cjs/uri.test.js +24 -36
  105. package/ts/cjs/uri.test.js.map +1 -1
  106. package/ts/cjs/utils/coerce-uint8.d.ts.map +1 -1
  107. package/ts/cjs/utils/coerce-uint8.js.map +1 -1
  108. package/ts/cjs/version.js +1 -1
  109. package/ts/esm/cli/patch-version-cmd.d.ts +1 -1
  110. package/ts/esm/cli/patch-version-cmd.d.ts.map +1 -1
  111. package/ts/esm/cli/patch-version-cmd.js +25 -9
  112. package/ts/esm/cli/patch-version-cmd.js.map +1 -1
  113. package/ts/esm/index.d.ts +1 -0
  114. package/ts/esm/index.d.ts.map +1 -1
  115. package/ts/esm/index.js +1 -0
  116. package/ts/esm/index.js.map +1 -1
  117. package/ts/esm/load-asset.d.ts.map +1 -1
  118. package/ts/esm/load-asset.js +7 -6
  119. package/ts/esm/load-asset.js.map +1 -1
  120. package/ts/esm/logger.d.ts.map +1 -1
  121. package/ts/esm/logger.js +2 -2
  122. package/ts/esm/logger.js.map +1 -1
  123. package/ts/esm/logger.test.js +2 -2
  124. package/ts/esm/logger.test.js.map +1 -1
  125. package/ts/esm/mutable-url.d.ts +72 -0
  126. package/ts/esm/mutable-url.d.ts.map +1 -0
  127. package/ts/esm/mutable-url.js +275 -0
  128. package/ts/esm/mutable-url.js.map +1 -0
  129. package/ts/esm/mutable-url.test.d.ts +2 -0
  130. package/ts/esm/mutable-url.test.d.ts.map +1 -0
  131. package/ts/esm/mutable-url.test.js +450 -0
  132. package/ts/esm/mutable-url.test.js.map +1 -0
  133. package/ts/esm/uri.d.ts +4 -30
  134. package/ts/esm/uri.d.ts.map +1 -1
  135. package/ts/esm/uri.js +26 -154
  136. package/ts/esm/uri.js.map +1 -1
  137. package/ts/esm/uri.test.js +25 -37
  138. package/ts/esm/uri.test.js.map +1 -1
  139. package/ts/esm/utils/coerce-uint8.d.ts.map +1 -1
  140. package/ts/esm/utils/coerce-uint8.js.map +1 -1
  141. package/ts/esm/version.js +1 -1
@@ -72,6 +72,7 @@ export function generateVersionTsCmd(): ReturnType<typeof command> {
72
72
  type: string,
73
73
  description: "Path to the file containing the version, defaults to './src/version.ts'.",
74
74
  }),
75
+
75
76
  tsconfig: option({
76
77
  long: "tsconfig",
77
78
  short: "t",
@@ -145,7 +146,7 @@ function setupDenoJson(packageJsonFile: string, jsrJsonFile: string): void {
145
146
  const packageJson = JSON.parse(fs.readFileSync(packageJsonFile).toString()) as { dependencies: Record<string, string> };
146
147
  const jsrJson = JSON.parse(fs.readFileSync(jsrJsonFile).toString()) as { imports: Record<string, string> };
147
148
  jsrJson.imports = Object.fromEntries(
148
- Array.from(Object.entries(packageJson.dependencies)).map(([k, v]) => [k, `npm:${k}@${v.replace(/^npm:/, "")}`]),
149
+ Array.from(Object.entries(packageJson.dependencies ?? {})).map(([k, v]) => [k, `npm:${k}@${v.replace(/^npm:/, "")}`]),
149
150
  );
150
151
  fs.writeFileSync(jsrJsonFile, JSON.stringify(jsrJson, undefined, 2) + "\n");
151
152
  }
@@ -181,7 +182,7 @@ export function setUpDenoJsonCmd(): ReturnType<typeof command> {
181
182
  });
182
183
  }
183
184
 
184
- export async function preparePubdir(pubdir: string, version: string): Promise<void> {
185
+ export async function preparePubdir(pubdir: string, version: string, baseDir: string, srcDir: string): Promise<void> {
185
186
  // Set shell options equivalent to 'set -ex'
186
187
  $.verbose = true;
187
188
 
@@ -195,7 +196,7 @@ export async function preparePubdir(pubdir: string, version: string): Promise<vo
195
196
  await $`mkdir -p ${pubdir}`;
196
197
 
197
198
  // Copy files to pubdir
198
- await $`cp -pr ../.gitignore ../README.md ../LICENSE ./dist/ts/ ${pubdir}/`;
199
+ await $`cp -pr ${path.join(baseDir, ".gitignore")} ${path.join(baseDir, "README.md")} ${path.join(baseDir, "LICENSE")} ./dist/ts/ ${pubdir}/`;
199
200
 
200
201
  // Copy from dist/pkg
201
202
  cd("dist/pkg");
@@ -203,8 +204,8 @@ export async function preparePubdir(pubdir: string, version: string): Promise<vo
203
204
  cd("../..");
204
205
 
205
206
  // Copy from src
206
- cd("src");
207
- await $`cp -pr . ../${pubdir}/src/`;
207
+ cd(srcDir);
208
+ await $`cp -pr . ../${pubdir}/${srcDir}/`;
208
209
  cd("..");
209
210
 
210
211
  // Rename .js files to .cjs in pubdir/cjs
@@ -231,20 +232,20 @@ export async function preparePubdir(pubdir: string, version: string): Promise<vo
231
232
  await $`cp package.json ${pubdir}/`;
232
233
 
233
234
  // Clean up test files in pubdir/src
234
- cd(`${pubdir}/src`);
235
+ cd(`${pubdir}/${srcDir}`);
235
236
  await $`rm -f test/test-exit-handler.* ./utils/stream-test-helper.ts`.catch(() => {
236
237
  // Ignore errors if files don't exist
237
238
  });
238
239
  cd("../..");
239
240
 
240
241
  // Remove __screenshots__ directories
241
- const screenshotDirs = await glob(`${pubdir}/src/**/__screenshots__`);
242
+ const screenshotDirs = await glob(`${pubdir}/${srcDir}/**/__screenshots__`);
242
243
  for (const dir of screenshotDirs) {
243
244
  await $`rm -rf ${dir}`;
244
245
  }
245
246
 
246
247
  // Remove test files
247
- const testFiles = await glob(`${pubdir}/src/**/*.test.ts`);
248
+ const testFiles = await glob(`${pubdir}/${srcDir}/**/*.test.ts`);
248
249
  for (const file of testFiles) {
249
250
  await $`rm -f ${file}`;
250
251
  }
@@ -280,6 +281,22 @@ export function preparePubdirCmd(): ReturnType<typeof command> {
280
281
  type: string,
281
282
  description: "Path to the pubdir, defaults to './pubdir'.",
282
283
  }),
284
+ srcDir: option({
285
+ long: "srcDir",
286
+ short: "s",
287
+ defaultValue: () => "src",
288
+ defaultValueIsSerializable: true,
289
+ type: string,
290
+ description: "Path to the src directory, defaults to './src'.",
291
+ }),
292
+ baseDir: option({
293
+ long: "baseDir",
294
+ short: "b",
295
+ defaultValue: () => "../",
296
+ defaultValueIsSerializable: true,
297
+ type: string,
298
+ description: "Path to the base directory of the project, defaults to '../'.",
299
+ }),
283
300
  version: option({
284
301
  long: "version",
285
302
  short: "v",
@@ -290,7 +307,7 @@ export function preparePubdirCmd(): ReturnType<typeof command> {
290
307
  }),
291
308
  },
292
309
  handler: async (args) => {
293
- await preparePubdir(args.pubdir, args.version);
310
+ await preparePubdir(args.pubdir, args.version, args.baseDir, args.srcDir);
294
311
  },
295
312
  });
296
313
  }
package/src/index.ts CHANGED
@@ -27,6 +27,7 @@ export * from "./coerce-binary.js";
27
27
  export * from "./is-promise.js";
28
28
  export * from "./app-context.js";
29
29
  export * from "./load-asset.js";
30
+ export * from "./mutable-url.js";
30
31
 
31
32
  // ugly
32
33
  export * as utils from "./utils/index.js";
package/src/load-asset.ts CHANGED
@@ -2,7 +2,7 @@ import { pathOps } from "./path-ops.js";
2
2
  import { Result, exception2Result } from "./result.js";
3
3
  import { runtimeFn } from "./runtime.js";
4
4
  import { TxtEnDecoderSingleton } from "./txt-en-decoder.js";
5
- import { CoerceURI, URI } from "./uri.js";
5
+ import { BuildURI, CoerceURI, URI } from "./uri.js";
6
6
 
7
7
  interface MockLoadAsset {
8
8
  fetch: typeof globalThis.fetch;
@@ -95,25 +95,26 @@ async function loadAssetReal(
95
95
  }
96
96
  return Result.Err(`cannot load file: ${baseUrl.url.toString()} from ${baseUrl.src}`);
97
97
  }
98
+ const urlToFetch = BuildURI.from(baseUrl.url);
98
99
  switch (baseUrl.src) {
99
100
  case "opts.fallBackUrl":
100
- baseUrl.url.pathname = opts.pathCleaner(baseUrl.url.pathname, localPath, "fallback");
101
+ urlToFetch.pathname(opts.pathCleaner(baseUrl.url.pathname, localPath, "fallback"));
101
102
  break;
102
103
  case "import.meta.url":
103
- baseUrl.url.pathname = opts.pathCleaner(baseUrl.url.pathname, localPath, "normal");
104
+ urlToFetch.pathname(opts.pathCleaner(baseUrl.url.pathname, localPath, "normal"));
104
105
  break;
105
106
  }
106
107
 
107
108
  const rRes = await exception2Result(() => {
108
- if (!baseUrl.url) {
109
+ if (!urlToFetch) {
109
110
  throw Error(`base url not found from ${baseUrl.src}`);
110
111
  }
111
- return callFetch(opts.mock)(baseUrl.url);
112
+ return callFetch(opts.mock)(urlToFetch.asURL());
112
113
  });
113
114
  if (rRes.isErr()) {
114
115
  if (baseUrl.src === "import.meta.url") {
115
116
  // eslint-disable-next-line no-console
116
- console.warn(`fetch failed for: ${baseUrl.url}`);
117
+ console.warn(`fetch failed for: ${urlToFetch.toString()}`);
117
118
  return loadAssetReal(fallBackBaseUrl(opts), localPath, opts);
118
119
  }
119
120
  return Result.Err(rRes);
package/src/logger.ts CHANGED
@@ -4,8 +4,9 @@ import { bin2string } from "./bin2text.js";
4
4
  import { Option } from "./option.js";
5
5
  import { Result } from "./result.js";
6
6
  import { TxtEnDecoder, TxtEnDecoderSingleton } from "./txt-en-decoder.js";
7
- import { CoerceURI, MutableURL } from "./uri.js";
7
+ import { CoerceURI } from "./uri.js";
8
8
  import { isJSON } from "./is-json.js";
9
+ import { ReadonlyURL } from "./mutable-url.js";
9
10
 
10
11
  export const Level = {
11
12
  WARN: "warn",
@@ -85,7 +86,7 @@ function logValueInternal(val: LogValueArg, ctx: LogValueStateInternal): LogValu
85
86
  return logValueInternal(ret, ctx);
86
87
  }
87
88
  }
88
- const resIsURI = MutableURL.from(val);
89
+ const resIsURI = ReadonlyURL.from(val);
89
90
  if (resIsURI.isOk()) {
90
91
  return new LogValue(() => resIsURI.Ok().toString());
91
92
  }
@@ -0,0 +1,365 @@
1
+ import { exception2Result, Result } from "./result.js";
2
+ import { hasHostPartProtocols } from "./uri.js";
3
+
4
+ // due to that the System URL class is has a strange behavior
5
+ // on different platforms, we need to implement our own URL class
6
+ const customInspectSymbol = Symbol.for("nodejs.util.inspect.custom");
7
+
8
+ const urlRegex = /^([a-z][a-z0-9_-]*):\/\/[^:]*$/i;
9
+
10
+ // there are deno which does not have URLSearchParams.entries() in types
11
+ export function* URLSearchParamsEntries(src: URLSearchParams): IterableIterator<[string, string]> {
12
+ const entries: [string, string][] = [];
13
+ src.forEach((v, k) => {
14
+ entries.push([k, v]);
15
+ });
16
+ for (const [key, value] of entries) {
17
+ yield [key, value];
18
+ }
19
+ }
20
+
21
+ export class ReadonlyURL extends URL {
22
+ protected readonly _sysURL: URL;
23
+ // private readonly _urlStr: string;
24
+
25
+ protected _protocol: string;
26
+ protected _pathname: string;
27
+ protected _hasHostpart: boolean;
28
+
29
+ static readonly fromThrow = (urlStr: string): ReadonlyURL => {
30
+ return new ReadonlyURL(urlStr);
31
+ };
32
+
33
+ static from(urlStr: string): Result<ReadonlyURL> {
34
+ if (urlRegex.test(urlStr)) {
35
+ return exception2Result(() => new ReadonlyURL(urlStr));
36
+ }
37
+ return Result.Err(`Invalid URL: ${urlStr}`);
38
+ }
39
+
40
+ protected constructor(urlStr: string) {
41
+ super("defect://does.not.exist");
42
+ const partedURL = urlStr.split(":");
43
+ this._hasHostpart = hasHostPartProtocols.has(partedURL[0]);
44
+ let hostPartUrl = ["http", ...partedURL.slice(1)].join(":");
45
+ if (!this._hasHostpart) {
46
+ const pathname = hostPartUrl.replace(/http:\/\/[/]*/, "").replace(/[#?].*$/, "");
47
+ hostPartUrl = hostPartUrl.replace(/http:\/\//, `http://localhost/${pathname}`);
48
+ }
49
+ try {
50
+ this._sysURL = new URL(hostPartUrl);
51
+ } catch (ie) {
52
+ const e = ie as Error;
53
+ e.message = `${e.message} for URL: ${urlStr}`;
54
+ throw e;
55
+ }
56
+ this._protocol = `${partedURL[0]}:`; // this._sysURL.protocol.replace(new RegExp("^cement-"), "");
57
+ if (this._hasHostpart) {
58
+ this._pathname = this._sysURL.pathname;
59
+ } else {
60
+ this._pathname = urlStr.replace(new RegExp(`^${this._protocol}//`), "").replace(/[#?].*$/, "");
61
+ }
62
+ // this.hash = this._sysURL.hash;
63
+ }
64
+
65
+ set origin(h: string) {
66
+ throw new Error("origin is readonly");
67
+ }
68
+
69
+ override get href(): string {
70
+ return this.toString();
71
+ }
72
+
73
+ set href(h: string) {
74
+ throw new Error("href is readonly");
75
+ }
76
+
77
+ override get password(): string {
78
+ return this._sysURL.password;
79
+ }
80
+
81
+ set password(h: string) {
82
+ throw new Error("password is readonly");
83
+ }
84
+
85
+ override get username(): string {
86
+ return this._sysURL.username;
87
+ }
88
+
89
+ set username(h: string) {
90
+ throw new Error("username is readonly");
91
+ }
92
+
93
+ toJSON(): string {
94
+ return this.toString();
95
+ }
96
+
97
+ [customInspectSymbol](): string {
98
+ // make node inspect to show the URL and not crash if URI is not http/https/file
99
+ return this.toString();
100
+ }
101
+
102
+ clone(): ReadonlyURL {
103
+ return this;
104
+ }
105
+
106
+ // Hash getter and setter
107
+ override get hash(): string {
108
+ return this._sysURL.hash;
109
+ }
110
+
111
+ set hash(h: string) {
112
+ throw new Error("hash is readonly");
113
+ }
114
+
115
+ // Host getter and setter
116
+ get host(): string {
117
+ if (!this._hasHostpart) {
118
+ throw new Error(
119
+ `you can use hostname only if protocol is ${this.toString()} ${JSON.stringify(Array.from(hasHostPartProtocols.keys()))}`,
120
+ );
121
+ }
122
+ return this._sysURL.host;
123
+ }
124
+
125
+ set host(h: string) {
126
+ throw new Error("host is readonly");
127
+ }
128
+
129
+ // Hostname getter and setter
130
+ get hostname(): string {
131
+ if (!this._hasHostpart) {
132
+ throw new Error(`you can use hostname only if protocol is ${JSON.stringify(Array.from(hasHostPartProtocols.keys()))}`);
133
+ }
134
+ return this._sysURL.hostname;
135
+ }
136
+
137
+ set hostname(h: string) {
138
+ throw new Error("hostname is readonly");
139
+ }
140
+
141
+ // Pathname getter and setter
142
+ override get pathname(): string {
143
+ return this._pathname;
144
+ }
145
+
146
+ set pathname(h: string) {
147
+ throw new Error("pathname is readonly");
148
+ }
149
+
150
+ // Port getter and setter
151
+ override get port(): string {
152
+ if (!this._hasHostpart) {
153
+ throw new Error(`you can use hostname only if protocol is ${JSON.stringify(Array.from(hasHostPartProtocols.keys()))}`);
154
+ }
155
+ return this._sysURL.port;
156
+ }
157
+
158
+ set port(h: string) {
159
+ throw new Error("port is readonly");
160
+ }
161
+
162
+ // Protocol getter and setter
163
+ override get protocol(): string {
164
+ return this._protocol;
165
+ }
166
+
167
+ set protocol(h: string) {
168
+ throw new Error("protocol is readonly");
169
+ }
170
+
171
+ // Search getter and setter
172
+ get search(): string {
173
+ let search = "";
174
+ if (this._sysURL.searchParams.size) {
175
+ for (const [key, value] of Array.from(URLSearchParamsEntries(this._sysURL.searchParams)).sort((a, b) =>
176
+ a[0].localeCompare(b[0]),
177
+ )) {
178
+ search += `${!search.length ? "?" : "&"}${key}=${encodeURIComponent(value)}`;
179
+ }
180
+ }
181
+ return search;
182
+ }
183
+
184
+ set search(h: string) {
185
+ throw new Error("search is readonly");
186
+ }
187
+
188
+ // SearchParams getter and setter
189
+ override get searchParams(): URLSearchParams {
190
+ return this._sysURL.searchParams;
191
+ }
192
+
193
+ set searchParams(h: URLSearchParams) {
194
+ throw new Error("searchParams is readonly");
195
+ }
196
+
197
+ override toString(): string {
198
+ const search = this.search;
199
+ let hostpart = "";
200
+ if (this._hasHostpart) {
201
+ hostpart = this._sysURL.hostname;
202
+ if (this._sysURL.port) {
203
+ hostpart += `:${this._sysURL.port}`;
204
+ }
205
+ if (!this._pathname.startsWith("/")) {
206
+ hostpart += "/";
207
+ }
208
+ }
209
+ if (this.username || this.password) {
210
+ hostpart = `${this.username}:${this.password}@${hostpart}`;
211
+ }
212
+ return `${this._protocol}//${hostpart}${this._pathname}${search}${this.hash}`;
213
+ }
214
+ }
215
+
216
+ export class WritableURL extends ReadonlyURL {
217
+ // override readonly hash: string;
218
+
219
+ static readonly fromThrow = (urlStr: string): WritableURL => {
220
+ return new WritableURL(urlStr);
221
+ };
222
+
223
+ static from(urlStr: string): Result<WritableURL> {
224
+ if (urlRegex.test(urlStr)) {
225
+ return exception2Result(() => new WritableURL(urlStr));
226
+ }
227
+ return Result.Err(`Invalid URL: ${urlStr}`);
228
+ }
229
+
230
+ private constructor(urlStr: string) {
231
+ super(urlStr);
232
+ }
233
+
234
+ override toJSON(): string {
235
+ return this.toString();
236
+ }
237
+
238
+ [customInspectSymbol](): string {
239
+ // make node inspect to show the URL and not crash if URI is not http/https/file
240
+ return this.toString();
241
+ }
242
+
243
+ clone(): WritableURL {
244
+ return new WritableURL(this.toString());
245
+ }
246
+
247
+ set origin(_h: string) {
248
+ throw new Error("don't use origin");
249
+ }
250
+
251
+ get href(): string {
252
+ return super.href;
253
+ }
254
+
255
+ set href(h: string) {
256
+ throw new Error("don't use href");
257
+ }
258
+
259
+ override get password(): string {
260
+ return super.password;
261
+ }
262
+
263
+ set password(h: string) {
264
+ this._sysURL.password = h;
265
+ }
266
+
267
+ override get username(): string {
268
+ return super.username;
269
+ }
270
+
271
+ set username(h: string) {
272
+ this._sysURL.username = h;
273
+ }
274
+
275
+ // Hash getter and setter
276
+ override get hash(): string {
277
+ return super.hash;
278
+ }
279
+
280
+ override set hash(h: string) {
281
+ this._sysURL.hash = h;
282
+ }
283
+
284
+ // Host getter and setter
285
+ override get host(): string {
286
+ return super.host;
287
+ }
288
+
289
+ override set host(h: string) {
290
+ this._sysURL.host = h;
291
+ }
292
+
293
+ // Hostname getter and setter
294
+ override get hostname(): string {
295
+ return super.hostname;
296
+ }
297
+
298
+ override set hostname(h: string) {
299
+ if (!this._hasHostpart) {
300
+ throw new Error(`you can use hostname only if protocol is ${JSON.stringify(Array.from(hasHostPartProtocols.keys()))}`);
301
+ }
302
+ this._sysURL.hostname = h;
303
+ }
304
+
305
+ // Pathname getter and setter
306
+ override get pathname(): string {
307
+ return super.pathname;
308
+ }
309
+
310
+ override set pathname(p: string) {
311
+ this._pathname = p;
312
+ }
313
+
314
+ // Port getter and setter
315
+ override get port(): string {
316
+ return super.port;
317
+ }
318
+
319
+ override set port(p: string) {
320
+ if (!this._hasHostpart) {
321
+ throw new Error(`you can use port only if protocol is ${JSON.stringify(Array.from(hasHostPartProtocols.keys()))}`);
322
+ }
323
+ this._sysURL.port = p;
324
+ }
325
+
326
+ // Protocol getter and setter
327
+ override get protocol(): string {
328
+ return super.protocol;
329
+ }
330
+
331
+ override set protocol(p: string) {
332
+ if (!p.endsWith(":")) {
333
+ p = `${p}:`;
334
+ }
335
+ this._protocol = p;
336
+ }
337
+
338
+ // Search getter and setter
339
+ override get search(): string {
340
+ return super.search;
341
+ }
342
+
343
+ override set search(h: string) {
344
+ this._sysURL.search = h;
345
+ }
346
+
347
+ // SearchParams getter and setter
348
+ override get searchParams(): URLSearchParams {
349
+ return super.searchParams;
350
+ }
351
+
352
+ override set searchParams(h: URLSearchParams) {
353
+ const toDel = new Set<string>();
354
+ for (const [key] of URLSearchParamsEntries(this._sysURL.searchParams)) {
355
+ toDel.add(key);
356
+ }
357
+ for (const [key, value] of URLSearchParamsEntries(h)) {
358
+ this._sysURL.searchParams.set(key, value);
359
+ toDel.delete(key);
360
+ }
361
+ for (const key of toDel) {
362
+ this._sysURL.searchParams.delete(key);
363
+ }
364
+ }
365
+ }