@ccheever/exact-ibex-runtime 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.
Files changed (161) hide show
  1. package/package.json +63 -0
  2. package/src/abort/AbortController.ts +23 -0
  3. package/src/abort/AbortSignal.ts +152 -0
  4. package/src/abort/index.ts +2 -0
  5. package/src/accessibility.ts +12 -0
  6. package/src/arraybuffer-detach.ts +109 -0
  7. package/src/base64/base64.ts +168 -0
  8. package/src/base64/index.ts +1 -0
  9. package/src/blob/Blob.ts +259 -0
  10. package/src/blob/File.ts +59 -0
  11. package/src/blob/FormData.ts +323 -0
  12. package/src/blob/index.ts +3 -0
  13. package/src/bootstrap.ts +1946 -0
  14. package/src/broadcast/BroadcastChannel.ts +280 -0
  15. package/src/broadcast/index.ts +5 -0
  16. package/src/cache/Cache.ts +349 -0
  17. package/src/cache/CacheStorage.ts +89 -0
  18. package/src/cache/index.ts +27 -0
  19. package/src/camera/index.ts +6202 -0
  20. package/src/camera/processor.worker.ts +194 -0
  21. package/src/camera/scene.ts +195 -0
  22. package/src/clipboard/Clipboard.ts +129 -0
  23. package/src/clipboard/ClipboardItem.ts +97 -0
  24. package/src/clipboard/index.ts +6 -0
  25. package/src/clone/index.ts +1 -0
  26. package/src/clone/structuredClone.ts +389 -0
  27. package/src/clone/transferableSymbols.ts +2 -0
  28. package/src/compression/CompressionStream.ts +146 -0
  29. package/src/compression/DecompressionStream.ts +342 -0
  30. package/src/compression/index.ts +4 -0
  31. package/src/console/Console.ts +341 -0
  32. package/src/console/index.ts +2 -0
  33. package/src/core/accessibility-state.ts +263 -0
  34. package/src/core/accessibility.ts +184 -0
  35. package/src/core/agent-state.ts +37 -0
  36. package/src/core/diagnostics-logs.ts +144 -0
  37. package/src/core/host-call-bridge.ts +16 -0
  38. package/src/core/i18n-helpers.ts +189 -0
  39. package/src/core/locale-state.ts +253 -0
  40. package/src/core/locale.ts +95 -0
  41. package/src/crypto/Crypto.ts +2743 -0
  42. package/src/crypto/index.ts +1 -0
  43. package/src/diagnostics/logs.ts +7 -0
  44. package/src/encoding/TextDecoder.ts +1181 -0
  45. package/src/encoding/TextDecoderStream.ts +58 -0
  46. package/src/encoding/TextEncoder.ts +180 -0
  47. package/src/encoding/TextEncoderStream.ts +39 -0
  48. package/src/encoding/index.ts +8 -0
  49. package/src/events/CloseEvent.ts +91 -0
  50. package/src/events/DOMException.ts +409 -0
  51. package/src/events/ErrorEvent.ts +39 -0
  52. package/src/events/Event.ts +151 -0
  53. package/src/events/EventTarget.ts +280 -0
  54. package/src/events/FocusEvent.ts +27 -0
  55. package/src/events/KeyboardEvent.ts +46 -0
  56. package/src/events/MessageEvent.ts +61 -0
  57. package/src/events/ProgressEvent.ts +33 -0
  58. package/src/events/PromiseRejectionEvent.ts +31 -0
  59. package/src/events/index.ts +52 -0
  60. package/src/eventsource/EventSource.ts +371 -0
  61. package/src/eventsource/index.ts +2 -0
  62. package/src/fetch/Headers.ts +642 -0
  63. package/src/fetch/Request.ts +760 -0
  64. package/src/fetch/Response.ts +543 -0
  65. package/src/fetch/body.ts +1256 -0
  66. package/src/fetch/cookie-jar.ts +566 -0
  67. package/src/fetch/demo.ts +207 -0
  68. package/src/fetch/errors.ts +101 -0
  69. package/src/fetch/fetch.ts +2610 -0
  70. package/src/fetch/index.ts +101 -0
  71. package/src/fetch/native-bridge.ts +65 -0
  72. package/src/fetch/types.ts +258 -0
  73. package/src/filereader/FileReader.ts +236 -0
  74. package/src/filereader/index.ts +1 -0
  75. package/src/fs/Dirent.ts +39 -0
  76. package/src/fs/ExactFile.ts +450 -0
  77. package/src/fs/Stats.ts +80 -0
  78. package/src/fs/index.ts +944 -0
  79. package/src/fs/promises.ts +386 -0
  80. package/src/fs/shared.ts +328 -0
  81. package/src/http-server/index.js +697 -0
  82. package/src/http-server/index.ts +27 -0
  83. package/src/identity.generated.ts +14 -0
  84. package/src/index.ts +283 -0
  85. package/src/indexeddb/IDBCursor.ts +188 -0
  86. package/src/indexeddb/IDBDatabase.ts +343 -0
  87. package/src/indexeddb/IDBFactory.ts +269 -0
  88. package/src/indexeddb/IDBIndex.ts +194 -0
  89. package/src/indexeddb/IDBKeyRange.ts +109 -0
  90. package/src/indexeddb/IDBObjectStore.ts +468 -0
  91. package/src/indexeddb/IDBRequest.ts +163 -0
  92. package/src/indexeddb/IDBTransaction.ts +207 -0
  93. package/src/indexeddb/index.ts +34 -0
  94. package/src/indexeddb/utils.ts +52 -0
  95. package/src/inspect/index.ts +1 -0
  96. package/src/inspect/inspect.ts +465 -0
  97. package/src/internal/detect.ts +104 -0
  98. package/src/locale.ts +10 -0
  99. package/src/location/index.ts +1059 -0
  100. package/src/locks/LockManager.ts +460 -0
  101. package/src/locks/index.ts +12 -0
  102. package/src/media/VideoFrame.ts +58 -0
  103. package/src/messaging/MessageChannel.ts +31 -0
  104. package/src/messaging/MessagePort.ts +180 -0
  105. package/src/messaging/index.ts +2 -0
  106. package/src/messaging.ts +247 -0
  107. package/src/native/NativeModules.ts +354 -0
  108. package/src/native/index.ts +1 -0
  109. package/src/navigator/Navigator.ts +351 -0
  110. package/src/navigator/index.ts +1 -0
  111. package/src/node/Buffer.ts +1786 -0
  112. package/src/node/index.ts +4 -0
  113. package/src/node/path.ts +495 -0
  114. package/src/node/process.ts +2528 -0
  115. package/src/performance/Performance.ts +532 -0
  116. package/src/performance/index.ts +21 -0
  117. package/src/polyfills/array.ts +236 -0
  118. package/src/polyfills/arraybuffer.ts +172 -0
  119. package/src/polyfills/groupby.ts +85 -0
  120. package/src/polyfills/index.ts +85 -0
  121. package/src/polyfills/intl.ts +1956 -0
  122. package/src/polyfills/iterator.ts +479 -0
  123. package/src/polyfills/promise.ts +37 -0
  124. package/src/polyfills/set.ts +245 -0
  125. package/src/polyfills/string.ts +85 -0
  126. package/src/polyfills/typedarray.ts +110 -0
  127. package/src/promise-rejection-tracking.ts +464 -0
  128. package/src/react-native/index.ts +388 -0
  129. package/src/runtime-entry.ts +55 -0
  130. package/src/scheduling/AnimationFrame.ts +105 -0
  131. package/src/scheduling/IdleCallback.ts +167 -0
  132. package/src/scheduling/index.ts +13 -0
  133. package/src/security/Capabilities.ts +1146 -0
  134. package/src/security/Permissions.ts +392 -0
  135. package/src/security/capability-bits.generated.ts +63 -0
  136. package/src/security/index.ts +16 -0
  137. package/src/sqlite/Database.ts +456 -0
  138. package/src/sqlite/Statement.ts +206 -0
  139. package/src/sqlite/constants.ts +79 -0
  140. package/src/sqlite/errors.ts +25 -0
  141. package/src/sqlite/index.ts +34 -0
  142. package/src/sqlite/module.js +438 -0
  143. package/src/storage/Storage.ts +291 -0
  144. package/src/storage/StorageManager.ts +91 -0
  145. package/src/storage/index.ts +3 -0
  146. package/src/stream-compat.ts +47 -0
  147. package/src/streams/ReadableStream.ts +4131 -0
  148. package/src/streams/TransformStream.ts +375 -0
  149. package/src/streams/WritableStream.ts +866 -0
  150. package/src/streams/index.ts +41 -0
  151. package/src/timers/Timers.ts +296 -0
  152. package/src/timers/index.ts +11 -0
  153. package/src/url/URL.ts +656 -0
  154. package/src/url/URLPattern.ts +850 -0
  155. package/src/url/URLSearchParams.ts +244 -0
  156. package/src/url/index.ts +9 -0
  157. package/src/websocket/WebSocket.ts +770 -0
  158. package/src/websocket/WebSocketError.ts +52 -0
  159. package/src/websocket/WebSocketStream.ts +628 -0
  160. package/src/websocket/index.ts +7 -0
  161. package/src/window/index.ts +872 -0
@@ -0,0 +1,850 @@
1
+ /**
2
+ * URLPattern - Web API Implementation
3
+ *
4
+ * Implements the URLPattern standard for matching URLs against patterns.
5
+ * Supports wildcards (*), named groups (:name), regex groups ((regex)),
6
+ * and optional segments ({pattern}?).
7
+ *
8
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/URLPattern
9
+ * @see https://urlpattern.spec.whatwg.org/
10
+ */
11
+
12
+ // ============================================================================
13
+ // Types
14
+ // ============================================================================
15
+
16
+ /**
17
+ * Input for URLPattern constructor - either a string URL pattern or
18
+ * an object with component patterns.
19
+ */
20
+ export interface URLPatternInit {
21
+ protocol?: string;
22
+ username?: string;
23
+ password?: string;
24
+ hostname?: string;
25
+ port?: string;
26
+ pathname?: string;
27
+ search?: string;
28
+ hash?: string;
29
+ baseURL?: string;
30
+ }
31
+
32
+ /**
33
+ * Result of a component match, containing the matched input and
34
+ * any named/positional capture groups.
35
+ */
36
+ export interface URLPatternComponentResult {
37
+ input: string;
38
+ groups: Record<string, string | undefined>;
39
+ }
40
+
41
+ /**
42
+ * Full result of URLPattern.exec(), with match results for each component.
43
+ */
44
+ export interface URLPatternResult {
45
+ inputs: [URLPatternInput] | [URLPatternInput, string];
46
+ protocol: URLPatternComponentResult;
47
+ username: URLPatternComponentResult;
48
+ password: URLPatternComponentResult;
49
+ hostname: URLPatternComponentResult;
50
+ port: URLPatternComponentResult;
51
+ pathname: URLPatternComponentResult;
52
+ search: URLPatternComponentResult;
53
+ hash: URLPatternComponentResult;
54
+ }
55
+
56
+ export type URLPatternInput = string | URLPatternInit;
57
+
58
+ // ============================================================================
59
+ // Internal: Pattern Compilation
60
+ // ============================================================================
61
+
62
+ /**
63
+ * Represents a compiled component pattern with its regex and group names.
64
+ */
65
+ interface CompiledComponent {
66
+ pattern: string;
67
+ regex: RegExp;
68
+ groupNames: string[];
69
+ }
70
+
71
+ /** The set of URL component names we track. */
72
+ const COMPONENTS = [
73
+ "protocol",
74
+ "username",
75
+ "password",
76
+ "hostname",
77
+ "port",
78
+ "pathname",
79
+ "search",
80
+ "hash",
81
+ ] as const;
82
+
83
+ type ComponentName = (typeof COMPONENTS)[number];
84
+
85
+ /**
86
+ * Default patterns for each component when not specified.
87
+ * These match "anything" in a way appropriate for that component.
88
+ */
89
+ const DEFAULT_PATTERNS: Record<ComponentName, string> = {
90
+ protocol: "*",
91
+ username: "*",
92
+ password: "*",
93
+ hostname: "*",
94
+ port: "*",
95
+ pathname: "*",
96
+ search: "*",
97
+ hash: "*",
98
+ };
99
+
100
+ /**
101
+ * Characters that separate segments in each component.
102
+ * Used to determine what a bare `:name` group should NOT match.
103
+ */
104
+ function getSegmentWildcard(component: ComponentName): string {
105
+ switch (component) {
106
+ case "pathname":
107
+ // Named groups in pathname should not cross `/`
108
+ return "[^/]+";
109
+ case "hostname":
110
+ // Named groups in hostname should not cross `.`
111
+ return "[^.]+";
112
+ default:
113
+ // For other components, match everything
114
+ return "[^]+";
115
+ }
116
+ }
117
+
118
+ /**
119
+ * For a full wildcard `*`, determine what regex to use.
120
+ */
121
+ function getFullWildcard(component: ComponentName): string {
122
+ return ".*";
123
+ }
124
+
125
+ /**
126
+ * Escape a string for use in a regular expression.
127
+ */
128
+ function escapeRegExp(str: string): string {
129
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
130
+ }
131
+
132
+ /**
133
+ * Parse and compile a pattern string for a given component into a RegExp.
134
+ *
135
+ * Supported syntax:
136
+ * :name - Named parameter (matches segment chars)
137
+ * * - Wildcard (matches everything, captured as group 0, 1, ...)
138
+ * (regex) - Custom regex group
139
+ * :name(regex)- Named parameter with custom regex
140
+ * {pattern}? - Optional group (the ? makes the entire group optional)
141
+ * literal - Literal text (escaped)
142
+ *
143
+ * Returns a CompiledComponent with the original pattern, compiled regex,
144
+ * and an ordered list of group names.
145
+ */
146
+ function compileComponent(
147
+ pattern: string,
148
+ component: ComponentName,
149
+ ): CompiledComponent {
150
+ const groupNames: string[] = [];
151
+ let regexStr = "^";
152
+ let i = 0;
153
+ let unnamedIndex = 0;
154
+
155
+ while (i < pattern.length) {
156
+ const ch = pattern[i];
157
+
158
+ // ----- Optional group: {pattern}? -----
159
+ if (ch === "{") {
160
+ const closeBrace = findMatchingBrace(pattern, i);
161
+ if (closeBrace === -1) {
162
+ // No matching brace, treat as literal
163
+ regexStr += escapeRegExp(ch);
164
+ i++;
165
+ continue;
166
+ }
167
+
168
+ const innerPattern = pattern.slice(i + 1, closeBrace);
169
+ const isOptional =
170
+ closeBrace + 1 < pattern.length && pattern[closeBrace + 1] === "?";
171
+
172
+ // Recursively compile the inner pattern
173
+ const inner = compileComponent(innerPattern, component);
174
+ // Strip the ^ and $ anchors from inner regex
175
+ const innerRegex = inner.regex.source.slice(1, -1);
176
+
177
+ if (isOptional) {
178
+ regexStr += `(?:${innerRegex})?`;
179
+ i = closeBrace + 2; // skip }?
180
+ } else {
181
+ regexStr += `(?:${innerRegex})`;
182
+ i = closeBrace + 1; // skip }
183
+ }
184
+
185
+ // Merge inner group names
186
+ for (const name of inner.groupNames) {
187
+ groupNames.push(name);
188
+ }
189
+ continue;
190
+ }
191
+
192
+ // ----- Named parameter: :name or :name(regex) -----
193
+ if (ch === ":") {
194
+ i++;
195
+ let name = "";
196
+ while (i < pattern.length && /[\w]/.test(pattern[i])) {
197
+ name += pattern[i];
198
+ i++;
199
+ }
200
+
201
+ if (name === "") {
202
+ // Bare colon, treat as literal
203
+ regexStr += escapeRegExp(":");
204
+ continue;
205
+ }
206
+
207
+ groupNames.push(name);
208
+
209
+ // Check for custom regex after the name: :name(regex)
210
+ if (i < pattern.length && pattern[i] === "(") {
211
+ const closeParen = findMatchingParen(pattern, i);
212
+ if (closeParen !== -1) {
213
+ const customRegex = pattern.slice(i + 1, closeParen);
214
+ regexStr += `(${customRegex})`;
215
+ i = closeParen + 1;
216
+ } else {
217
+ regexStr += `(${getSegmentWildcard(component)})`;
218
+ }
219
+ } else {
220
+ regexStr += `(${getSegmentWildcard(component)})`;
221
+ }
222
+ continue;
223
+ }
224
+
225
+ // ----- Wildcard: * -----
226
+ if (ch === "*") {
227
+ groupNames.push(String(unnamedIndex));
228
+ unnamedIndex++;
229
+ regexStr += `(${getFullWildcard(component)})`;
230
+ i++;
231
+ continue;
232
+ }
233
+
234
+ // ----- Custom regex group: (regex) -----
235
+ if (ch === "(") {
236
+ const closeParen = findMatchingParen(pattern, i);
237
+ if (closeParen !== -1) {
238
+ const customRegex = pattern.slice(i + 1, closeParen);
239
+ groupNames.push(String(unnamedIndex));
240
+ unnamedIndex++;
241
+ regexStr += `(${customRegex})`;
242
+ i = closeParen + 1;
243
+ continue;
244
+ }
245
+ // No matching paren, treat as literal
246
+ regexStr += escapeRegExp(ch);
247
+ i++;
248
+ continue;
249
+ }
250
+
251
+ // ----- Literal character -----
252
+ regexStr += escapeRegExp(ch);
253
+ i++;
254
+ }
255
+
256
+ regexStr += "$";
257
+
258
+ return {
259
+ pattern,
260
+ regex: new RegExp(regexStr),
261
+ groupNames,
262
+ };
263
+ }
264
+
265
+ /**
266
+ * Find the matching closing brace for an opening brace at position `start`.
267
+ * Handles nested braces.
268
+ */
269
+ function findMatchingBrace(str: string, start: number): number {
270
+ let depth = 1;
271
+ let i = start + 1;
272
+ while (i < str.length && depth > 0) {
273
+ if (str[i] === "{") depth++;
274
+ else if (str[i] === "}") depth--;
275
+ if (depth === 0) return i;
276
+ i++;
277
+ }
278
+ return -1;
279
+ }
280
+
281
+ /**
282
+ * Find the matching closing parenthesis for an opening paren at position `start`.
283
+ * Handles nested parens and escaped chars.
284
+ */
285
+ function findMatchingParen(str: string, start: number): number {
286
+ let depth = 1;
287
+ let i = start + 1;
288
+ while (i < str.length && depth > 0) {
289
+ if (str[i] === "\\") {
290
+ i += 2; // Skip escaped character
291
+ continue;
292
+ }
293
+ if (str[i] === "(") depth++;
294
+ else if (str[i] === ")") depth--;
295
+ if (depth === 0) return i;
296
+ i++;
297
+ }
298
+ return -1;
299
+ }
300
+
301
+ // ============================================================================
302
+ // Internal: URL Parsing Helpers
303
+ // ============================================================================
304
+
305
+ /**
306
+ * Parse a URL string (or URLPatternInit) into its components.
307
+ * Returns an object with each component as a string.
308
+ */
309
+ function parseURLInput(
310
+ input: URLPatternInput,
311
+ baseURL?: string,
312
+ ): Record<ComponentName, string> {
313
+ if (typeof input === "object") {
314
+ const result: Record<ComponentName, string> = {
315
+ protocol: "",
316
+ username: "",
317
+ password: "",
318
+ hostname: "",
319
+ port: "",
320
+ pathname: "",
321
+ search: "",
322
+ hash: "",
323
+ };
324
+
325
+ // If baseURL is provided, use it as defaults
326
+ if (input.baseURL) {
327
+ const base = parseURLString(input.baseURL);
328
+ for (const comp of COMPONENTS) {
329
+ result[comp] = base[comp];
330
+ }
331
+ }
332
+
333
+ // Override with provided values
334
+ for (const comp of COMPONENTS) {
335
+ if (input[comp] !== undefined) {
336
+ result[comp] = input[comp]!;
337
+ }
338
+ }
339
+
340
+ return result;
341
+ }
342
+
343
+ // It's a string URL
344
+ const urlStr = baseURL ? resolveURL(input, baseURL) : input;
345
+ return parseURLString(urlStr);
346
+ }
347
+
348
+ /**
349
+ * Simple URL resolution: resolve `url` against `base`.
350
+ */
351
+ function resolveURL(url: string, base: string): string {
352
+ // If url already has a protocol, it's absolute
353
+ if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url)) {
354
+ return url;
355
+ }
356
+
357
+ const baseParts = parseURLString(base);
358
+
359
+ if (url.startsWith("//")) {
360
+ // Protocol-relative
361
+ return baseParts.protocol + ":" + url;
362
+ }
363
+
364
+ if (url.startsWith("/")) {
365
+ // Absolute path
366
+ const authority = buildAuthority(baseParts);
367
+ return baseParts.protocol + "://" + authority + url;
368
+ }
369
+
370
+ // Relative path
371
+ const authority = buildAuthority(baseParts);
372
+ const basePath = baseParts.pathname.slice(
373
+ 0,
374
+ baseParts.pathname.lastIndexOf("/") + 1,
375
+ );
376
+ return baseParts.protocol + "://" + authority + basePath + url;
377
+ }
378
+
379
+ /**
380
+ * Build the authority section (user:pass@host:port) from parsed components.
381
+ */
382
+ function buildAuthority(parts: Record<ComponentName, string>): string {
383
+ let authority = "";
384
+ if (parts.username) {
385
+ authority += parts.username;
386
+ if (parts.password) {
387
+ authority += ":" + parts.password;
388
+ }
389
+ authority += "@";
390
+ }
391
+ authority += parts.hostname;
392
+ if (parts.port) {
393
+ authority += ":" + parts.port;
394
+ }
395
+ return authority;
396
+ }
397
+
398
+ /**
399
+ * Parse a URL string into its component parts.
400
+ * This is a pattern-aware parser that handles both actual URLs and patterns.
401
+ */
402
+ function parseURLString(url: string): Record<ComponentName, string> {
403
+ const result: Record<ComponentName, string> = {
404
+ protocol: "",
405
+ username: "",
406
+ password: "",
407
+ hostname: "",
408
+ port: "",
409
+ pathname: "",
410
+ search: "",
411
+ hash: "",
412
+ };
413
+
414
+ let remaining = url;
415
+
416
+ // Extract hash
417
+ const hashIdx = remaining.indexOf("#");
418
+ if (hashIdx !== -1) {
419
+ result.hash = remaining.slice(hashIdx + 1);
420
+ remaining = remaining.slice(0, hashIdx);
421
+ }
422
+
423
+ // Extract search
424
+ const searchIdx = remaining.indexOf("?");
425
+ if (searchIdx !== -1) {
426
+ result.search = remaining.slice(searchIdx + 1);
427
+ remaining = remaining.slice(0, searchIdx);
428
+ }
429
+
430
+ // Extract protocol
431
+ const protoMatch = remaining.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):(.*)$/);
432
+ if (protoMatch) {
433
+ result.protocol = protoMatch[1].toLowerCase();
434
+ remaining = protoMatch[2];
435
+ }
436
+
437
+ // Extract authority
438
+ if (remaining.startsWith("//")) {
439
+ remaining = remaining.slice(2);
440
+
441
+ // Find end of authority (starts of path)
442
+ let authEnd = remaining.length;
443
+ const slashIdx = remaining.indexOf("/");
444
+ if (slashIdx !== -1) {
445
+ authEnd = slashIdx;
446
+ }
447
+
448
+ const authority = remaining.slice(0, authEnd);
449
+ remaining = remaining.slice(authEnd);
450
+
451
+ // Parse userinfo
452
+ const atIdx = authority.lastIndexOf("@");
453
+ let hostPart = authority;
454
+ if (atIdx !== -1) {
455
+ const userinfo = authority.slice(0, atIdx);
456
+ hostPart = authority.slice(atIdx + 1);
457
+
458
+ const colonIdx = userinfo.indexOf(":");
459
+ if (colonIdx !== -1) {
460
+ result.username = userinfo.slice(0, colonIdx);
461
+ result.password = userinfo.slice(colonIdx + 1);
462
+ } else {
463
+ result.username = userinfo;
464
+ }
465
+ }
466
+
467
+ // Parse host:port
468
+ // Handle IPv6
469
+ if (hostPart.startsWith("[")) {
470
+ const bracketEnd = hostPart.indexOf("]");
471
+ if (bracketEnd !== -1) {
472
+ result.hostname = hostPart.slice(0, bracketEnd + 1);
473
+ if (
474
+ bracketEnd + 1 < hostPart.length &&
475
+ hostPart[bracketEnd + 1] === ":"
476
+ ) {
477
+ result.port = hostPart.slice(bracketEnd + 2);
478
+ }
479
+ } else {
480
+ result.hostname = hostPart;
481
+ }
482
+ } else {
483
+ const colonIdx = hostPart.lastIndexOf(":");
484
+ if (colonIdx !== -1) {
485
+ result.hostname = hostPart.slice(0, colonIdx);
486
+ result.port = hostPart.slice(colonIdx + 1);
487
+ } else {
488
+ result.hostname = hostPart;
489
+ }
490
+ }
491
+ }
492
+
493
+ // Remaining is the pathname
494
+ result.pathname = remaining || "/";
495
+
496
+ return result;
497
+ }
498
+
499
+ /**
500
+ * Parse a pattern string that may contain URL components.
501
+ * Similar to parseURLString but designed for patterns where components
502
+ * may contain wildcard and pattern syntax.
503
+ */
504
+ function parsePatternString(pattern: string): Record<ComponentName, string> {
505
+ const result: Record<ComponentName, string> = {
506
+ protocol: "*",
507
+ username: "*",
508
+ password: "*",
509
+ hostname: "*",
510
+ port: "*",
511
+ pathname: "*",
512
+ search: "*",
513
+ hash: "*",
514
+ };
515
+
516
+ let remaining = pattern;
517
+
518
+ // Extract hash
519
+ const hashIdx = findUnescapedChar(remaining, "#");
520
+ if (hashIdx !== -1) {
521
+ result.hash = remaining.slice(hashIdx + 1);
522
+ remaining = remaining.slice(0, hashIdx);
523
+ }
524
+
525
+ // Extract search
526
+ const searchIdx = findUnescapedChar(remaining, "?", true);
527
+ if (searchIdx !== -1) {
528
+ result.search = remaining.slice(searchIdx + 1);
529
+ remaining = remaining.slice(0, searchIdx);
530
+ }
531
+
532
+ // Extract protocol
533
+ // Look for a protocol-like pattern: something followed by ://
534
+ // But be careful not to match :name patterns
535
+ const protoEndIdx = remaining.indexOf("://");
536
+ if (protoEndIdx !== -1) {
537
+ result.protocol = remaining.slice(0, protoEndIdx);
538
+ remaining = remaining.slice(protoEndIdx + 3);
539
+
540
+ // Extract authority - everything up to the first /
541
+ const slashIdx = remaining.indexOf("/");
542
+ let authority: string;
543
+ if (slashIdx !== -1) {
544
+ authority = remaining.slice(0, slashIdx);
545
+ remaining = remaining.slice(slashIdx);
546
+ } else {
547
+ authority = remaining;
548
+ remaining = "";
549
+ }
550
+
551
+ // Parse userinfo@host from authority
552
+ const atIdx = authority.lastIndexOf("@");
553
+ let hostPart = authority;
554
+ if (atIdx !== -1) {
555
+ const userinfo = authority.slice(0, atIdx);
556
+ hostPart = authority.slice(atIdx + 1);
557
+
558
+ const colonIdx = userinfo.indexOf(":");
559
+ if (colonIdx !== -1) {
560
+ result.username = userinfo.slice(0, colonIdx);
561
+ result.password = userinfo.slice(colonIdx + 1);
562
+ } else {
563
+ result.username = userinfo;
564
+ }
565
+ }
566
+
567
+ // Parse host:port - but be careful with patterns
568
+ // We need to find a colon that separates hostname from port,
569
+ // not one that's part of a :name pattern
570
+ const portSep = findPortSeparator(hostPart);
571
+ if (portSep !== -1) {
572
+ result.hostname = hostPart.slice(0, portSep);
573
+ result.port = hostPart.slice(portSep + 1);
574
+ } else {
575
+ result.hostname = hostPart;
576
+ result.port = "*";
577
+ }
578
+ } else {
579
+ // No protocol, might be just a pathname pattern
580
+ // Check if it looks like an absolute path
581
+ if (
582
+ remaining.startsWith("/") ||
583
+ remaining.startsWith(":") ||
584
+ remaining.startsWith("*")
585
+ ) {
586
+ result.protocol = "*";
587
+ result.hostname = "*";
588
+ }
589
+ }
590
+
591
+ // Remaining is pathname
592
+ if (remaining) {
593
+ result.pathname = remaining;
594
+ }
595
+
596
+ return result;
597
+ }
598
+
599
+ /**
600
+ * Find a character in a string, skipping characters inside parentheses and braces.
601
+ * For '?', `topLevelOnly` skips '?' that are part of {pattern}? syntax.
602
+ */
603
+ function findUnescapedChar(
604
+ str: string,
605
+ ch: string,
606
+ topLevelOnly: boolean = false,
607
+ ): number {
608
+ let depth = 0;
609
+ for (let i = 0; i < str.length; i++) {
610
+ const c = str[i];
611
+ if (c === "(" || c === "{") depth++;
612
+ else if (c === ")" || c === "}") depth--;
613
+ else if (c === ch && depth === 0) {
614
+ if (topLevelOnly && ch === "?") {
615
+ // Skip ? that follows } (part of optional syntax)
616
+ if (i > 0 && str[i - 1] === "}") {
617
+ continue;
618
+ }
619
+ }
620
+ return i;
621
+ }
622
+ }
623
+ return -1;
624
+ }
625
+
626
+ /**
627
+ * Find the colon that separates hostname from port in a host pattern.
628
+ * Must distinguish between `host:port` and `host:name` patterns.
629
+ * A port separator colon is one where the part after it looks like
630
+ * a port (digits, *, or a named param).
631
+ * We scan from the right side.
632
+ */
633
+ function findPortSeparator(hostPart: string): number {
634
+ // Simple heuristic: find the last colon that's not preceded by
635
+ // the start-of-pattern or another colon (which would make it :name)
636
+ // and where the right side looks like a port pattern.
637
+
638
+ // If the entire string is a wildcard or named param, no port sep
639
+ if (hostPart === "*" || (hostPart.startsWith(":") && !hostPart.includes("."))) {
640
+ return -1;
641
+ }
642
+
643
+ // Look for pattern like hostname:port where port is \d+, *, or :name
644
+ // We look for a colon from the right
645
+ for (let i = hostPart.length - 1; i >= 0; i--) {
646
+ if (hostPart[i] === ":") {
647
+ // Check if this colon is a port separator
648
+ // The part after should look like a port (number, *, :name, or pattern)
649
+ const after = hostPart.slice(i + 1);
650
+ const before = hostPart.slice(0, i);
651
+
652
+ // If 'before' is empty, it's not a port separator
653
+ if (!before) continue;
654
+
655
+ // If 'after' is all digits, *, or starts with :, it's a port
656
+ if (/^\d+$/.test(after) || after === "*" || after.startsWith(":")) {
657
+ return i;
658
+ }
659
+
660
+ // If 'after' is a pattern in parens, it's a port
661
+ if (after.startsWith("(")) {
662
+ return i;
663
+ }
664
+ }
665
+ }
666
+
667
+ return -1;
668
+ }
669
+
670
+ // ============================================================================
671
+ // URLPattern Class
672
+ // ============================================================================
673
+
674
+ export class URLPattern {
675
+ private _components: Record<ComponentName, CompiledComponent>;
676
+
677
+ /**
678
+ * Create a new URLPattern.
679
+ *
680
+ * @param input - A pattern string (like "https://example.com/:path") or
681
+ * an object with component patterns ({pathname: "/users/:id"})
682
+ * @param baseURL - Optional base URL string (only when input is a string)
683
+ */
684
+ constructor(input: URLPatternInput, baseURL?: string) {
685
+ let patterns: Record<ComponentName, string>;
686
+
687
+ if (typeof input === "string") {
688
+ // Parse the pattern string into components
689
+ if (baseURL) {
690
+ // If baseURL is provided, parse the base first, then overlay the input
691
+ const baseParts = parsePatternString(baseURL);
692
+ const inputParts = parsePatternString(input);
693
+
694
+ // The input overrides the base for components it specifies
695
+ patterns = { ...baseParts };
696
+ for (const comp of COMPONENTS) {
697
+ if (inputParts[comp] !== "*" || comp === "pathname") {
698
+ patterns[comp] = inputParts[comp];
699
+ }
700
+ }
701
+ } else {
702
+ patterns = parsePatternString(input);
703
+ }
704
+ } else {
705
+ // Object input - use defaults for unspecified components
706
+ patterns = { ...DEFAULT_PATTERNS };
707
+
708
+ // If baseURL is provided in the init object, parse it first
709
+ if (input.baseURL) {
710
+ const baseParts = parsePatternString(input.baseURL);
711
+ for (let i = 0; i < COMPONENTS.length; i++) {
712
+ const comp = COMPONENTS[i];
713
+ patterns[comp] = baseParts[comp];
714
+ }
715
+ }
716
+
717
+ // Override with explicitly provided values
718
+ for (let i = 0; i < COMPONENTS.length; i++) {
719
+ const comp = COMPONENTS[i];
720
+ if (input[comp] !== undefined) {
721
+ patterns[comp] = input[comp]!;
722
+ }
723
+ }
724
+ }
725
+
726
+ // Compile each component pattern into a regex
727
+ this._components = {} as Record<ComponentName, CompiledComponent>;
728
+ for (let i = 0; i < COMPONENTS.length; i++) {
729
+ const comp = COMPONENTS[i];
730
+ this._components[comp] = compileComponent(patterns[comp], comp);
731
+ }
732
+ }
733
+
734
+ /**
735
+ * The compiled pattern string for the protocol component.
736
+ */
737
+ get protocol(): string {
738
+ return this._components.protocol.pattern;
739
+ }
740
+
741
+ /**
742
+ * The compiled pattern string for the username component.
743
+ */
744
+ get username(): string {
745
+ return this._components.username.pattern;
746
+ }
747
+
748
+ /**
749
+ * The compiled pattern string for the password component.
750
+ */
751
+ get password(): string {
752
+ return this._components.password.pattern;
753
+ }
754
+
755
+ /**
756
+ * The compiled pattern string for the hostname component.
757
+ */
758
+ get hostname(): string {
759
+ return this._components.hostname.pattern;
760
+ }
761
+
762
+ /**
763
+ * The compiled pattern string for the port component.
764
+ */
765
+ get port(): string {
766
+ return this._components.port.pattern;
767
+ }
768
+
769
+ /**
770
+ * The compiled pattern string for the pathname component.
771
+ */
772
+ get pathname(): string {
773
+ return this._components.pathname.pattern;
774
+ }
775
+
776
+ /**
777
+ * The compiled pattern string for the search component.
778
+ */
779
+ get search(): string {
780
+ return this._components.search.pattern;
781
+ }
782
+
783
+ /**
784
+ * The compiled pattern string for the hash component.
785
+ */
786
+ get hash(): string {
787
+ return this._components.hash.pattern;
788
+ }
789
+
790
+ /**
791
+ * Test whether a URL matches this pattern.
792
+ *
793
+ * @param input - A URL string or URLPatternInit to test
794
+ * @param baseURL - Optional base URL (only when input is a string)
795
+ * @returns true if the URL matches the pattern
796
+ */
797
+ test(input: URLPatternInput, baseURL?: string): boolean {
798
+ return this.exec(input, baseURL) !== null;
799
+ }
800
+
801
+ /**
802
+ * Execute this pattern against a URL, returning match details or null.
803
+ *
804
+ * @param input - A URL string or URLPatternInit to match
805
+ * @param baseURL - Optional base URL (only when input is a string)
806
+ * @returns URLPatternResult with match details, or null if no match
807
+ */
808
+ exec(input: URLPatternInput, baseURL?: string): URLPatternResult | null {
809
+ let urlComponents: Record<ComponentName, string>;
810
+
811
+ try {
812
+ urlComponents = parseURLInput(input, baseURL);
813
+ } catch {
814
+ return null;
815
+ }
816
+
817
+ const result: Partial<URLPatternResult> = {};
818
+
819
+ // Build inputs array
820
+ if (baseURL) {
821
+ (result as any).inputs = [input, baseURL];
822
+ } else {
823
+ (result as any).inputs = [input];
824
+ }
825
+
826
+ // Match each component
827
+ for (const comp of COMPONENTS) {
828
+ const compiled = this._components[comp];
829
+ const value = urlComponents[comp];
830
+ const match = compiled.regex.exec(value);
831
+
832
+ if (!match) {
833
+ return null;
834
+ }
835
+
836
+ // Build groups from captures
837
+ const groups: Record<string, string | undefined> = {};
838
+ for (let i = 0; i < compiled.groupNames.length; i++) {
839
+ groups[compiled.groupNames[i]] = match[i + 1];
840
+ }
841
+
842
+ (result as any)[comp] = {
843
+ input: value,
844
+ groups,
845
+ };
846
+ }
847
+
848
+ return result as URLPatternResult;
849
+ }
850
+ }