@ricsam/quickjs-core 0.2.12 → 0.2.14

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/dist/mjs/url.mjs CHANGED
@@ -1,22 +1,31 @@
1
1
  // @bun
2
2
  // packages/core/src/url.ts
3
3
  import { defineClass } from "./class-builder.mjs";
4
+ function rebuildURLHref(state) {
5
+ let userinfo = "";
6
+ if (state.username) {
7
+ userinfo = state.password ? `${state.username}:${state.password}@` : `${state.username}@`;
8
+ }
9
+ state.href = `${state.protocol}//${userinfo}${state.host}${state.pathname}${state.search}${state.hash}`;
10
+ state.origin = `${state.protocol}//${state.host}`;
11
+ }
4
12
  function createURLClass(context, stateMap) {
5
13
  return defineClass(context, stateMap, {
6
14
  name: "URL",
7
15
  construct: (args) => {
8
- const urlString = args[0];
16
+ const urlInput = args[0];
9
17
  const base = args[1];
10
- if (urlString === undefined) {
18
+ if (urlInput === undefined) {
11
19
  throw new TypeError("Failed to construct 'URL': 1 argument required, but only 0 present.");
12
20
  }
21
+ const urlString = typeof urlInput === "object" && urlInput !== null && "href" in urlInput ? urlInput.href : String(urlInput);
13
22
  let parsed;
14
23
  try {
15
24
  if (base !== undefined) {
16
25
  const baseStr = typeof base === "object" && base !== null && "href" in base ? base.href : String(base);
17
- parsed = new globalThis.URL(String(urlString), baseStr);
26
+ parsed = new globalThis.URL(urlString, baseStr);
18
27
  } else {
19
- parsed = new globalThis.URL(String(urlString));
28
+ parsed = new globalThis.URL(urlString);
20
29
  }
21
30
  } catch (e) {
22
31
  throw new TypeError(`Failed to construct 'URL': Invalid URL`);
@@ -39,21 +48,56 @@ function createURLClass(context, stateMap) {
39
48
  hash: {
40
49
  get() {
41
50
  return this.hash;
51
+ },
52
+ set(value) {
53
+ this.hash = value.startsWith("#") ? value : `#${value}`;
54
+ rebuildURLHref(this);
42
55
  }
43
56
  },
44
57
  host: {
45
58
  get() {
46
59
  return this.host;
60
+ },
61
+ set(value) {
62
+ const colonIndex = value.lastIndexOf(":");
63
+ if (colonIndex > -1 && !value.includes("]", colonIndex)) {
64
+ this.hostname = value.slice(0, colonIndex);
65
+ this.port = value.slice(colonIndex + 1);
66
+ } else {
67
+ this.hostname = value;
68
+ this.port = "";
69
+ }
70
+ this.host = value;
71
+ rebuildURLHref(this);
47
72
  }
48
73
  },
49
74
  hostname: {
50
75
  get() {
51
76
  return this.hostname;
77
+ },
78
+ set(value) {
79
+ this.hostname = value;
80
+ this.host = this.port ? `${value}:${this.port}` : value;
81
+ rebuildURLHref(this);
52
82
  }
53
83
  },
54
84
  href: {
55
85
  get() {
56
86
  return this.href;
87
+ },
88
+ set(value) {
89
+ const parsed = new globalThis.URL(value);
90
+ this.hash = parsed.hash;
91
+ this.host = parsed.host;
92
+ this.hostname = parsed.hostname;
93
+ this.href = parsed.href;
94
+ this.origin = parsed.origin;
95
+ this.password = parsed.password;
96
+ this.pathname = parsed.pathname;
97
+ this.port = parsed.port;
98
+ this.protocol = parsed.protocol;
99
+ this.search = parsed.search;
100
+ this.username = parsed.username;
57
101
  }
58
102
  },
59
103
  origin: {
@@ -64,31 +108,57 @@ function createURLClass(context, stateMap) {
64
108
  password: {
65
109
  get() {
66
110
  return this.password;
111
+ },
112
+ set(value) {
113
+ this.password = value;
114
+ rebuildURLHref(this);
67
115
  }
68
116
  },
69
117
  pathname: {
70
118
  get() {
71
119
  return this.pathname;
120
+ },
121
+ set(value) {
122
+ this.pathname = value.startsWith("/") ? value : `/${value}`;
123
+ rebuildURLHref(this);
72
124
  }
73
125
  },
74
126
  port: {
75
127
  get() {
76
128
  return this.port;
129
+ },
130
+ set(value) {
131
+ this.port = value;
132
+ this.host = value ? `${this.hostname}:${value}` : this.hostname;
133
+ rebuildURLHref(this);
77
134
  }
78
135
  },
79
136
  protocol: {
80
137
  get() {
81
138
  return this.protocol;
139
+ },
140
+ set(value) {
141
+ this.protocol = value.endsWith(":") ? value : `${value}:`;
142
+ this.origin = `${this.protocol}//${this.host}`;
143
+ rebuildURLHref(this);
82
144
  }
83
145
  },
84
146
  search: {
85
147
  get() {
86
148
  return this.search;
149
+ },
150
+ set(value) {
151
+ this.search = value && !value.startsWith("?") ? `?${value}` : value;
152
+ rebuildURLHref(this);
87
153
  }
88
154
  },
89
155
  username: {
90
156
  get() {
91
157
  return this.username;
158
+ },
159
+ set(value) {
160
+ this.username = value;
161
+ rebuildURLHref(this);
92
162
  }
93
163
  }
94
164
  },
@@ -98,9 +168,6 @@ function createURLClass(context, stateMap) {
98
168
  },
99
169
  toJSON() {
100
170
  return this.href;
101
- },
102
- __getSearch__() {
103
- return this.search;
104
171
  }
105
172
  },
106
173
  staticMethods: {
@@ -119,18 +186,42 @@ function createURLClass(context, stateMap) {
119
186
  }
120
187
  });
121
188
  }
122
- function addURLSearchParamsGetter(context) {
189
+ function addURLSearchParamsLinkage(context) {
123
190
  const result = context.evalCode(`
124
191
  (function() {
125
- const searchParamsCache = new Map();
126
-
127
192
  Object.defineProperty(URL.prototype, 'searchParams', {
128
193
  get: function() {
129
- const instanceId = this.__instanceId__;
130
- if (!searchParamsCache.has(instanceId)) {
131
- searchParamsCache.set(instanceId, new URLSearchParams(this.__getSearch__()));
194
+ // Cache the URLSearchParams instance on first access
195
+ if (!this._cachedSearchParams) {
196
+ const url = this;
197
+ const params = new URLSearchParams(this.search);
198
+
199
+ // Wrap mutating methods to sync back to URL
200
+ const originalSet = params.set.bind(params);
201
+ const originalAppend = params.append.bind(params);
202
+ const originalDelete = params.delete.bind(params);
203
+ const originalSort = params.sort.bind(params);
204
+
205
+ params.set = function(name, value) {
206
+ originalSet(name, value);
207
+ url.search = this.toString();
208
+ };
209
+ params.append = function(name, value) {
210
+ originalAppend(name, value);
211
+ url.search = this.toString();
212
+ };
213
+ params.delete = function(name, value) {
214
+ originalDelete(name, value);
215
+ url.search = this.toString();
216
+ };
217
+ params.sort = function() {
218
+ originalSort();
219
+ url.search = this.toString();
220
+ };
221
+
222
+ this._cachedSearchParams = params;
132
223
  }
133
- return searchParamsCache.get(instanceId);
224
+ return this._cachedSearchParams;
134
225
  },
135
226
  enumerable: true,
136
227
  configurable: true
@@ -140,13 +231,15 @@ function addURLSearchParamsGetter(context) {
140
231
  if (result.error) {
141
232
  const err = context.dump(result.error);
142
233
  result.error.dispose();
143
- throw new Error(`Failed to add searchParams getter: ${JSON.stringify(err)}`);
234
+ throw new Error(`Failed to setup URL/URLSearchParams linkage: ${JSON.stringify(err)}`);
144
235
  }
145
236
  result.value.dispose();
146
237
  }
238
+ var addURLSearchParamsGetter = addURLSearchParamsLinkage;
147
239
  export {
148
240
  createURLClass,
241
+ addURLSearchParamsLinkage,
149
242
  addURLSearchParamsGetter
150
243
  };
151
244
 
152
- //# debugId=8408820A4441CF3264756E2164756E21
245
+ //# debugId=3CB02D6BB183083C64756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../src/url.ts"],
4
4
  "sourcesContent": [
5
- "import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport type { StateMap } from \"./types.mjs\";\nimport { defineClass } from \"./class-builder.mjs\";\n\n/**\n * Internal state for URL\n */\nexport interface URLState {\n hash: string;\n host: string;\n hostname: string;\n href: string;\n origin: string;\n password: string;\n pathname: string;\n port: string;\n protocol: string;\n search: string;\n username: string;\n}\n\n/**\n * Create the URL class for QuickJS\n *\n * Note: The searchParams property is added separately via evalCode\n * after this class is registered, as it needs to return a URLSearchParams instance.\n */\nexport function createURLClass(\n context: QuickJSContext,\n stateMap: StateMap\n): QuickJSHandle {\n return defineClass<URLState>(context, stateMap, {\n name: \"URL\",\n construct: (args) => {\n const urlString = args[0];\n const base = args[1];\n\n if (urlString === undefined) {\n throw new TypeError(\"Failed to construct 'URL': 1 argument required, but only 0 present.\");\n }\n\n let parsed: globalThis.URL;\n try {\n if (base !== undefined) {\n // Handle base URL\n const baseStr = typeof base === \"object\" && base !== null && \"href\" in base\n ? (base as URLState).href\n : String(base);\n parsed = new globalThis.URL(String(urlString), baseStr);\n } else {\n parsed = new globalThis.URL(String(urlString));\n }\n } catch (e) {\n throw new TypeError(`Failed to construct 'URL': Invalid URL`);\n }\n\n return {\n hash: parsed.hash,\n host: parsed.host,\n hostname: parsed.hostname,\n href: parsed.href,\n origin: parsed.origin,\n password: parsed.password,\n pathname: parsed.pathname,\n port: parsed.port,\n protocol: parsed.protocol,\n search: parsed.search,\n username: parsed.username,\n };\n },\n properties: {\n hash: {\n get(this: URLState) {\n return this.hash;\n },\n },\n host: {\n get(this: URLState) {\n return this.host;\n },\n },\n hostname: {\n get(this: URLState) {\n return this.hostname;\n },\n },\n href: {\n get(this: URLState) {\n return this.href;\n },\n },\n origin: {\n get(this: URLState) {\n return this.origin;\n },\n },\n password: {\n get(this: URLState) {\n return this.password;\n },\n },\n pathname: {\n get(this: URLState) {\n return this.pathname;\n },\n },\n port: {\n get(this: URLState) {\n return this.port;\n },\n },\n protocol: {\n get(this: URLState) {\n return this.protocol;\n },\n },\n search: {\n get(this: URLState) {\n return this.search;\n },\n },\n username: {\n get(this: URLState) {\n return this.username;\n },\n },\n },\n methods: {\n toString(this: URLState): string {\n return this.href;\n },\n toJSON(this: URLState): string {\n return this.href;\n },\n // Private method to get search for URLSearchParams creation\n __getSearch__(this: URLState): string {\n return this.search;\n },\n },\n staticMethods: {\n canParse(url: unknown, base?: unknown): boolean {\n try {\n if (base !== undefined) {\n new globalThis.URL(String(url), String(base));\n } else {\n new globalThis.URL(String(url));\n }\n return true;\n } catch {\n return false;\n }\n },\n },\n });\n}\n\n/**\n * Add searchParams getter to URL prototype using evalCode\n * This must be called after both URL and URLSearchParams are registered as globals\n */\nexport function addURLSearchParamsGetter(context: QuickJSContext): void {\n // Use a WeakMap-like pattern with a Map keyed by instance ID\n // Each URL instance will cache its URLSearchParams\n const result = context.evalCode(`\n (function() {\n const searchParamsCache = new Map();\n\n Object.defineProperty(URL.prototype, 'searchParams', {\n get: function() {\n const instanceId = this.__instanceId__;\n if (!searchParamsCache.has(instanceId)) {\n searchParamsCache.set(instanceId, new URLSearchParams(this.__getSearch__()));\n }\n return searchParamsCache.get(instanceId);\n },\n enumerable: true,\n configurable: true\n });\n })();\n `);\n\n if (result.error) {\n const err = context.dump(result.error);\n result.error.dispose();\n throw new Error(`Failed to add searchParams getter: ${JSON.stringify(err)}`);\n }\n result.value.dispose();\n}\n"
5
+ "import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport type { StateMap } from \"./types.mjs\";\nimport { defineClass } from \"./class-builder.mjs\";\n\n/**\n * Internal state for URL\n */\nexport interface URLState {\n hash: string;\n host: string;\n hostname: string;\n href: string;\n origin: string;\n password: string;\n pathname: string;\n port: string;\n protocol: string;\n search: string;\n username: string;\n}\n\n/**\n * Helper to rebuild the href from URL components\n */\nfunction rebuildURLHref(state: URLState): void {\n // Build userinfo part\n let userinfo = \"\";\n if (state.username) {\n userinfo = state.password\n ? `${state.username}:${state.password}@`\n : `${state.username}@`;\n }\n\n // Build the href\n state.href = `${state.protocol}//${userinfo}${state.host}${state.pathname}${state.search}${state.hash}`;\n\n // Update origin\n state.origin = `${state.protocol}//${state.host}`;\n}\n\n/**\n * Create the URL class for QuickJS\n *\n * Note: The searchParams property is added separately via evalCode\n * after this class is registered, as it needs to return a URLSearchParams instance.\n */\nexport function createURLClass(\n context: QuickJSContext,\n stateMap: StateMap\n): QuickJSHandle {\n return defineClass<URLState>(context, stateMap, {\n name: \"URL\",\n construct: (args) => {\n const urlInput = args[0];\n const base = args[1];\n\n if (urlInput === undefined) {\n throw new TypeError(\"Failed to construct 'URL': 1 argument required, but only 0 present.\");\n }\n\n // Handle URL object as first argument (extract href)\n const urlString = typeof urlInput === \"object\" && urlInput !== null && \"href\" in urlInput\n ? (urlInput as URLState).href\n : String(urlInput);\n\n let parsed: globalThis.URL;\n try {\n if (base !== undefined) {\n // Handle base URL\n const baseStr = typeof base === \"object\" && base !== null && \"href\" in base\n ? (base as URLState).href\n : String(base);\n parsed = new globalThis.URL(urlString, baseStr);\n } else {\n parsed = new globalThis.URL(urlString);\n }\n } catch (e) {\n throw new TypeError(`Failed to construct 'URL': Invalid URL`);\n }\n\n return {\n hash: parsed.hash,\n host: parsed.host,\n hostname: parsed.hostname,\n href: parsed.href,\n origin: parsed.origin,\n password: parsed.password,\n pathname: parsed.pathname,\n port: parsed.port,\n protocol: parsed.protocol,\n search: parsed.search,\n username: parsed.username,\n };\n },\n properties: {\n hash: {\n get(this: URLState) {\n return this.hash;\n },\n set(this: URLState, value: string) {\n this.hash = value.startsWith(\"#\") ? value : `#${value}`;\n rebuildURLHref(this);\n },\n },\n host: {\n get(this: URLState) {\n return this.host;\n },\n set(this: URLState, value: string) {\n // Parse host into hostname and port\n const colonIndex = value.lastIndexOf(\":\");\n if (colonIndex > -1 && !value.includes(\"]\", colonIndex)) {\n this.hostname = value.slice(0, colonIndex);\n this.port = value.slice(colonIndex + 1);\n } else {\n this.hostname = value;\n this.port = \"\";\n }\n this.host = value;\n rebuildURLHref(this);\n },\n },\n hostname: {\n get(this: URLState) {\n return this.hostname;\n },\n set(this: URLState, value: string) {\n this.hostname = value;\n this.host = this.port ? `${value}:${this.port}` : value;\n rebuildURLHref(this);\n },\n },\n href: {\n get(this: URLState) {\n return this.href;\n },\n set(this: URLState, value: string) {\n // Parse the new href and update all properties\n const parsed = new globalThis.URL(value);\n this.hash = parsed.hash;\n this.host = parsed.host;\n this.hostname = parsed.hostname;\n this.href = parsed.href;\n this.origin = parsed.origin;\n this.password = parsed.password;\n this.pathname = parsed.pathname;\n this.port = parsed.port;\n this.protocol = parsed.protocol;\n this.search = parsed.search;\n this.username = parsed.username;\n },\n },\n origin: {\n get(this: URLState) {\n return this.origin;\n },\n // origin is read-only per spec\n },\n password: {\n get(this: URLState) {\n return this.password;\n },\n set(this: URLState, value: string) {\n this.password = value;\n rebuildURLHref(this);\n },\n },\n pathname: {\n get(this: URLState) {\n return this.pathname;\n },\n set(this: URLState, value: string) {\n this.pathname = value.startsWith(\"/\") ? value : `/${value}`;\n rebuildURLHref(this);\n },\n },\n port: {\n get(this: URLState) {\n return this.port;\n },\n set(this: URLState, value: string) {\n this.port = value;\n this.host = value ? `${this.hostname}:${value}` : this.hostname;\n rebuildURLHref(this);\n },\n },\n protocol: {\n get(this: URLState) {\n return this.protocol;\n },\n set(this: URLState, value: string) {\n this.protocol = value.endsWith(\":\") ? value : `${value}:`;\n // Update origin\n this.origin = `${this.protocol}//${this.host}`;\n rebuildURLHref(this);\n },\n },\n search: {\n get(this: URLState) {\n return this.search;\n },\n set(this: URLState, value: string) {\n this.search = value && !value.startsWith(\"?\") ? `?${value}` : value;\n rebuildURLHref(this);\n },\n },\n username: {\n get(this: URLState) {\n return this.username;\n },\n set(this: URLState, value: string) {\n this.username = value;\n rebuildURLHref(this);\n },\n },\n },\n methods: {\n toString(this: URLState): string {\n return this.href;\n },\n toJSON(this: URLState): string {\n return this.href;\n },\n },\n staticMethods: {\n canParse(url: unknown, base?: unknown): boolean {\n try {\n if (base !== undefined) {\n new globalThis.URL(String(url), String(base));\n } else {\n new globalThis.URL(String(url));\n }\n return true;\n } catch {\n return false;\n }\n },\n },\n });\n}\n\n/**\n * Add searchParams getter to URL prototype using evalCode\n * This must be called after both URL and URLSearchParams are registered as globals\n *\n * The getter creates a URLSearchParams instance on first access and wraps its\n * mutating methods (set, append, delete, sort) to sync changes back to the URL.\n * This eliminates cross-module coupling by keeping all sync logic in JavaScript.\n */\nexport function addURLSearchParamsLinkage(context: QuickJSContext): void {\n const result = context.evalCode(`\n (function() {\n Object.defineProperty(URL.prototype, 'searchParams', {\n get: function() {\n // Cache the URLSearchParams instance on first access\n if (!this._cachedSearchParams) {\n const url = this;\n const params = new URLSearchParams(this.search);\n\n // Wrap mutating methods to sync back to URL\n const originalSet = params.set.bind(params);\n const originalAppend = params.append.bind(params);\n const originalDelete = params.delete.bind(params);\n const originalSort = params.sort.bind(params);\n\n params.set = function(name, value) {\n originalSet(name, value);\n url.search = this.toString();\n };\n params.append = function(name, value) {\n originalAppend(name, value);\n url.search = this.toString();\n };\n params.delete = function(name, value) {\n originalDelete(name, value);\n url.search = this.toString();\n };\n params.sort = function() {\n originalSort();\n url.search = this.toString();\n };\n\n this._cachedSearchParams = params;\n }\n return this._cachedSearchParams;\n },\n enumerable: true,\n configurable: true\n });\n })();\n `);\n\n if (result.error) {\n const err = context.dump(result.error);\n result.error.dispose();\n throw new Error(`Failed to setup URL/URLSearchParams linkage: ${JSON.stringify(err)}`);\n }\n result.value.dispose();\n}\n\n/**\n * @deprecated Use addURLSearchParamsLinkage instead\n */\nexport const addURLSearchParamsGetter = addURLSearchParamsLinkage;\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "8408820A4441CF3264756E2164756E21",
7
+ "mappings": ";;AAEA;AAsBA,SAAS,cAAc,CAAC,OAAuB;AAAA,EAE7C,IAAI,WAAW;AAAA,EACf,IAAI,MAAM,UAAU;AAAA,IAClB,WAAW,MAAM,WACb,GAAG,MAAM,YAAY,MAAM,cAC3B,GAAG,MAAM;AAAA,EACf;AAAA,EAGA,MAAM,OAAO,GAAG,MAAM,aAAa,WAAW,MAAM,OAAO,MAAM,WAAW,MAAM,SAAS,MAAM;AAAA,EAGjG,MAAM,SAAS,GAAG,MAAM,aAAa,MAAM;AAAA;AAStC,SAAS,cAAc,CAC5B,SACA,UACe;AAAA,EACf,OAAO,YAAsB,SAAS,UAAU;AAAA,IAC9C,MAAM;AAAA,IACN,WAAW,CAAC,SAAS;AAAA,MACnB,MAAM,WAAW,KAAK;AAAA,MACtB,MAAM,OAAO,KAAK;AAAA,MAElB,IAAI,aAAa,WAAW;AAAA,QAC1B,MAAM,IAAI,UAAU,qEAAqE;AAAA,MAC3F;AAAA,MAGA,MAAM,YAAY,OAAO,aAAa,YAAY,aAAa,QAAQ,UAAU,WAC5E,SAAsB,OACvB,OAAO,QAAQ;AAAA,MAEnB,IAAI;AAAA,MACJ,IAAI;AAAA,QACF,IAAI,SAAS,WAAW;AAAA,UAEtB,MAAM,UAAU,OAAO,SAAS,YAAY,SAAS,QAAQ,UAAU,OAClE,KAAkB,OACnB,OAAO,IAAI;AAAA,UACf,SAAS,IAAI,WAAW,IAAI,WAAW,OAAO;AAAA,QAChD,EAAO;AAAA,UACL,SAAS,IAAI,WAAW,IAAI,SAAS;AAAA;AAAA,QAEvC,OAAO,GAAG;AAAA,QACV,MAAM,IAAI,UAAU,wCAAwC;AAAA;AAAA,MAG9D,OAAO;AAAA,QACL,MAAM,OAAO;AAAA,QACb,MAAM,OAAO;AAAA,QACb,UAAU,OAAO;AAAA,QACjB,MAAM,OAAO;AAAA,QACb,QAAQ,OAAO;AAAA,QACf,UAAU,OAAO;AAAA,QACjB,UAAU,OAAO;AAAA,QACjB,MAAM,OAAO;AAAA,QACb,UAAU,OAAO;AAAA,QACjB,QAAQ,OAAO;AAAA,QACf,UAAU,OAAO;AAAA,MACnB;AAAA;AAAA,IAEF,YAAY;AAAA,MACV,MAAM;AAAA,QACJ,GAAG,GAAiB;AAAA,UAClB,OAAO,KAAK;AAAA;AAAA,QAEd,GAAG,CAAiB,OAAe;AAAA,UACjC,KAAK,OAAO,MAAM,WAAW,GAAG,IAAI,QAAQ,IAAI;AAAA,UAChD,eAAe,IAAI;AAAA;AAAA,MAEvB;AAAA,MACA,MAAM;AAAA,QACJ,GAAG,GAAiB;AAAA,UAClB,OAAO,KAAK;AAAA;AAAA,QAEd,GAAG,CAAiB,OAAe;AAAA,UAEjC,MAAM,aAAa,MAAM,YAAY,GAAG;AAAA,UACxC,IAAI,aAAa,MAAM,CAAC,MAAM,SAAS,KAAK,UAAU,GAAG;AAAA,YACvD,KAAK,WAAW,MAAM,MAAM,GAAG,UAAU;AAAA,YACzC,KAAK,OAAO,MAAM,MAAM,aAAa,CAAC;AAAA,UACxC,EAAO;AAAA,YACL,KAAK,WAAW;AAAA,YAChB,KAAK,OAAO;AAAA;AAAA,UAEd,KAAK,OAAO;AAAA,UACZ,eAAe,IAAI;AAAA;AAAA,MAEvB;AAAA,MACA,UAAU;AAAA,QACR,GAAG,GAAiB;AAAA,UAClB,OAAO,KAAK;AAAA;AAAA,QAEd,GAAG,CAAiB,OAAe;AAAA,UACjC,KAAK,WAAW;AAAA,UAChB,KAAK,OAAO,KAAK,OAAO,GAAG,SAAS,KAAK,SAAS;AAAA,UAClD,eAAe,IAAI;AAAA;AAAA,MAEvB;AAAA,MACA,MAAM;AAAA,QACJ,GAAG,GAAiB;AAAA,UAClB,OAAO,KAAK;AAAA;AAAA,QAEd,GAAG,CAAiB,OAAe;AAAA,UAEjC,MAAM,SAAS,IAAI,WAAW,IAAI,KAAK;AAAA,UACvC,KAAK,OAAO,OAAO;AAAA,UACnB,KAAK,OAAO,OAAO;AAAA,UACnB,KAAK,WAAW,OAAO;AAAA,UACvB,KAAK,OAAO,OAAO;AAAA,UACnB,KAAK,SAAS,OAAO;AAAA,UACrB,KAAK,WAAW,OAAO;AAAA,UACvB,KAAK,WAAW,OAAO;AAAA,UACvB,KAAK,OAAO,OAAO;AAAA,UACnB,KAAK,WAAW,OAAO;AAAA,UACvB,KAAK,SAAS,OAAO;AAAA,UACrB,KAAK,WAAW,OAAO;AAAA;AAAA,MAE3B;AAAA,MACA,QAAQ;AAAA,QACN,GAAG,GAAiB;AAAA,UAClB,OAAO,KAAK;AAAA;AAAA,MAGhB;AAAA,MACA,UAAU;AAAA,QACR,GAAG,GAAiB;AAAA,UAClB,OAAO,KAAK;AAAA;AAAA,QAEd,GAAG,CAAiB,OAAe;AAAA,UACjC,KAAK,WAAW;AAAA,UAChB,eAAe,IAAI;AAAA;AAAA,MAEvB;AAAA,MACA,UAAU;AAAA,QACR,GAAG,GAAiB;AAAA,UAClB,OAAO,KAAK;AAAA;AAAA,QAEd,GAAG,CAAiB,OAAe;AAAA,UACjC,KAAK,WAAW,MAAM,WAAW,GAAG,IAAI,QAAQ,IAAI;AAAA,UACpD,eAAe,IAAI;AAAA;AAAA,MAEvB;AAAA,MACA,MAAM;AAAA,QACJ,GAAG,GAAiB;AAAA,UAClB,OAAO,KAAK;AAAA;AAAA,QAEd,GAAG,CAAiB,OAAe;AAAA,UACjC,KAAK,OAAO;AAAA,UACZ,KAAK,OAAO,QAAQ,GAAG,KAAK,YAAY,UAAU,KAAK;AAAA,UACvD,eAAe,IAAI;AAAA;AAAA,MAEvB;AAAA,MACA,UAAU;AAAA,QACR,GAAG,GAAiB;AAAA,UAClB,OAAO,KAAK;AAAA;AAAA,QAEd,GAAG,CAAiB,OAAe;AAAA,UACjC,KAAK,WAAW,MAAM,SAAS,GAAG,IAAI,QAAQ,GAAG;AAAA,UAEjD,KAAK,SAAS,GAAG,KAAK,aAAa,KAAK;AAAA,UACxC,eAAe,IAAI;AAAA;AAAA,MAEvB;AAAA,MACA,QAAQ;AAAA,QACN,GAAG,GAAiB;AAAA,UAClB,OAAO,KAAK;AAAA;AAAA,QAEd,GAAG,CAAiB,OAAe;AAAA,UACjC,KAAK,SAAS,SAAS,CAAC,MAAM,WAAW,GAAG,IAAI,IAAI,UAAU;AAAA,UAC9D,eAAe,IAAI;AAAA;AAAA,MAEvB;AAAA,MACA,UAAU;AAAA,QACR,GAAG,GAAiB;AAAA,UAClB,OAAO,KAAK;AAAA;AAAA,QAEd,GAAG,CAAiB,OAAe;AAAA,UACjC,KAAK,WAAW;AAAA,UAChB,eAAe,IAAI;AAAA;AAAA,MAEvB;AAAA,IACF;AAAA,IACA,SAAS;AAAA,MACP,QAAQ,GAAyB;AAAA,QAC/B,OAAO,KAAK;AAAA;AAAA,MAEd,MAAM,GAAyB;AAAA,QAC7B,OAAO,KAAK;AAAA;AAAA,IAEhB;AAAA,IACA,eAAe;AAAA,MACb,QAAQ,CAAC,KAAc,MAAyB;AAAA,QAC9C,IAAI;AAAA,UACF,IAAI,SAAS,WAAW;AAAA,YACtB,IAAI,WAAW,IAAI,OAAO,GAAG,GAAG,OAAO,IAAI,CAAC;AAAA,UAC9C,EAAO;AAAA,YACL,IAAI,WAAW,IAAI,OAAO,GAAG,CAAC;AAAA;AAAA,UAEhC,OAAO;AAAA,UACP,MAAM;AAAA,UACN,OAAO;AAAA;AAAA;AAAA,IAGb;AAAA,EACF,CAAC;AAAA;AAWI,SAAS,yBAAyB,CAAC,SAA+B;AAAA,EACvE,MAAM,SAAS,QAAQ,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAwC/B;AAAA,EAED,IAAI,OAAO,OAAO;AAAA,IAChB,MAAM,MAAM,QAAQ,KAAK,OAAO,KAAK;AAAA,IACrC,OAAO,MAAM,QAAQ;AAAA,IACrB,MAAM,IAAI,MAAM,gDAAgD,KAAK,UAAU,GAAG,GAAG;AAAA,EACvF;AAAA,EACA,OAAO,MAAM,QAAQ;AAAA;AAMhB,IAAM,2BAA2B;",
8
+ "debugId": "3CB02D6BB183083C64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -43,3 +43,35 @@ export declare function getInstanceId(value: unknown): number | undefined;
43
43
  * Get the class name from an unmarshalled value if it's a defineClass instance
44
44
  */
45
45
  export declare function getClassName(value: unknown): string | undefined;
46
+ /**
47
+ * Interface for typed class instance with specific class name
48
+ */
49
+ export interface TypedClassInstance<T extends string> extends DefineClassInstance {
50
+ __className__: T;
51
+ }
52
+ /**
53
+ * Create a type guard for a specific class name
54
+ *
55
+ * @example
56
+ * const isRequest = createClassTypeGuard<"Request">("Request");
57
+ * if (isRequest(value)) {
58
+ * // value is TypedClassInstance<"Request">
59
+ * }
60
+ */
61
+ export declare function createClassTypeGuard<T extends string>(className: T): (value: unknown) => value is TypedClassInstance<T>;
62
+ /**
63
+ * Pre-built type guard for Request instances
64
+ */
65
+ export declare const isUnmarshalledRequest: (value: unknown) => value is TypedClassInstance<"Request">;
66
+ /**
67
+ * Pre-built type guard for Response instances
68
+ */
69
+ export declare const isUnmarshalledResponse: (value: unknown) => value is TypedClassInstance<"Response">;
70
+ /**
71
+ * Pre-built type guard for Headers instances
72
+ */
73
+ export declare const isUnmarshalledHeaders: (value: unknown) => value is TypedClassInstance<"Headers">;
74
+ /**
75
+ * Pre-built type guard for FormData instances
76
+ */
77
+ export declare const isUnmarshalledFormData: (value: unknown) => value is TypedClassInstance<"FormData">;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Coercion system for QuickJS class instances
3
+ *
4
+ * Provides a Zod-like API for detecting and extracting values from
5
+ * class instances, plain objects, and primitive values.
6
+ *
7
+ * This enables consistent handling of class instances across the codebase,
8
+ * reducing duplication and ensuring reliable type detection.
9
+ */
10
+ /**
11
+ * Result of a coercion attempt
12
+ */
13
+ export type CoercionResult<T> = {
14
+ success: true;
15
+ value: T;
16
+ } | {
17
+ success: false;
18
+ error: string;
19
+ };
20
+ /**
21
+ * A coercer that can transform unknown values into a specific type
22
+ */
23
+ export interface Coercer<T> {
24
+ /** The name of this coercer (for error messages) */
25
+ readonly name: string;
26
+ /** Attempt to coerce a value, returning a result object */
27
+ safeParse(value: unknown): CoercionResult<T>;
28
+ /** Coerce a value, throwing on failure */
29
+ parse(value: unknown): T;
30
+ /** Check if a value can be coerced without actually coercing */
31
+ is(value: unknown): boolean;
32
+ /** Create a new coercer with a fallback */
33
+ or<U>(other: Coercer<U>): Coercer<T | U>;
34
+ /** Transform the coerced value */
35
+ transform<U>(fn: (value: T) => U): Coercer<U>;
36
+ /** Make the coercer optional (allows undefined/null) */
37
+ optional(): Coercer<T | undefined>;
38
+ }
39
+ /**
40
+ * Create a base coercer from detection and extraction functions
41
+ */
42
+ export declare function createCoercer<T>(name: string, detect: (value: unknown) => boolean, extract: (value: unknown) => T): Coercer<T>;
43
+ /**
44
+ * Create a coercer for a QuickJS class instance
45
+ */
46
+ export declare function classCoercer<TState>(className: string): Coercer<TState>;
47
+ /**
48
+ * Coerce a value that could be a class instance OR have a specific shape
49
+ */
50
+ export declare function instanceOrShape<TState, TShape>(className: string, shapeCheck: (value: unknown) => value is TShape, shapeToState: (value: TShape) => TState): Coercer<TState>;
51
+ export interface URLCoerced {
52
+ href: string;
53
+ protocol: string;
54
+ host: string;
55
+ hostname: string;
56
+ port: string;
57
+ pathname: string;
58
+ search: string;
59
+ hash: string;
60
+ origin: string;
61
+ username: string;
62
+ password: string;
63
+ }
64
+ /** Coerce a value to URL data */
65
+ export declare const coerceURL: Coercer<URLCoerced>;
66
+ /** Get just the href string from a URL-like value */
67
+ export declare function coerceToURLString(value: unknown): string;
68
+ export interface HeadersCoerced {
69
+ headers: Map<string, string[]>;
70
+ }
71
+ /** Coerce a value to Headers data */
72
+ export declare const coerceHeaders: Coercer<HeadersCoerced>;
73
+ /** Coerce body to Uint8Array */
74
+ export declare function coerceBody(value: unknown): Uint8Array | null;
75
+ export interface RequestInitCoerced {
76
+ method?: string;
77
+ headersState?: HeadersCoerced;
78
+ body?: Uint8Array | null;
79
+ cache?: string;
80
+ credentials?: string;
81
+ destination?: string;
82
+ integrity?: string;
83
+ keepalive?: boolean;
84
+ mode?: string;
85
+ redirect?: string;
86
+ referrer?: string;
87
+ referrerPolicy?: string;
88
+ signal?: unknown;
89
+ }
90
+ /** Coerce a value to Request init data (for use as RequestInit second arg) */
91
+ export declare const coerceRequestInit: Coercer<RequestInitCoerced>;
92
+ export interface ResponseInitCoerced {
93
+ status?: number;
94
+ statusText?: string;
95
+ headersState?: HeadersCoerced;
96
+ }
97
+ /** Coerce a value to Response init data */
98
+ export declare const coerceResponseInit: Coercer<ResponseInitCoerced>;
@@ -36,8 +36,8 @@ export { marshal, isHandle, getHandleType } from "./marshal.ts";
36
36
  export { unmarshal, cleanupUnmarshaledHandles } from "./unmarshal.ts";
37
37
  export { defineFunction, defineAsyncFunction } from "./function-builder.ts";
38
38
  export { defineClass, createStateMap, getState, setState, getInstanceState, setInstanceState, getInstanceStateById, cleanupInstanceState, clearAllInstanceState, } from "./class-builder.ts";
39
- export { isDefineClassInstance, isInstanceOf, getClassInstanceState, getInstanceId, getClassName, } from "./class-helpers.ts";
40
- export type { DefineClassInstance } from "./class-helpers.ts";
39
+ export { isDefineClassInstance, isInstanceOf, getClassInstanceState, getInstanceId, getClassName, createClassTypeGuard, isUnmarshalledRequest, isUnmarshalledResponse, isUnmarshalledHeaders, isUnmarshalledFormData, } from "./class-helpers.ts";
40
+ export type { DefineClassInstance, TypedClassInstance } from "./class-helpers.ts";
41
41
  export { nextInstanceId, registerInstance, getInstanceMetadata, getInstanceClassName, cleanupInstanceState as cleanupInstanceStateById, } from "./instance-state.ts";
42
42
  export type { InstanceMetadata } from "./instance-state.ts";
43
43
  export { createReadableStream, consumeReadableStream, } from "./streams/readable-stream.ts";
@@ -46,5 +46,7 @@ export { createBlob } from "./blob.ts";
46
46
  export { createFile } from "./file.ts";
47
47
  export { createURLSearchParamsClass } from "./url-search-params.ts";
48
48
  export type { URLSearchParamsState } from "./url-search-params.ts";
49
- export { createURLClass, addURLSearchParamsGetter } from "./url.ts";
49
+ export { createURLClass, addURLSearchParamsLinkage, addURLSearchParamsGetter } from "./url.ts";
50
50
  export type { URLState } from "./url.ts";
51
+ export { createCoercer, classCoercer, instanceOrShape, coerceURL, coerceToURLString, coerceHeaders, coerceBody, coerceRequestInit, coerceResponseInit, } from "./coerce.ts";
52
+ export type { Coercer, CoercionResult, URLCoerced, HeadersCoerced, RequestInitCoerced, ResponseInitCoerced, } from "./coerce.ts";
@@ -26,5 +26,13 @@ export declare function createURLClass(context: QuickJSContext, stateMap: StateM
26
26
  /**
27
27
  * Add searchParams getter to URL prototype using evalCode
28
28
  * This must be called after both URL and URLSearchParams are registered as globals
29
+ *
30
+ * The getter creates a URLSearchParams instance on first access and wraps its
31
+ * mutating methods (set, append, delete, sort) to sync changes back to the URL.
32
+ * This eliminates cross-module coupling by keeping all sync logic in JavaScript.
33
+ */
34
+ export declare function addURLSearchParamsLinkage(context: QuickJSContext): void;
35
+ /**
36
+ * @deprecated Use addURLSearchParamsLinkage instead
29
37
  */
30
- export declare function addURLSearchParamsGetter(context: QuickJSContext): void;
38
+ export declare const addURLSearchParamsGetter: typeof addURLSearchParamsLinkage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ricsam/quickjs-core",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "main": "./dist/cjs/index.cjs",
5
5
  "types": "./dist/types/index.d.ts",
6
6
  "exports": {