@scalar/json-magic 0.5.2 → 0.6.1

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.
@@ -1,10 +1,10 @@
1
1
 
2
- > @scalar/json-magic@0.5.2 build /home/runner/work/scalar/scalar/packages/json-magic
2
+ > @scalar/json-magic@0.6.1 build /home/runner/work/scalar/scalar/packages/json-magic
3
3
  > scalar-build-esbuild
4
4
 
5
- @scalar/json-magic: Build completed in 29.84ms
5
+ @scalar/json-magic: Build completed in 25.49ms
6
6
 
7
- > @scalar/json-magic@0.5.2 types:build /home/runner/work/scalar/scalar/packages/json-magic
7
+ > @scalar/json-magic@0.6.1 types:build /home/runner/work/scalar/scalar/packages/json-magic
8
8
  > scalar-types-build
9
9
 
10
- Types build completed in 1.58s
10
+ Types build completed in 1.86s
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @scalar/json-magic
2
2
 
3
+ ## 0.6.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 2089748: chore: add logs when fetching unsupported formats
8
+ - 8a7fb2a: fix: schema properties starting with an underscore are hidden
9
+ - Updated dependencies [3f6d0b9]
10
+ - @scalar/helpers@0.0.12
11
+
12
+ ## 0.6.0
13
+
14
+ ### Minor Changes
15
+
16
+ - 4951456: feat: merge yaml aliases for in-mem representation
17
+
3
18
  ## 0.5.2
4
19
 
5
20
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/bundle/plugins/fetch-urls/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAK3D,KAAK,WAAW,GAAG,OAAO,CAAC;IACzB,OAAO,EAAE;QAAE,OAAO,EAAE,WAAW,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAA;IACtD,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAC3F,CAAC,CAAA;AAcF;;;;;;;;;;;;;GAaG;AACH,wBAAsB,QAAQ,CAC5B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EAChD,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAgCxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,GAAG,YAAY,CAShG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/bundle/plugins/fetch-urls/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAK3D,KAAK,WAAW,GAAG,OAAO,CAAC;IACzB,OAAO,EAAE;QAAE,OAAO,EAAE,WAAW,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAA;IACtD,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAC3F,CAAC,CAAA;AAcF;;;;;;;;;;;;;GAaG;AACH,wBAAsB,QAAQ,CAC5B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EAChD,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAyCxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,GAAG,YAAY,CAShG"}
@@ -25,10 +25,16 @@ async function fetchUrl(url, limiter, config) {
25
25
  data: normalize(body)
26
26
  };
27
27
  }
28
+ const contentType = result.headers.get("Content-Type") ?? "";
29
+ if (["text/html", "application/xml"].includes(contentType)) {
30
+ console.warn(`[WARN] We only support JSON/YAML formats, received ${contentType}`);
31
+ }
32
+ console.warn(`[WARN] Fetch failed with status ${result.status} ${result.statusText} for URL: ${url}`);
28
33
  return {
29
34
  ok: false
30
35
  };
31
36
  } catch {
37
+ console.warn(`[WARN] Failed to parse JSON/YAML from URL: ${url}`);
32
38
  return {
33
39
  ok: false
34
40
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/bundle/plugins/fetch-urls/index.ts"],
4
- "sourcesContent": ["import type { LoaderPlugin, ResolveResult } from '@/bundle'\nimport { isRemoteUrl } from '@/bundle/bundle'\nimport { createLimiter } from '@/bundle/create-limiter'\nimport { normalize } from '@/helpers/normalize'\n\ntype FetchConfig = Partial<{\n headers: { headers: HeadersInit; domains: string[] }[]\n fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>\n}>\n\n/**\n * Safely checks for host from a URL\n * Needed because we cannot create a URL from a relative remote URL ex: examples/openapi.json\n */\nconst getHost = (url: string): string | null => {\n try {\n return new URL(url).host\n } catch {\n return null\n }\n}\n\n/**\n * Fetches and normalizes data from a remote URL\n * @param url - The URL to fetch data from\n * @returns A promise that resolves to either the normalized data or an error result\n * @example\n * ```ts\n * const result = await fetchUrl('https://api.example.com/data.json')\n * if (result.ok) {\n * console.log(result.data) // The normalized data\n * } else {\n * console.log('Failed to fetch data')\n * }\n * ```\n */\nexport async function fetchUrl(\n url: string,\n limiter: <T>(fn: () => Promise<T>) => Promise<T>,\n config?: FetchConfig,\n): Promise<ResolveResult> {\n try {\n const host = getHost(url)\n\n // Get the headers that match the domain\n const headers = config?.headers?.find((a) => a.domains.find((d) => d === host) !== undefined)?.headers\n\n const exec = config?.fetch ?? fetch\n\n const result = await limiter(() =>\n exec(url, {\n headers,\n }),\n )\n\n if (result.ok) {\n const body = await result.text()\n\n return {\n ok: true,\n data: normalize(body),\n }\n }\n\n return {\n ok: false,\n }\n } catch {\n return {\n ok: false,\n }\n }\n}\n\n/**\n * Creates a plugin for handling remote URL references.\n * This plugin validates and fetches data from HTTP/HTTPS URLs.\n *\n * @returns A plugin object with validate and exec functions\n * @example\n * const urlPlugin = fetchUrls()\n * if (urlPlugin.validate('https://example.com/schema.json')) {\n * const result = await urlPlugin.exec('https://example.com/schema.json')\n * }\n */\nexport function fetchUrls(config?: FetchConfig & Partial<{ limit: number | null }>): LoaderPlugin {\n // If there is a limit specified we limit the number of concurrent calls\n const limiter = config?.limit ? createLimiter(config.limit) : <T>(fn: () => Promise<T>) => fn()\n\n return {\n type: 'loader',\n validate: isRemoteUrl,\n exec: (value) => fetchUrl(value, limiter, config),\n }\n}\n"],
5
- "mappings": "AACA,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAC9B,SAAS,iBAAiB;AAW1B,MAAM,UAAU,CAAC,QAA+B;AAC9C,MAAI;AACF,WAAO,IAAI,IAAI,GAAG,EAAE;AAAA,EACtB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAgBA,eAAsB,SACpB,KACA,SACA,QACwB;AACxB,MAAI;AACF,UAAM,OAAO,QAAQ,GAAG;AAGxB,UAAM,UAAU,QAAQ,SAAS,KAAK,CAAC,MAAM,EAAE,QAAQ,KAAK,CAAC,MAAM,MAAM,IAAI,MAAM,MAAS,GAAG;AAE/F,UAAM,OAAO,QAAQ,SAAS;AAE9B,UAAM,SAAS,MAAM;AAAA,MAAQ,MAC3B,KAAK,KAAK;AAAA,QACR;AAAA,MACF,CAAC;AAAA,IACH;AAEA,QAAI,OAAO,IAAI;AACb,YAAM,OAAO,MAAM,OAAO,KAAK;AAE/B,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,MAAM,UAAU,IAAI;AAAA,MACtB;AAAA,IACF;AAEA,WAAO;AAAA,MACL,IAAI;AAAA,IACN;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,MACL,IAAI;AAAA,IACN;AAAA,EACF;AACF;AAaO,SAAS,UAAU,QAAwE;AAEhG,QAAM,UAAU,QAAQ,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAI,OAAyB,GAAG;AAE9F,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,IACV,MAAM,CAAC,UAAU,SAAS,OAAO,SAAS,MAAM;AAAA,EAClD;AACF;",
4
+ "sourcesContent": ["import type { LoaderPlugin, ResolveResult } from '@/bundle'\nimport { isRemoteUrl } from '@/bundle/bundle'\nimport { createLimiter } from '@/bundle/create-limiter'\nimport { normalize } from '@/helpers/normalize'\n\ntype FetchConfig = Partial<{\n headers: { headers: HeadersInit; domains: string[] }[]\n fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>\n}>\n\n/**\n * Safely checks for host from a URL\n * Needed because we cannot create a URL from a relative remote URL ex: examples/openapi.json\n */\nconst getHost = (url: string): string | null => {\n try {\n return new URL(url).host\n } catch {\n return null\n }\n}\n\n/**\n * Fetches and normalizes data from a remote URL\n * @param url - The URL to fetch data from\n * @returns A promise that resolves to either the normalized data or an error result\n * @example\n * ```ts\n * const result = await fetchUrl('https://api.example.com/data.json')\n * if (result.ok) {\n * console.log(result.data) // The normalized data\n * } else {\n * console.log('Failed to fetch data')\n * }\n * ```\n */\nexport async function fetchUrl(\n url: string,\n limiter: <T>(fn: () => Promise<T>) => Promise<T>,\n config?: FetchConfig,\n): Promise<ResolveResult> {\n try {\n const host = getHost(url)\n\n // Get the headers that match the domain\n const headers = config?.headers?.find((a) => a.domains.find((d) => d === host) !== undefined)?.headers\n\n const exec = config?.fetch ?? fetch\n\n const result = await limiter(() =>\n exec(url, {\n headers,\n }),\n )\n\n if (result.ok) {\n const body = await result.text()\n\n return {\n ok: true,\n data: normalize(body),\n }\n }\n\n const contentType = result.headers.get('Content-Type') ?? ''\n\n // Warn if the content type is HTML or XML as we only support JSON/YAML\n if (['text/html', 'application/xml'].includes(contentType)) {\n console.warn(`[WARN] We only support JSON/YAML formats, received ${contentType}`)\n }\n\n console.warn(`[WARN] Fetch failed with status ${result.status} ${result.statusText} for URL: ${url}`)\n return {\n ok: false,\n }\n } catch {\n console.warn(`[WARN] Failed to parse JSON/YAML from URL: ${url}`)\n return {\n ok: false,\n }\n }\n}\n\n/**\n * Creates a plugin for handling remote URL references.\n * This plugin validates and fetches data from HTTP/HTTPS URLs.\n *\n * @returns A plugin object with validate and exec functions\n * @example\n * const urlPlugin = fetchUrls()\n * if (urlPlugin.validate('https://example.com/schema.json')) {\n * const result = await urlPlugin.exec('https://example.com/schema.json')\n * }\n */\nexport function fetchUrls(config?: FetchConfig & Partial<{ limit: number | null }>): LoaderPlugin {\n // If there is a limit specified we limit the number of concurrent calls\n const limiter = config?.limit ? createLimiter(config.limit) : <T>(fn: () => Promise<T>) => fn()\n\n return {\n type: 'loader',\n validate: isRemoteUrl,\n exec: (value) => fetchUrl(value, limiter, config),\n }\n}\n"],
5
+ "mappings": "AACA,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAC9B,SAAS,iBAAiB;AAW1B,MAAM,UAAU,CAAC,QAA+B;AAC9C,MAAI;AACF,WAAO,IAAI,IAAI,GAAG,EAAE;AAAA,EACtB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAgBA,eAAsB,SACpB,KACA,SACA,QACwB;AACxB,MAAI;AACF,UAAM,OAAO,QAAQ,GAAG;AAGxB,UAAM,UAAU,QAAQ,SAAS,KAAK,CAAC,MAAM,EAAE,QAAQ,KAAK,CAAC,MAAM,MAAM,IAAI,MAAM,MAAS,GAAG;AAE/F,UAAM,OAAO,QAAQ,SAAS;AAE9B,UAAM,SAAS,MAAM;AAAA,MAAQ,MAC3B,KAAK,KAAK;AAAA,QACR;AAAA,MACF,CAAC;AAAA,IACH;AAEA,QAAI,OAAO,IAAI;AACb,YAAM,OAAO,MAAM,OAAO,KAAK;AAE/B,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,MAAM,UAAU,IAAI;AAAA,MACtB;AAAA,IACF;AAEA,UAAM,cAAc,OAAO,QAAQ,IAAI,cAAc,KAAK;AAG1D,QAAI,CAAC,aAAa,iBAAiB,EAAE,SAAS,WAAW,GAAG;AAC1D,cAAQ,KAAK,sDAAsD,WAAW,EAAE;AAAA,IAClF;AAEA,YAAQ,KAAK,mCAAmC,OAAO,MAAM,IAAI,OAAO,UAAU,aAAa,GAAG,EAAE;AACpG,WAAO;AAAA,MACL,IAAI;AAAA,IACN;AAAA,EACF,QAAQ;AACN,YAAQ,KAAK,8CAA8C,GAAG,EAAE;AAChE,WAAO;AAAA,MACL,IAAI;AAAA,IACN;AAAA,EACF;AACF;AAaO,SAAS,UAAU,QAAwE;AAEhG,QAAM,UAAU,QAAQ,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAI,OAAyB,GAAG;AAE9F,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,IACV,MAAM,CAAC,UAAU,SAAS,OAAO,SAAS,MAAM;AAAA,EAClD;AACF;",
6
6
  "names": []
7
7
  }
@@ -8,7 +8,7 @@ function parseYaml() {
8
8
  try {
9
9
  return {
10
10
  ok: true,
11
- data: YAML.parse(value)
11
+ data: YAML.parse(value, { merge: true, maxAliasCount: 1e4 })
12
12
  };
13
13
  } catch {
14
14
  return {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/bundle/plugins/parse-yaml/index.ts"],
4
- "sourcesContent": ["import YAML from 'yaml'\n\nimport type { LoaderPlugin, ResolveResult } from '@/bundle'\nimport { isYaml } from '@/helpers/is-yaml'\n\n/**\n * Creates a plugin that parses YAML strings into JavaScript objects.\n * @returns A plugin object with validate and exec functions\n * @example\n * ```ts\n * const yamlPlugin = parseYaml()\n * const result = yamlPlugin.exec('name: John\\nage: 30')\n * // result = { name: 'John', age: 30 }\n * ```\n */\nexport function parseYaml(): LoaderPlugin {\n return {\n type: 'loader',\n validate: isYaml,\n exec: async (value): Promise<ResolveResult> => {\n try {\n return {\n ok: true,\n data: YAML.parse(value),\n }\n } catch {\n return {\n ok: false,\n }\n }\n },\n }\n}\n"],
5
- "mappings": "AAAA,OAAO,UAAU;AAGjB,SAAS,cAAc;AAYhB,SAAS,YAA0B;AACxC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,IACV,MAAM,OAAO,UAAkC;AAC7C,UAAI;AACF,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,MAAM,KAAK,MAAM,KAAK;AAAA,QACxB;AAAA,MACF,QAAQ;AACN,eAAO;AAAA,UACL,IAAI;AAAA,QACN;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import YAML from 'yaml'\n\nimport type { LoaderPlugin, ResolveResult } from '@/bundle'\nimport { isYaml } from '@/helpers/is-yaml'\n\n/**\n * Creates a plugin that parses YAML strings into JavaScript objects.\n * @returns A plugin object with validate and exec functions\n * @example\n * ```ts\n * const yamlPlugin = parseYaml()\n * const result = yamlPlugin.exec('name: John\\nage: 30')\n * // result = { name: 'John', age: 30 }\n * ```\n */\nexport function parseYaml(): LoaderPlugin {\n return {\n type: 'loader',\n validate: isYaml,\n exec: async (value): Promise<ResolveResult> => {\n try {\n return {\n ok: true,\n data: YAML.parse(value, { merge: true, maxAliasCount: 10000 }),\n }\n } catch {\n return {\n ok: false,\n }\n }\n },\n }\n}\n"],
5
+ "mappings": "AAAA,OAAO,UAAU;AAGjB,SAAS,cAAc;AAYhB,SAAS,YAA0B;AACxC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,IACV,MAAM,OAAO,UAAkC;AAC7C,UAAI;AACF,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,MAAM,KAAK,MAAM,OAAO,EAAE,OAAO,MAAM,eAAe,IAAM,CAAC;AAAA,QAC/D;AAAA,MACF,QAAQ;AACN,eAAO;AAAA,UACL,IAAI;AAAA,QACN;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../src/helpers/normalize.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,GAAG,OA4BrC"}
1
+ {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../src/helpers/normalize.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,GAAG,OA6BrC"}
@@ -16,7 +16,8 @@ function normalize(content) {
16
16
  return void 0;
17
17
  }
18
18
  return parse(content, {
19
- maxAliasCount: 1e4
19
+ maxAliasCount: 1e4,
20
+ merge: true
20
21
  });
21
22
  }
22
23
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/helpers/normalize.ts"],
4
- "sourcesContent": ["import { parse } from 'yaml'\n\n/**\n * Normalize a string (YAML, JSON, object) to a JavaScript datatype.\n */\nexport function normalize(content: any) {\n if (content === null) {\n return undefined\n }\n\n if (typeof content === 'string') {\n if (content.trim() === '') {\n return undefined\n }\n\n try {\n return JSON.parse(content)\n } catch (_error) {\n // Does it look like YAML?\n const hasColon = /^[^:]+:/.test(content)\n const isJson = content.slice(0, 50).trimStart().startsWith('{')\n\n if (!hasColon || isJson) {\n return undefined\n }\n\n return parse(content, {\n maxAliasCount: 10000,\n })\n }\n }\n\n return content\n}\n"],
5
- "mappings": "AAAA,SAAS,aAAa;AAKf,SAAS,UAAU,SAAc;AACtC,MAAI,YAAY,MAAM;AACpB,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,YAAY,UAAU;AAC/B,QAAI,QAAQ,KAAK,MAAM,IAAI;AACzB,aAAO;AAAA,IACT;AAEA,QAAI;AACF,aAAO,KAAK,MAAM,OAAO;AAAA,IAC3B,SAAS,QAAQ;AAEf,YAAM,WAAW,UAAU,KAAK,OAAO;AACvC,YAAM,SAAS,QAAQ,MAAM,GAAG,EAAE,EAAE,UAAU,EAAE,WAAW,GAAG;AAE9D,UAAI,CAAC,YAAY,QAAQ;AACvB,eAAO;AAAA,MACT;AAEA,aAAO,MAAM,SAAS;AAAA,QACpB,eAAe;AAAA,MACjB,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;",
4
+ "sourcesContent": ["import { parse } from 'yaml'\n\n/**\n * Normalize a string (YAML, JSON, object) to a JavaScript datatype.\n */\nexport function normalize(content: any) {\n if (content === null) {\n return undefined\n }\n\n if (typeof content === 'string') {\n if (content.trim() === '') {\n return undefined\n }\n\n try {\n return JSON.parse(content)\n } catch (_error) {\n // Does it look like YAML?\n const hasColon = /^[^:]+:/.test(content)\n const isJson = content.slice(0, 50).trimStart().startsWith('{')\n\n if (!hasColon || isJson) {\n return undefined\n }\n\n return parse(content, {\n maxAliasCount: 10000,\n merge: true,\n })\n }\n }\n\n return content\n}\n"],
5
+ "mappings": "AAAA,SAAS,aAAa;AAKf,SAAS,UAAU,SAAc;AACtC,MAAI,YAAY,MAAM;AACpB,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,YAAY,UAAU;AAC/B,QAAI,QAAQ,KAAK,MAAM,IAAI;AACzB,aAAO;AAAA,IACT;AAEA,QAAI;AACF,aAAO,KAAK,MAAM,OAAO;AAAA,IAC3B,SAAS,QAAQ;AAEf,YAAM,WAAW,UAAU,KAAK,OAAO;AACvC,YAAM,SAAS,QAAQ,MAAM,GAAG,EAAE,EAAE,UAAU,EAAE,WAAW,GAAG;AAE9D,UAAI,CAAC,YAAY,QAAQ;AACvB,eAAO;AAAA,MACT;AAEA,aAAO,MAAM,SAAS;AAAA,QACpB,eAAe;AAAA,QACf,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -6,7 +6,7 @@ import type { UnknownObject } from '../types.js';
6
6
  * Features:
7
7
  * - If an object contains a `$ref` property, accessing the special `$ref-value` property will resolve and return the referenced value from the root object.
8
8
  * - All nested objects and arrays are recursively wrapped in proxies, so reference resolution works at any depth.
9
- * - Properties starting with an underscore (_) are considered internal and are hidden by default: they return undefined on access, are excluded from enumeration, and `'in'` checks return false. This can be overridden with the `showInternal` option.
9
+ * - Properties starting with `__scalar_` are considered internal and are hidden by default: they return undefined on access, are excluded from enumeration, and `'in'` checks return false. This can be overridden with the `showInternal` option.
10
10
  * - Setting, deleting, and enumerating properties works as expected, including for proxied references.
11
11
  * - Ensures referential stability by caching proxies for the same target object.
12
12
  *
@@ -21,17 +21,17 @@ import type { UnknownObject } from '../types.js';
21
21
  * foo: { bar: 123 }
22
22
  * },
23
23
  * refObj: { $ref: '#/definitions/foo' },
24
- * _internal: 'hidden property'
24
+ * __scalar_internal: 'hidden property'
25
25
  * }
26
26
  * const proxy = createMagicProxy(input)
27
27
  *
28
28
  * // Accessing proxy.refObj['$ref-value'] will resolve to { bar: 123 }
29
29
  * console.log(proxy.refObj['$ref-value']) // { bar: 123 }
30
30
  *
31
- * // Properties starting with underscore are hidden
32
- * console.log(proxy._internal) // undefined
33
- * console.log('_internal' in proxy) // false
34
- * console.log(Object.keys(proxy)) // ['definitions', 'refObj'] (no '_internal')
31
+ * // Properties starting with __scalar_ are hidden
32
+ * console.log(proxy.__scalar_internal) // undefined
33
+ * console.log('__scalar_internal' in proxy) // false
34
+ * console.log(Object.keys(proxy)) // ['definitions', 'refObj'] (no '__scalar_internal')
35
35
  *
36
36
  * // Setting and deleting properties works as expected
37
37
  * proxy.refObj.extra = 'hello'
@@ -25,7 +25,7 @@ const createMagicProxy = (target, options, args = {
25
25
  * Proxy "get" trap for magic proxy.
26
26
  * - If accessing the special isMagicProxy symbol, return true to identify proxy.
27
27
  * - If accessing the magicProxyTarget symbol, return the original target object.
28
- * - Hide properties starting with underscore by returning undefined.
28
+ * - Hide properties starting with __scalar_ by returning undefined.
29
29
  * - If accessing "$ref-value" and the object has a local $ref, resolve and return the referenced value as a new magic proxy.
30
30
  * - For all other properties, recursively wrap the returned value in a magic proxy (if applicable).
31
31
  */
@@ -36,7 +36,7 @@ const createMagicProxy = (target, options, args = {
36
36
  if (prop === magicProxyTarget) {
37
37
  return target2;
38
38
  }
39
- if (typeof prop === "string" && prop.startsWith("_") && !options?.showInternal) {
39
+ if (typeof prop === "string" && prop.startsWith("__scalar_") && !options?.showInternal) {
40
40
  return void 0;
41
41
  }
42
42
  const ref = Reflect.get(target2, REF_KEY, receiver);
@@ -65,12 +65,12 @@ const createMagicProxy = (target, options, args = {
65
65
  * Allows setting properties on the proxied object.
66
66
  * This will update the underlying target object.
67
67
  *
68
- * Note: it will not update if the property starts with an underscore (_)
68
+ * Note: it will not update if the property starts with __scalar_
69
69
  * Those will be considered private properties by the proxy
70
70
  */
71
71
  set(target2, prop, newValue, receiver) {
72
72
  const ref = Reflect.get(target2, REF_KEY, receiver);
73
- if (typeof prop === "string" && prop.startsWith("_") && !options?.showInternal) {
73
+ if (typeof prop === "string" && prop.startsWith("__scalar_") && !options?.showInternal) {
74
74
  return true;
75
75
  }
76
76
  if (prop === REF_VALUE && typeof ref === "string") {
@@ -110,11 +110,11 @@ Please fix your input file to fix this issue.`
110
110
  * - Pretend that "$ref-value" exists if "$ref" exists on the target.
111
111
  * This allows expressions like `"$ref-value" in obj` to return true for objects with a $ref,
112
112
  * even though "$ref-value" is a virtual property provided by the proxy.
113
- * - Hide properties starting with underscore by returning false.
113
+ * - Hide properties starting with __scalar_ by returning false.
114
114
  * - For all other properties, defer to the default Reflect.has behavior.
115
115
  */
116
116
  has(target2, prop) {
117
- if (typeof prop === "string" && prop.startsWith("_") && !options?.showInternal) {
117
+ if (typeof prop === "string" && prop.startsWith("__scalar_") && !options?.showInternal) {
118
118
  return false;
119
119
  }
120
120
  if (prop === REF_VALUE && REF_KEY in target2) {
@@ -128,12 +128,12 @@ Please fix your input file to fix this issue.`
128
128
  * - If the object has a "$ref" property, ensures that "$ref-value" is also included in the keys,
129
129
  * even though "$ref-value" is a virtual property provided by the proxy.
130
130
  * This allows Object.keys, Reflect.ownKeys, etc. to include "$ref-value" for objects with $ref.
131
- * - Filters out properties starting with underscore.
131
+ * - Filters out properties starting with __scalar_.
132
132
  */
133
133
  ownKeys(target2) {
134
134
  const keys = Reflect.ownKeys(target2);
135
135
  const filteredKeys = keys.filter(
136
- (key) => typeof key !== "string" || !(key.startsWith("_") && !options?.showInternal)
136
+ (key) => typeof key !== "string" || !(key.startsWith("__scalar_") && !options?.showInternal)
137
137
  );
138
138
  if (REF_KEY in target2 && !filteredKeys.includes(REF_VALUE)) {
139
139
  filteredKeys.push(REF_VALUE);
@@ -143,12 +143,12 @@ Please fix your input file to fix this issue.`
143
143
  /**
144
144
  * Proxy "getOwnPropertyDescriptor" trap for magic proxy.
145
145
  * - For the virtual "$ref-value" property, returns a descriptor that makes it appear as a regular property.
146
- * - Hide properties starting with underscore by returning undefined.
146
+ * - Hide properties starting with __scalar_ by returning undefined.
147
147
  * - For all other properties, delegates to the default Reflect.getOwnPropertyDescriptor behavior.
148
148
  * - This ensures that Object.getOwnPropertyDescriptor and similar methods work correctly with the virtual property.
149
149
  */
150
150
  getOwnPropertyDescriptor(target2, prop) {
151
- if (typeof prop === "string" && prop.startsWith("_") && !options?.showInternal) {
151
+ if (typeof prop === "string" && prop.startsWith("__scalar_") && !options?.showInternal) {
152
152
  return void 0;
153
153
  }
154
154
  const ref = Reflect.get(target2, REF_KEY);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/magic-proxy/proxy.ts"],
4
- "sourcesContent": ["import { convertToLocalRef } from '@/helpers/convert-to-local-ref'\nimport { getId, getSchemas } from '@/helpers/get-schemas'\nimport { getValueByPath } from '@/helpers/get-value-by-path'\nimport { isObject } from '@/helpers/is-object'\nimport { createPathFromSegments, parseJsonPointer } from '@/helpers/json-path-utils'\nimport type { UnknownObject } from '@/types'\n\nconst isMagicProxy = Symbol('isMagicProxy')\nconst magicProxyTarget = Symbol('magicProxyTarget')\n\nconst REF_VALUE = '$ref-value'\nconst REF_KEY = '$ref'\n\n/**\n * Creates a \"magic\" proxy for a given object or array, enabling transparent access to\n * JSON Reference ($ref) values as if they were directly present on the object.\n *\n * Features:\n * - If an object contains a `$ref` property, accessing the special `$ref-value` property will resolve and return the referenced value from the root object.\n * - All nested objects and arrays are recursively wrapped in proxies, so reference resolution works at any depth.\n * - Properties starting with an underscore (_) are considered internal and are hidden by default: they return undefined on access, are excluded from enumeration, and `'in'` checks return false. This can be overridden with the `showInternal` option.\n * - Setting, deleting, and enumerating properties works as expected, including for proxied references.\n * - Ensures referential stability by caching proxies for the same target object.\n *\n * @param target - The object or array to wrap in a magic proxy\n * @param options - Optional settings (e.g., showInternal to expose internal properties)\n * @param args - Internal arguments for advanced usage (root object, proxy/cache maps, current context)\n * @returns A proxied version of the input object/array with magic $ref-value support\n *\n * @example\n * const input = {\n * definitions: {\n * foo: { bar: 123 }\n * },\n * refObj: { $ref: '#/definitions/foo' },\n * _internal: 'hidden property'\n * }\n * const proxy = createMagicProxy(input)\n *\n * // Accessing proxy.refObj['$ref-value'] will resolve to { bar: 123 }\n * console.log(proxy.refObj['$ref-value']) // { bar: 123 }\n *\n * // Properties starting with underscore are hidden\n * console.log(proxy._internal) // undefined\n * console.log('_internal' in proxy) // false\n * console.log(Object.keys(proxy)) // ['definitions', 'refObj'] (no '_internal')\n *\n * // Setting and deleting properties works as expected\n * proxy.refObj.extra = 'hello'\n * delete proxy.refObj.extra\n */\nexport const createMagicProxy = <T extends Record<keyof T & symbol, unknown>, S extends UnknownObject>(\n target: T,\n options?: Partial<{ showInternal: boolean }>,\n args: {\n /**\n * The root object for resolving local JSON references.\n */\n root: S | T\n /**\n * Cache to store already created proxies for target objects to ensure referential stability.\n *\n * It is helpful when dealing with reactive frameworks like Vue,\n */\n proxyCache: WeakMap<object, T>\n /**\n * Cache to store resolved JSON references.\n */\n cache: Map<string, unknown>\n /**\n * Map of all schemas by their $id or $anchor for cross-document reference resolution.\n */\n schemas: Map<string, string>\n /**\n * The current JSON path context within the root object.\n *\n * Used to resolve $anchor references correctly.\n */\n currentContext: string\n } = {\n root: target,\n proxyCache: new WeakMap(),\n cache: new Map(),\n schemas: getSchemas(target),\n currentContext: '',\n },\n) => {\n if (!isObject(target) && !Array.isArray(target)) {\n return target\n }\n\n // Return existing proxy for the same target to ensure referential stability\n if (args.proxyCache.has(target)) {\n return args.proxyCache.get(target)\n }\n\n const handler: ProxyHandler<T> = {\n /**\n * Proxy \"get\" trap for magic proxy.\n * - If accessing the special isMagicProxy symbol, return true to identify proxy.\n * - If accessing the magicProxyTarget symbol, return the original target object.\n * - Hide properties starting with underscore by returning undefined.\n * - If accessing \"$ref-value\" and the object has a local $ref, resolve and return the referenced value as a new magic proxy.\n * - For all other properties, recursively wrap the returned value in a magic proxy (if applicable).\n */\n get(target, prop, receiver) {\n if (prop === isMagicProxy) {\n // Used to identify if an object is a magic proxy\n return true\n }\n\n if (prop === magicProxyTarget) {\n // Used to retrieve the original target object from the proxy\n return target\n }\n\n // Hide properties starting with underscore - these are considered internal/private properties\n // and should not be accessible through the magic proxy interface\n if (typeof prop === 'string' && prop.startsWith('_') && !options?.showInternal) {\n return undefined\n }\n\n // Get the $ref value of the current target (if any)\n const ref = Reflect.get(target, REF_KEY, receiver)\n // Get the identifier ($id) of the current target for context tracking\n const id = getId(target)\n\n // If accessing \"$ref-value\" and $ref is a local reference, resolve and return the referenced value\n if (prop === REF_VALUE && typeof ref === 'string') {\n // Check cache first for performance optimization\n if (args.cache.has(ref)) {\n return args.cache.get(ref)\n }\n\n const path = convertToLocalRef(ref, id ?? args.currentContext, args.schemas)\n\n if (path === undefined) {\n return undefined\n }\n\n // Resolve the reference and create a new magic proxy\n const resolvedValue = getValueByPath(args.root, parseJsonPointer(`#/${path}`))\n const proxiedValue = createMagicProxy(resolvedValue.value, options, {\n ...args,\n currentContext: resolvedValue.context,\n })\n\n // Store in cache for future lookups\n args.cache.set(ref, proxiedValue)\n return proxiedValue\n }\n\n // For all other properties, recursively wrap the value in a magic proxy\n const value = Reflect.get(target, prop, receiver)\n return createMagicProxy(value as T, options, { ...args, currentContext: id ?? args.currentContext })\n },\n /**\n * Proxy \"set\" trap for magic proxy.\n * Allows setting properties on the proxied object.\n * This will update the underlying target object.\n *\n * Note: it will not update if the property starts with an underscore (_)\n * Those will be considered private properties by the proxy\n */\n set(target, prop, newValue, receiver) {\n const ref = Reflect.get(target, REF_KEY, receiver)\n\n if (typeof prop === 'string' && prop.startsWith('_') && !options?.showInternal) {\n return true\n }\n\n if (prop === REF_VALUE && typeof ref === 'string') {\n const id = getId(target)\n const path = convertToLocalRef(ref, id ?? args.currentContext, args.schemas)\n\n if (path === undefined) {\n return undefined\n }\n\n const segments = parseJsonPointer(`#/${path}`)\n\n if (segments.length === 0) {\n return false // Can not set top level $ref-value\n }\n\n // Get the parent node or create it if it does not exist\n const getParentNode = () => getValueByPath(args.root, segments.slice(0, -1)).value\n\n if (getParentNode() === undefined) {\n createPathFromSegments(args.root, segments.slice(0, -1))\n\n // In this case the ref is pointing to an invalid path, so we warn the user\n console.warn(\n `Trying to set $ref-value for invalid reference: ${ref}\\n\\nPlease fix your input file to fix this issue.`,\n )\n }\n\n // Set the value on the parent node\n getParentNode()[segments.at(-1)] = newValue\n return true\n }\n\n return Reflect.set(target, prop, newValue, receiver)\n },\n /**\n * Proxy \"deleteProperty\" trap for magic proxy.\n * Allows deleting properties from the proxied object.\n * This will update the underlying target object.\n */\n deleteProperty(target, prop) {\n return Reflect.deleteProperty(target, prop)\n },\n /**\n * Proxy \"has\" trap for magic proxy.\n * - Pretend that \"$ref-value\" exists if \"$ref\" exists on the target.\n * This allows expressions like `\"$ref-value\" in obj` to return true for objects with a $ref,\n * even though \"$ref-value\" is a virtual property provided by the proxy.\n * - Hide properties starting with underscore by returning false.\n * - For all other properties, defer to the default Reflect.has behavior.\n */\n has(target, prop) {\n // Hide properties starting with underscore\n if (typeof prop === 'string' && prop.startsWith('_') && !options?.showInternal) {\n return false\n }\n\n // Pretend that \"$ref-value\" exists if \"$ref\" exists\n if (prop === REF_VALUE && REF_KEY in target) {\n return true\n }\n return Reflect.has(target, prop)\n },\n /**\n * Proxy \"ownKeys\" trap for magic proxy.\n * - Returns the list of own property keys for the proxied object.\n * - If the object has a \"$ref\" property, ensures that \"$ref-value\" is also included in the keys,\n * even though \"$ref-value\" is a virtual property provided by the proxy.\n * This allows Object.keys, Reflect.ownKeys, etc. to include \"$ref-value\" for objects with $ref.\n * - Filters out properties starting with underscore.\n */\n ownKeys(target) {\n const keys = Reflect.ownKeys(target)\n\n // Filter out properties starting with underscore\n const filteredKeys = keys.filter(\n (key) => typeof key !== 'string' || !(key.startsWith('_') && !options?.showInternal),\n )\n\n if (REF_KEY in target && !filteredKeys.includes(REF_VALUE)) {\n filteredKeys.push(REF_VALUE)\n }\n return filteredKeys\n },\n\n /**\n * Proxy \"getOwnPropertyDescriptor\" trap for magic proxy.\n * - For the virtual \"$ref-value\" property, returns a descriptor that makes it appear as a regular property.\n * - Hide properties starting with underscore by returning undefined.\n * - For all other properties, delegates to the default Reflect.getOwnPropertyDescriptor behavior.\n * - This ensures that Object.getOwnPropertyDescriptor and similar methods work correctly with the virtual property.\n */\n getOwnPropertyDescriptor(target, prop) {\n // Hide properties starting with underscore\n if (typeof prop === 'string' && prop.startsWith('_') && !options?.showInternal) {\n return undefined\n }\n\n const ref = Reflect.get(target, REF_KEY)\n\n if (prop === REF_VALUE && typeof ref === 'string') {\n return {\n configurable: true,\n enumerable: true,\n value: undefined,\n writable: false,\n }\n }\n\n // Otherwise, delegate to the default behavior\n return Reflect.getOwnPropertyDescriptor(target, prop)\n },\n }\n\n const proxied = new Proxy<T>(target, handler)\n args.proxyCache.set(target, proxied)\n return proxied\n}\n\n/**\n * Gets the raw (non-proxied) version of an object created by createMagicProxy.\n * This is useful when you need to access the original object without the magic proxy wrapper.\n *\n * @param obj - The magic proxy object to get the raw version of\n * @returns The raw version of the object\n * @example\n * const proxy = createMagicProxy({ foo: { $ref: '#/bar' } })\n * const raw = getRaw(proxy) // { foo: { $ref: '#/bar' } }\n */\nexport function getRaw<T>(obj: T): T {\n if (typeof obj !== 'object' || obj === null) {\n return obj\n }\n\n if ((obj as T & { [isMagicProxy]: boolean | undefined })[isMagicProxy]) {\n return (obj as T & { [magicProxyTarget]: T })[magicProxyTarget]\n }\n\n return obj\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { convertToLocalRef } from '@/helpers/convert-to-local-ref'\nimport { getId, getSchemas } from '@/helpers/get-schemas'\nimport { getValueByPath } from '@/helpers/get-value-by-path'\nimport { isObject } from '@/helpers/is-object'\nimport { createPathFromSegments, parseJsonPointer } from '@/helpers/json-path-utils'\nimport type { UnknownObject } from '@/types'\n\nconst isMagicProxy = Symbol('isMagicProxy')\nconst magicProxyTarget = Symbol('magicProxyTarget')\n\nconst REF_VALUE = '$ref-value'\nconst REF_KEY = '$ref'\n\n/**\n * Creates a \"magic\" proxy for a given object or array, enabling transparent access to\n * JSON Reference ($ref) values as if they were directly present on the object.\n *\n * Features:\n * - If an object contains a `$ref` property, accessing the special `$ref-value` property will resolve and return the referenced value from the root object.\n * - All nested objects and arrays are recursively wrapped in proxies, so reference resolution works at any depth.\n * - Properties starting with `__scalar_` are considered internal and are hidden by default: they return undefined on access, are excluded from enumeration, and `'in'` checks return false. This can be overridden with the `showInternal` option.\n * - Setting, deleting, and enumerating properties works as expected, including for proxied references.\n * - Ensures referential stability by caching proxies for the same target object.\n *\n * @param target - The object or array to wrap in a magic proxy\n * @param options - Optional settings (e.g., showInternal to expose internal properties)\n * @param args - Internal arguments for advanced usage (root object, proxy/cache maps, current context)\n * @returns A proxied version of the input object/array with magic $ref-value support\n *\n * @example\n * const input = {\n * definitions: {\n * foo: { bar: 123 }\n * },\n * refObj: { $ref: '#/definitions/foo' },\n * __scalar_internal: 'hidden property'\n * }\n * const proxy = createMagicProxy(input)\n *\n * // Accessing proxy.refObj['$ref-value'] will resolve to { bar: 123 }\n * console.log(proxy.refObj['$ref-value']) // { bar: 123 }\n *\n * // Properties starting with __scalar_ are hidden\n * console.log(proxy.__scalar_internal) // undefined\n * console.log('__scalar_internal' in proxy) // false\n * console.log(Object.keys(proxy)) // ['definitions', 'refObj'] (no '__scalar_internal')\n *\n * // Setting and deleting properties works as expected\n * proxy.refObj.extra = 'hello'\n * delete proxy.refObj.extra\n */\nexport const createMagicProxy = <T extends Record<keyof T & symbol, unknown>, S extends UnknownObject>(\n target: T,\n options?: Partial<{ showInternal: boolean }>,\n args: {\n /**\n * The root object for resolving local JSON references.\n */\n root: S | T\n /**\n * Cache to store already created proxies for target objects to ensure referential stability.\n *\n * It is helpful when dealing with reactive frameworks like Vue,\n */\n proxyCache: WeakMap<object, T>\n /**\n * Cache to store resolved JSON references.\n */\n cache: Map<string, unknown>\n /**\n * Map of all schemas by their $id or $anchor for cross-document reference resolution.\n */\n schemas: Map<string, string>\n /**\n * The current JSON path context within the root object.\n *\n * Used to resolve $anchor references correctly.\n */\n currentContext: string\n } = {\n root: target,\n proxyCache: new WeakMap(),\n cache: new Map(),\n schemas: getSchemas(target),\n currentContext: '',\n },\n) => {\n if (!isObject(target) && !Array.isArray(target)) {\n return target\n }\n\n // Return existing proxy for the same target to ensure referential stability\n if (args.proxyCache.has(target)) {\n return args.proxyCache.get(target)\n }\n\n const handler: ProxyHandler<T> = {\n /**\n * Proxy \"get\" trap for magic proxy.\n * - If accessing the special isMagicProxy symbol, return true to identify proxy.\n * - If accessing the magicProxyTarget symbol, return the original target object.\n * - Hide properties starting with __scalar_ by returning undefined.\n * - If accessing \"$ref-value\" and the object has a local $ref, resolve and return the referenced value as a new magic proxy.\n * - For all other properties, recursively wrap the returned value in a magic proxy (if applicable).\n */\n get(target, prop, receiver) {\n if (prop === isMagicProxy) {\n // Used to identify if an object is a magic proxy\n return true\n }\n\n if (prop === magicProxyTarget) {\n // Used to retrieve the original target object from the proxy\n return target\n }\n\n // Hide properties starting with __scalar_ - these are considered internal/private properties\n // and should not be accessible through the magic proxy interface\n if (typeof prop === 'string' && prop.startsWith('__scalar_') && !options?.showInternal) {\n return undefined\n }\n\n // Get the $ref value of the current target (if any)\n const ref = Reflect.get(target, REF_KEY, receiver)\n // Get the identifier ($id) of the current target for context tracking\n const id = getId(target)\n\n // If accessing \"$ref-value\" and $ref is a local reference, resolve and return the referenced value\n if (prop === REF_VALUE && typeof ref === 'string') {\n // Check cache first for performance optimization\n if (args.cache.has(ref)) {\n return args.cache.get(ref)\n }\n\n const path = convertToLocalRef(ref, id ?? args.currentContext, args.schemas)\n\n if (path === undefined) {\n return undefined\n }\n\n // Resolve the reference and create a new magic proxy\n const resolvedValue = getValueByPath(args.root, parseJsonPointer(`#/${path}`))\n const proxiedValue = createMagicProxy(resolvedValue.value, options, {\n ...args,\n currentContext: resolvedValue.context,\n })\n\n // Store in cache for future lookups\n args.cache.set(ref, proxiedValue)\n return proxiedValue\n }\n\n // For all other properties, recursively wrap the value in a magic proxy\n const value = Reflect.get(target, prop, receiver)\n return createMagicProxy(value as T, options, { ...args, currentContext: id ?? args.currentContext })\n },\n /**\n * Proxy \"set\" trap for magic proxy.\n * Allows setting properties on the proxied object.\n * This will update the underlying target object.\n *\n * Note: it will not update if the property starts with __scalar_\n * Those will be considered private properties by the proxy\n */\n set(target, prop, newValue, receiver) {\n const ref = Reflect.get(target, REF_KEY, receiver)\n\n if (typeof prop === 'string' && prop.startsWith('__scalar_') && !options?.showInternal) {\n return true\n }\n\n if (prop === REF_VALUE && typeof ref === 'string') {\n const id = getId(target)\n const path = convertToLocalRef(ref, id ?? args.currentContext, args.schemas)\n\n if (path === undefined) {\n return undefined\n }\n\n const segments = parseJsonPointer(`#/${path}`)\n\n if (segments.length === 0) {\n return false // Can not set top level $ref-value\n }\n\n // Get the parent node or create it if it does not exist\n const getParentNode = () => getValueByPath(args.root, segments.slice(0, -1)).value\n\n if (getParentNode() === undefined) {\n createPathFromSegments(args.root, segments.slice(0, -1))\n\n // In this case the ref is pointing to an invalid path, so we warn the user\n console.warn(\n `Trying to set $ref-value for invalid reference: ${ref}\\n\\nPlease fix your input file to fix this issue.`,\n )\n }\n\n // Set the value on the parent node\n getParentNode()[segments.at(-1)] = newValue\n return true\n }\n\n return Reflect.set(target, prop, newValue, receiver)\n },\n /**\n * Proxy \"deleteProperty\" trap for magic proxy.\n * Allows deleting properties from the proxied object.\n * This will update the underlying target object.\n */\n deleteProperty(target, prop) {\n return Reflect.deleteProperty(target, prop)\n },\n /**\n * Proxy \"has\" trap for magic proxy.\n * - Pretend that \"$ref-value\" exists if \"$ref\" exists on the target.\n * This allows expressions like `\"$ref-value\" in obj` to return true for objects with a $ref,\n * even though \"$ref-value\" is a virtual property provided by the proxy.\n * - Hide properties starting with __scalar_ by returning false.\n * - For all other properties, defer to the default Reflect.has behavior.\n */\n has(target, prop) {\n // Hide properties starting with __scalar_\n if (typeof prop === 'string' && prop.startsWith('__scalar_') && !options?.showInternal) {\n return false\n }\n\n // Pretend that \"$ref-value\" exists if \"$ref\" exists\n if (prop === REF_VALUE && REF_KEY in target) {\n return true\n }\n return Reflect.has(target, prop)\n },\n /**\n * Proxy \"ownKeys\" trap for magic proxy.\n * - Returns the list of own property keys for the proxied object.\n * - If the object has a \"$ref\" property, ensures that \"$ref-value\" is also included in the keys,\n * even though \"$ref-value\" is a virtual property provided by the proxy.\n * This allows Object.keys, Reflect.ownKeys, etc. to include \"$ref-value\" for objects with $ref.\n * - Filters out properties starting with __scalar_.\n */\n ownKeys(target) {\n const keys = Reflect.ownKeys(target)\n\n // Filter out properties starting with __scalar_\n const filteredKeys = keys.filter(\n (key) => typeof key !== 'string' || !(key.startsWith('__scalar_') && !options?.showInternal),\n )\n\n if (REF_KEY in target && !filteredKeys.includes(REF_VALUE)) {\n filteredKeys.push(REF_VALUE)\n }\n return filteredKeys\n },\n\n /**\n * Proxy \"getOwnPropertyDescriptor\" trap for magic proxy.\n * - For the virtual \"$ref-value\" property, returns a descriptor that makes it appear as a regular property.\n * - Hide properties starting with __scalar_ by returning undefined.\n * - For all other properties, delegates to the default Reflect.getOwnPropertyDescriptor behavior.\n * - This ensures that Object.getOwnPropertyDescriptor and similar methods work correctly with the virtual property.\n */\n getOwnPropertyDescriptor(target, prop) {\n // Hide properties starting with __scalar_\n if (typeof prop === 'string' && prop.startsWith('__scalar_') && !options?.showInternal) {\n return undefined\n }\n\n const ref = Reflect.get(target, REF_KEY)\n\n if (prop === REF_VALUE && typeof ref === 'string') {\n return {\n configurable: true,\n enumerable: true,\n value: undefined,\n writable: false,\n }\n }\n\n // Otherwise, delegate to the default behavior\n return Reflect.getOwnPropertyDescriptor(target, prop)\n },\n }\n\n const proxied = new Proxy<T>(target, handler)\n args.proxyCache.set(target, proxied)\n return proxied\n}\n\n/**\n * Gets the raw (non-proxied) version of an object created by createMagicProxy.\n * This is useful when you need to access the original object without the magic proxy wrapper.\n *\n * @param obj - The magic proxy object to get the raw version of\n * @returns The raw version of the object\n * @example\n * const proxy = createMagicProxy({ foo: { $ref: '#/bar' } })\n * const raw = getRaw(proxy) // { foo: { $ref: '#/bar' } }\n */\nexport function getRaw<T>(obj: T): T {\n if (typeof obj !== 'object' || obj === null) {\n return obj\n }\n\n if ((obj as T & { [isMagicProxy]: boolean | undefined })[isMagicProxy]) {\n return (obj as T & { [magicProxyTarget]: T })[magicProxyTarget]\n }\n\n return obj\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["target"]
7
7
  }
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "url": "git+https://github.com/scalar/scalar.git",
11
11
  "directory": "packages/json-magic"
12
12
  },
13
- "version": "0.5.2",
13
+ "version": "0.6.1",
14
14
  "engines": {
15
15
  "node": ">=20"
16
16
  },
@@ -54,7 +54,7 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "yaml": "2.8.0",
57
- "@scalar/helpers": "0.0.11"
57
+ "@scalar/helpers": "0.0.12"
58
58
  },
59
59
  "devDependencies": {
60
60
  "fastify": "^5.3.3",
@@ -62,10 +62,19 @@ export async function fetchUrl(
62
62
  }
63
63
  }
64
64
 
65
+ const contentType = result.headers.get('Content-Type') ?? ''
66
+
67
+ // Warn if the content type is HTML or XML as we only support JSON/YAML
68
+ if (['text/html', 'application/xml'].includes(contentType)) {
69
+ console.warn(`[WARN] We only support JSON/YAML formats, received ${contentType}`)
70
+ }
71
+
72
+ console.warn(`[WARN] Fetch failed with status ${result.status} ${result.statusText} for URL: ${url}`)
65
73
  return {
66
74
  ok: false,
67
75
  }
68
76
  } catch {
77
+ console.warn(`[WARN] Failed to parse JSON/YAML from URL: ${url}`)
69
78
  return {
70
79
  ok: false,
71
80
  }
@@ -21,7 +21,7 @@ export function parseYaml(): LoaderPlugin {
21
21
  try {
22
22
  return {
23
23
  ok: true,
24
- data: YAML.parse(value),
24
+ data: YAML.parse(value, { merge: true, maxAliasCount: 10000 }),
25
25
  }
26
26
  } catch {
27
27
  return {
@@ -26,6 +26,7 @@ export function normalize(content: any) {
26
26
 
27
27
  return parse(content, {
28
28
  maxAliasCount: 10000,
29
+ merge: true,
29
30
  })
30
31
  }
31
32
  }
@@ -1154,41 +1154,41 @@ describe('createMagicProxy', () => {
1154
1154
  })
1155
1155
 
1156
1156
  describe('show underscore properties when specified', () => {
1157
- it('should not hide properties starting with underscore from direct access', () => {
1157
+ it('should not hide properties starting with __scalar_ from direct access', () => {
1158
1158
  const input = {
1159
1159
  public: 'visible',
1160
- _private: 'hidden',
1161
- __internal: 'also hidden',
1160
+ __scalar_private: 'hidden',
1161
+ __scalar_internal: 'also hidden',
1162
1162
  normal_underscore: 'visible with underscore in middle',
1163
1163
  }
1164
1164
 
1165
1165
  const result = createMagicProxy(input, { showInternal: true })
1166
1166
 
1167
1167
  expect(result.public).toBe('visible')
1168
- expect(result._private).toBe('hidden')
1169
- expect(result.__internal).toBe('also hidden')
1168
+ expect(result.__scalar_private).toBe('hidden')
1169
+ expect(result.__scalar_internal).toBe('also hidden')
1170
1170
  expect(result.normal_underscore).toBe('visible with underscore in middle')
1171
1171
  })
1172
1172
 
1173
- it('should not hide underscore properties from "in" operator', () => {
1173
+ it('should not hide __scalar_ properties from "in" operator', () => {
1174
1174
  const input = {
1175
1175
  public: 'visible',
1176
- _private: 'hidden',
1177
- __internal: 'also hidden',
1176
+ __scalar_private: 'hidden',
1177
+ __scalar_internal: 'also hidden',
1178
1178
  }
1179
1179
 
1180
1180
  const result = createMagicProxy(input, { showInternal: true })
1181
1181
 
1182
1182
  expect('public' in result).toBe(true)
1183
- expect('_private' in result).toBe(true)
1184
- expect('__internal' in result).toBe(true)
1183
+ expect('__scalar_private' in result).toBe(true)
1184
+ expect('__scalar_internal' in result).toBe(true)
1185
1185
  })
1186
1186
 
1187
- it('should not exclude underscore properties from Object.keys enumeration', () => {
1187
+ it('should not exclude __scalar_ properties from Object.keys enumeration', () => {
1188
1188
  const input = {
1189
1189
  public: 'visible',
1190
- _private: 'hidden',
1191
- __internal: 'also hidden',
1190
+ __scalar_private: 'hidden',
1191
+ __scalar_internal: 'also hidden',
1192
1192
  another: 'visible',
1193
1193
  }
1194
1194
 
@@ -1197,79 +1197,79 @@ describe('createMagicProxy', () => {
1197
1197
 
1198
1198
  expect(keys).toContain('public')
1199
1199
  expect(keys).toContain('another')
1200
- expect(keys).toContain('_private')
1201
- expect(keys).toContain('__internal')
1200
+ expect(keys).toContain('__scalar_private')
1201
+ expect(keys).toContain('__scalar_internal')
1202
1202
  })
1203
1203
 
1204
- it('should not hide underscore properties from getOwnPropertyDescriptor', () => {
1204
+ it('should not hide __scalar_ properties from getOwnPropertyDescriptor', () => {
1205
1205
  const input = {
1206
1206
  public: 'visible',
1207
- _private: 'hidden',
1207
+ __scalar_private: 'hidden',
1208
1208
  }
1209
1209
 
1210
1210
  const result = createMagicProxy(input, { showInternal: true })
1211
1211
 
1212
1212
  expect(Object.getOwnPropertyDescriptor(result, 'public')).toBeDefined()
1213
- expect(Object.getOwnPropertyDescriptor(result, '_private')).toBeDefined()
1213
+ expect(Object.getOwnPropertyDescriptor(result, '__scalar_private')).toBeDefined()
1214
1214
  })
1215
1215
 
1216
- it('should not hide underscore properties in nested objects', () => {
1216
+ it('should not hide __scalar_ properties in nested objects', () => {
1217
1217
  const input = {
1218
1218
  nested: {
1219
1219
  public: 'visible',
1220
- _private: 'hidden',
1220
+ __scalar_private: 'hidden',
1221
1221
  deeper: {
1222
- _alsoHidden: 'secret',
1222
+ __scalar_alsoHidden: 'secret',
1223
1223
  visible: 'shown',
1224
1224
  },
1225
1225
  },
1226
- _topLevel: 'hidden',
1226
+ __scalar_topLevel: 'hidden',
1227
1227
  }
1228
1228
 
1229
1229
  const result = createMagicProxy(input, { showInternal: true })
1230
1230
 
1231
- expect(result._topLevel).toBe('hidden')
1231
+ expect(result.__scalar_topLevel).toBe('hidden')
1232
1232
  expect(result.nested.public).toBe('visible')
1233
- expect(result.nested._private).toBe('hidden')
1234
- expect(result.nested.deeper._alsoHidden).toBe('secret')
1233
+ expect(result.nested.__scalar_private).toBe('hidden')
1234
+ expect(result.nested.deeper.__scalar_alsoHidden).toBe('secret')
1235
1235
  expect(result.nested.deeper.visible).toBe('shown')
1236
1236
  })
1237
1237
 
1238
- it('should show underscore properties with arrays containing objects with underscore properties', () => {
1238
+ it('should show __scalar_ properties with arrays containing objects with __scalar_ properties', () => {
1239
1239
  const input = {
1240
1240
  items: [
1241
- { public: 'item1', _private: 'hidden1' },
1242
- { public: 'item2', _private: 'hidden2' },
1241
+ { public: 'item1', __scalar_private: 'hidden1' },
1242
+ { public: 'item2', __scalar_private: 'hidden2' },
1243
1243
  ],
1244
1244
  }
1245
1245
 
1246
1246
  const result = createMagicProxy(input, { showInternal: true })
1247
1247
 
1248
1248
  expect(result.items[0].public).toBe('item1')
1249
- expect(result.items[0]._private).toBe('hidden1')
1249
+ expect(result.items[0].__scalar_private).toBe('hidden1')
1250
1250
  expect(result.items[1].public).toBe('item2')
1251
- expect(result.items[1]._private).toBe('hidden2')
1251
+ expect(result.items[1].__scalar_private).toBe('hidden2')
1252
1252
  })
1253
1253
 
1254
- it('should show underscore ref properties', () => {
1254
+ it('should show __scalar_ ref properties', () => {
1255
1255
  const input = {
1256
1256
  definitions: {
1257
1257
  example: {
1258
1258
  value: 'hello',
1259
- _internal: 'hidden',
1259
+ __scalar_internal: 'hidden',
1260
1260
  },
1261
1261
  },
1262
- _hiddenRef: { $ref: '#/definitions/example' },
1262
+ __scalar_hiddenRef: { $ref: '#/definitions/example' },
1263
1263
  publicRef: { $ref: '#/definitions/example' },
1264
1264
  }
1265
1265
 
1266
1266
  const result = createMagicProxy(input, { showInternal: true })
1267
1267
 
1268
- // Underscore property should be hidden
1269
- expect(result._hiddenRef).toEqual({
1268
+ // __scalar_ property should be hidden
1269
+ expect(result.__scalar_hiddenRef).toEqual({
1270
1270
  '$ref': '#/definitions/example',
1271
1271
  '$ref-value': {
1272
- '_internal': 'hidden',
1272
+ '__scalar_internal': 'hidden',
1273
1273
  'value': 'hello',
1274
1274
  },
1275
1275
  })
@@ -1277,8 +1277,8 @@ describe('createMagicProxy', () => {
1277
1277
  // Public ref should work normally
1278
1278
  expect(result.publicRef['$ref-value'].value).toBe('hello')
1279
1279
 
1280
- // Underscore properties in referenced objects should be hidden
1281
- expect(result.publicRef['$ref-value']._internal).toBe('hidden')
1280
+ // __scalar_ properties in referenced objects should be hidden
1281
+ expect(result.publicRef['$ref-value'].__scalar_internal).toBe('hidden')
1282
1282
  })
1283
1283
  })
1284
1284
 
@@ -1830,42 +1830,46 @@ describe('createMagicProxy', () => {
1830
1830
  })
1831
1831
  })
1832
1832
 
1833
- describe('hide underscore properties', () => {
1834
- it('should hide properties starting with underscore from direct access', () => {
1833
+ describe('hide __scalar_ properties', () => {
1834
+ it('should hide properties starting with __scalar_ from direct access', () => {
1835
1835
  const input = {
1836
1836
  public: 'visible',
1837
- _private: 'hidden',
1838
- __internal: 'also hidden',
1837
+ __scalar_private: 'hidden',
1838
+ __scalar_internal: 'also hidden',
1839
1839
  normal_underscore: 'visible with underscore in middle',
1840
+ _id: 'legitimate user property', // This should NOT be hidden
1841
+ _type: 'another legitimate property', // This should NOT be hidden
1840
1842
  }
1841
1843
 
1842
1844
  const result = createMagicProxy(input)
1843
1845
 
1844
1846
  expect(result.public).toBe('visible')
1845
- expect(result._private).toBe(undefined)
1846
- expect(result.__internal).toBe(undefined)
1847
+ expect(result.__scalar_private).toBe(undefined)
1848
+ expect(result.__scalar_internal).toBe(undefined)
1847
1849
  expect(result.normal_underscore).toBe('visible with underscore in middle')
1850
+ expect(result._id).toBe('legitimate user property') // Should be visible
1851
+ expect(result._type).toBe('another legitimate property') // Should be visible
1848
1852
  })
1849
1853
 
1850
- it('should hide underscore properties from "in" operator', () => {
1854
+ it('should hide __scalar_ properties from "in" operator', () => {
1851
1855
  const input = {
1852
1856
  public: 'visible',
1853
- _private: 'hidden',
1854
- __internal: 'also hidden',
1857
+ __scalar_private: 'hidden',
1858
+ __scalar_internal: 'also hidden',
1855
1859
  }
1856
1860
 
1857
1861
  const result = createMagicProxy(input)
1858
1862
 
1859
1863
  expect('public' in result).toBe(true)
1860
- expect('_private' in result).toBe(false)
1861
- expect('__internal' in result).toBe(false)
1864
+ expect('__scalar_private' in result).toBe(false)
1865
+ expect('__scalar_internal' in result).toBe(false)
1862
1866
  })
1863
1867
 
1864
- it('should exclude underscore properties from Object.keys enumeration', () => {
1868
+ it('should exclude __scalar_ properties from Object.keys enumeration', () => {
1865
1869
  const input = {
1866
1870
  public: 'visible',
1867
- _private: 'hidden',
1868
- __internal: 'also hidden',
1871
+ __scalar_private: 'hidden',
1872
+ __scalar_internal: 'also hidden',
1869
1873
  another: 'visible',
1870
1874
  }
1871
1875
 
@@ -1874,82 +1878,110 @@ describe('createMagicProxy', () => {
1874
1878
 
1875
1879
  expect(keys).toContain('public')
1876
1880
  expect(keys).toContain('another')
1877
- expect(keys).not.toContain('_private')
1878
- expect(keys).not.toContain('__internal')
1881
+ expect(keys).not.toContain('__scalar_private')
1882
+ expect(keys).not.toContain('__scalar_internal')
1879
1883
  })
1880
1884
 
1881
- it('should hide underscore properties from getOwnPropertyDescriptor', () => {
1885
+ it('should hide __scalar_ properties from getOwnPropertyDescriptor', () => {
1882
1886
  const input = {
1883
1887
  public: 'visible',
1884
- _private: 'hidden',
1888
+ __scalar_private: 'hidden',
1885
1889
  }
1886
1890
 
1887
1891
  const result = createMagicProxy(input)
1888
1892
 
1889
1893
  expect(Object.getOwnPropertyDescriptor(result, 'public')).toBeDefined()
1890
- expect(Object.getOwnPropertyDescriptor(result, '_private')).toBe(undefined)
1894
+ expect(Object.getOwnPropertyDescriptor(result, '__scalar_private')).toBe(undefined)
1891
1895
  })
1892
1896
 
1893
- it('should hide underscore properties in nested objects', () => {
1897
+ it('should hide __scalar_ properties in nested objects', () => {
1894
1898
  const input = {
1895
1899
  nested: {
1896
1900
  public: 'visible',
1897
- _private: 'hidden',
1901
+ __scalar_private: 'hidden',
1898
1902
  deeper: {
1899
- _alsoHidden: 'secret',
1903
+ __scalar_alsoHidden: 'secret',
1900
1904
  visible: 'shown',
1901
1905
  },
1902
1906
  },
1903
- _topLevel: 'hidden',
1907
+ __scalar_topLevel: 'hidden',
1904
1908
  }
1905
1909
 
1906
1910
  const result = createMagicProxy(input)
1907
1911
 
1908
- expect(result._topLevel).toBe(undefined)
1912
+ expect(result.__scalar_topLevel).toBe(undefined)
1909
1913
  expect(result.nested.public).toBe('visible')
1910
- expect(result.nested._private).toBe(undefined)
1911
- expect(result.nested.deeper._alsoHidden).toBe(undefined)
1914
+ expect(result.nested.__scalar_private).toBe(undefined)
1915
+ expect(result.nested.deeper.__scalar_alsoHidden).toBe(undefined)
1912
1916
  expect(result.nested.deeper.visible).toBe('shown')
1913
1917
  })
1914
1918
 
1915
- it('should work with arrays containing objects with underscore properties', () => {
1919
+ it('should work with arrays containing objects with __scalar_ properties', () => {
1916
1920
  const input = {
1917
1921
  items: [
1918
- { public: 'item1', _private: 'hidden1' },
1919
- { public: 'item2', _private: 'hidden2' },
1922
+ { public: 'item1', __scalar_private: 'hidden1' },
1923
+ { public: 'item2', __scalar_private: 'hidden2' },
1920
1924
  ],
1921
1925
  }
1922
1926
 
1923
1927
  const result = createMagicProxy(input)
1924
1928
 
1925
1929
  expect(result.items[0].public).toBe('item1')
1926
- expect(result.items[0]._private).toBe(undefined)
1930
+ expect(result.items[0].__scalar_private).toBe(undefined)
1927
1931
  expect(result.items[1].public).toBe('item2')
1928
- expect(result.items[1]._private).toBe(undefined)
1932
+ expect(result.items[1].__scalar_private).toBe(undefined)
1929
1933
  })
1930
1934
 
1931
- it('should still allow refs to work with underscore hiding', () => {
1935
+ it('should still allow refs to work with __scalar_ hiding', () => {
1932
1936
  const input = {
1933
1937
  definitions: {
1934
1938
  example: {
1935
1939
  value: 'hello',
1936
- _internal: 'hidden',
1940
+ __scalar_internal: 'hidden',
1937
1941
  },
1938
1942
  },
1939
- _hiddenRef: { $ref: '#/definitions/example' },
1943
+ __scalar_hiddenRef: { $ref: '#/definitions/example' },
1940
1944
  publicRef: { $ref: '#/definitions/example' },
1941
1945
  }
1942
1946
 
1943
1947
  const result = createMagicProxy(input)
1944
1948
 
1945
- // Underscore property should be hidden
1946
- expect(result._hiddenRef).toBe(undefined)
1949
+ // __scalar_ property should be hidden
1950
+ expect(result.__scalar_hiddenRef).toBe(undefined)
1947
1951
 
1948
1952
  // Public ref should work normally
1949
1953
  expect(result.publicRef['$ref-value'].value).toBe('hello')
1950
1954
 
1951
- // Underscore properties in referenced objects should be hidden
1952
- expect(result.publicRef['$ref-value']._internal).toBe(undefined)
1955
+ // __scalar_ properties in referenced objects should be hidden
1956
+ expect(result.publicRef['$ref-value'].__scalar_internal).toBe(undefined)
1957
+ })
1958
+
1959
+ it('preserves regular underscore properties', () => {
1960
+ const input = {
1961
+ _id: 'user123',
1962
+ user_name: 'john',
1963
+ __version: '1.0',
1964
+ normal_property: 'visible',
1965
+ }
1966
+
1967
+ const result = createMagicProxy(input)
1968
+
1969
+ // Regular underscore properties should be visible
1970
+ expect(result._id).toBe('user123')
1971
+ expect(result.user_name).toBe('john')
1972
+ expect(result.__version).toBe('1.0')
1973
+ expect(result.normal_property).toBe('visible')
1974
+
1975
+ // Should be included in enumeration and "in" checks
1976
+ expect('_id' in result).toBe(true)
1977
+ expect('user_name' in result).toBe(true)
1978
+ expect('__version' in result).toBe(true)
1979
+
1980
+ const keys = Object.keys(result)
1981
+ expect(keys).toContain('_id')
1982
+ expect(keys).toContain('user_name')
1983
+ expect(keys).toContain('__version')
1984
+ expect(keys).toContain('normal_property')
1953
1985
  })
1954
1986
  })
1955
1987
  })
@@ -18,7 +18,7 @@ const REF_KEY = '$ref'
18
18
  * Features:
19
19
  * - If an object contains a `$ref` property, accessing the special `$ref-value` property will resolve and return the referenced value from the root object.
20
20
  * - All nested objects and arrays are recursively wrapped in proxies, so reference resolution works at any depth.
21
- * - Properties starting with an underscore (_) are considered internal and are hidden by default: they return undefined on access, are excluded from enumeration, and `'in'` checks return false. This can be overridden with the `showInternal` option.
21
+ * - Properties starting with `__scalar_` are considered internal and are hidden by default: they return undefined on access, are excluded from enumeration, and `'in'` checks return false. This can be overridden with the `showInternal` option.
22
22
  * - Setting, deleting, and enumerating properties works as expected, including for proxied references.
23
23
  * - Ensures referential stability by caching proxies for the same target object.
24
24
  *
@@ -33,17 +33,17 @@ const REF_KEY = '$ref'
33
33
  * foo: { bar: 123 }
34
34
  * },
35
35
  * refObj: { $ref: '#/definitions/foo' },
36
- * _internal: 'hidden property'
36
+ * __scalar_internal: 'hidden property'
37
37
  * }
38
38
  * const proxy = createMagicProxy(input)
39
39
  *
40
40
  * // Accessing proxy.refObj['$ref-value'] will resolve to { bar: 123 }
41
41
  * console.log(proxy.refObj['$ref-value']) // { bar: 123 }
42
42
  *
43
- * // Properties starting with underscore are hidden
44
- * console.log(proxy._internal) // undefined
45
- * console.log('_internal' in proxy) // false
46
- * console.log(Object.keys(proxy)) // ['definitions', 'refObj'] (no '_internal')
43
+ * // Properties starting with __scalar_ are hidden
44
+ * console.log(proxy.__scalar_internal) // undefined
45
+ * console.log('__scalar_internal' in proxy) // false
46
+ * console.log(Object.keys(proxy)) // ['definitions', 'refObj'] (no '__scalar_internal')
47
47
  *
48
48
  * // Setting and deleting properties works as expected
49
49
  * proxy.refObj.extra = 'hello'
@@ -99,7 +99,7 @@ export const createMagicProxy = <T extends Record<keyof T & symbol, unknown>, S
99
99
  * Proxy "get" trap for magic proxy.
100
100
  * - If accessing the special isMagicProxy symbol, return true to identify proxy.
101
101
  * - If accessing the magicProxyTarget symbol, return the original target object.
102
- * - Hide properties starting with underscore by returning undefined.
102
+ * - Hide properties starting with __scalar_ by returning undefined.
103
103
  * - If accessing "$ref-value" and the object has a local $ref, resolve and return the referenced value as a new magic proxy.
104
104
  * - For all other properties, recursively wrap the returned value in a magic proxy (if applicable).
105
105
  */
@@ -114,9 +114,9 @@ export const createMagicProxy = <T extends Record<keyof T & symbol, unknown>, S
114
114
  return target
115
115
  }
116
116
 
117
- // Hide properties starting with underscore - these are considered internal/private properties
117
+ // Hide properties starting with __scalar_ - these are considered internal/private properties
118
118
  // and should not be accessible through the magic proxy interface
119
- if (typeof prop === 'string' && prop.startsWith('_') && !options?.showInternal) {
119
+ if (typeof prop === 'string' && prop.startsWith('__scalar_') && !options?.showInternal) {
120
120
  return undefined
121
121
  }
122
122
 
@@ -159,13 +159,13 @@ export const createMagicProxy = <T extends Record<keyof T & symbol, unknown>, S
159
159
  * Allows setting properties on the proxied object.
160
160
  * This will update the underlying target object.
161
161
  *
162
- * Note: it will not update if the property starts with an underscore (_)
162
+ * Note: it will not update if the property starts with __scalar_
163
163
  * Those will be considered private properties by the proxy
164
164
  */
165
165
  set(target, prop, newValue, receiver) {
166
166
  const ref = Reflect.get(target, REF_KEY, receiver)
167
167
 
168
- if (typeof prop === 'string' && prop.startsWith('_') && !options?.showInternal) {
168
+ if (typeof prop === 'string' && prop.startsWith('__scalar_') && !options?.showInternal) {
169
169
  return true
170
170
  }
171
171
 
@@ -215,12 +215,12 @@ export const createMagicProxy = <T extends Record<keyof T & symbol, unknown>, S
215
215
  * - Pretend that "$ref-value" exists if "$ref" exists on the target.
216
216
  * This allows expressions like `"$ref-value" in obj` to return true for objects with a $ref,
217
217
  * even though "$ref-value" is a virtual property provided by the proxy.
218
- * - Hide properties starting with underscore by returning false.
218
+ * - Hide properties starting with __scalar_ by returning false.
219
219
  * - For all other properties, defer to the default Reflect.has behavior.
220
220
  */
221
221
  has(target, prop) {
222
- // Hide properties starting with underscore
223
- if (typeof prop === 'string' && prop.startsWith('_') && !options?.showInternal) {
222
+ // Hide properties starting with __scalar_
223
+ if (typeof prop === 'string' && prop.startsWith('__scalar_') && !options?.showInternal) {
224
224
  return false
225
225
  }
226
226
 
@@ -236,14 +236,14 @@ export const createMagicProxy = <T extends Record<keyof T & symbol, unknown>, S
236
236
  * - If the object has a "$ref" property, ensures that "$ref-value" is also included in the keys,
237
237
  * even though "$ref-value" is a virtual property provided by the proxy.
238
238
  * This allows Object.keys, Reflect.ownKeys, etc. to include "$ref-value" for objects with $ref.
239
- * - Filters out properties starting with underscore.
239
+ * - Filters out properties starting with __scalar_.
240
240
  */
241
241
  ownKeys(target) {
242
242
  const keys = Reflect.ownKeys(target)
243
243
 
244
- // Filter out properties starting with underscore
244
+ // Filter out properties starting with __scalar_
245
245
  const filteredKeys = keys.filter(
246
- (key) => typeof key !== 'string' || !(key.startsWith('_') && !options?.showInternal),
246
+ (key) => typeof key !== 'string' || !(key.startsWith('__scalar_') && !options?.showInternal),
247
247
  )
248
248
 
249
249
  if (REF_KEY in target && !filteredKeys.includes(REF_VALUE)) {
@@ -255,13 +255,13 @@ export const createMagicProxy = <T extends Record<keyof T & symbol, unknown>, S
255
255
  /**
256
256
  * Proxy "getOwnPropertyDescriptor" trap for magic proxy.
257
257
  * - For the virtual "$ref-value" property, returns a descriptor that makes it appear as a regular property.
258
- * - Hide properties starting with underscore by returning undefined.
258
+ * - Hide properties starting with __scalar_ by returning undefined.
259
259
  * - For all other properties, delegates to the default Reflect.getOwnPropertyDescriptor behavior.
260
260
  * - This ensures that Object.getOwnPropertyDescriptor and similar methods work correctly with the virtual property.
261
261
  */
262
262
  getOwnPropertyDescriptor(target, prop) {
263
- // Hide properties starting with underscore
264
- if (typeof prop === 'string' && prop.startsWith('_') && !options?.showInternal) {
263
+ // Hide properties starting with __scalar_
264
+ if (typeof prop === 'string' && prop.startsWith('__scalar_') && !options?.showInternal) {
265
265
  return undefined
266
266
  }
267
267