@scalar/json-magic 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/.turbo/turbo-build.log +4 -3
  2. package/CHANGELOG.md +22 -0
  3. package/README.md +21 -3
  4. package/dist/bundle/bundle.d.ts +84 -14
  5. package/dist/bundle/bundle.d.ts.map +1 -1
  6. package/dist/bundle/bundle.js +56 -15
  7. package/dist/bundle/bundle.js.map +3 -3
  8. package/dist/bundle/index.d.ts +2 -1
  9. package/dist/bundle/index.d.ts.map +1 -1
  10. package/dist/bundle/index.js.map +2 -2
  11. package/dist/bundle/plugins/fetch-urls/index.d.ts +2 -2
  12. package/dist/bundle/plugins/fetch-urls/index.d.ts.map +1 -1
  13. package/dist/bundle/plugins/fetch-urls/index.js +1 -0
  14. package/dist/bundle/plugins/fetch-urls/index.js.map +2 -2
  15. package/dist/bundle/plugins/parse-json/index.d.ts +2 -2
  16. package/dist/bundle/plugins/parse-json/index.d.ts.map +1 -1
  17. package/dist/bundle/plugins/parse-json/index.js +1 -0
  18. package/dist/bundle/plugins/parse-json/index.js.map +2 -2
  19. package/dist/bundle/plugins/parse-yaml/index.d.ts +2 -2
  20. package/dist/bundle/plugins/parse-yaml/index.d.ts.map +1 -1
  21. package/dist/bundle/plugins/parse-yaml/index.js +1 -0
  22. package/dist/bundle/plugins/parse-yaml/index.js.map +2 -2
  23. package/dist/bundle/plugins/read-files/index.d.ts +2 -2
  24. package/dist/bundle/plugins/read-files/index.d.ts.map +1 -1
  25. package/dist/bundle/plugins/read-files/index.js +1 -0
  26. package/dist/bundle/plugins/read-files/index.js.map +2 -2
  27. package/dist/diff/apply.d.ts +1 -1
  28. package/dist/diff/apply.d.ts.map +1 -1
  29. package/dist/diff/apply.js.map +2 -2
  30. package/dist/diff/diff.d.ts +2 -2
  31. package/dist/diff/diff.d.ts.map +1 -1
  32. package/dist/diff/diff.js.map +2 -2
  33. package/dist/diff/merge.d.ts +3 -3
  34. package/dist/diff/merge.d.ts.map +1 -1
  35. package/dist/diff/merge.js.map +2 -2
  36. package/dist/magic-proxy/proxy.d.ts +23 -42
  37. package/dist/magic-proxy/proxy.d.ts.map +1 -1
  38. package/dist/magic-proxy/proxy.js +103 -80
  39. package/dist/magic-proxy/proxy.js.map +3 -3
  40. package/dist/utils/is-object.d.ts +1 -1
  41. package/dist/utils/is-object.d.ts.map +1 -1
  42. package/dist/utils/is-object.js.map +2 -2
  43. package/package.json +11 -10
  44. package/src/bundle/bundle.test.ts +594 -44
  45. package/src/bundle/bundle.ts +167 -32
  46. package/src/bundle/index.ts +2 -1
  47. package/src/bundle/plugins/fetch-urls/index.ts +3 -2
  48. package/src/bundle/plugins/parse-json/index.ts +3 -2
  49. package/src/bundle/plugins/parse-yaml/index.ts +3 -2
  50. package/src/bundle/plugins/read-files/index.ts +4 -2
  51. package/src/dereference/dereference.test.ts +26 -18
  52. package/src/diff/apply.ts +8 -3
  53. package/src/diff/diff.ts +3 -3
  54. package/src/diff/merge.ts +6 -6
  55. package/src/magic-proxy/proxy.test.ts +1095 -100
  56. package/src/magic-proxy/proxy.ts +150 -171
  57. package/src/utils/is-object.ts +1 -1
@@ -1,107 +1,130 @@
1
- import { isReactive, toRaw } from "vue";
2
1
  import { isLocalRef } from "../bundle/bundle.js";
2
+ import { getSegmentsFromPath } from "../utils/get-segments-from-path.js";
3
3
  import { isObject } from "../utils/is-object.js";
4
4
  import { getValueByPath, parseJsonPointer } from "../utils/json-path-utils.js";
5
5
  const isMagicProxy = Symbol("isMagicProxy");
6
- function createProxyHandler(sourceDocument, resolvedProxyCache) {
7
- return {
8
- get(target, property, receiver) {
9
- if (property === TARGET_SYMBOL) {
10
- return target;
11
- }
12
- if (property === isMagicProxy) {
6
+ const magicProxyTarget = Symbol("magicProxyTarget");
7
+ const REF_VALUE = "$ref-value";
8
+ const REF_KEY = "$ref";
9
+ const createMagicProxy = (target, root = target, cache = /* @__PURE__ */ new Map()) => {
10
+ if (!isObject(target) && !Array.isArray(target)) {
11
+ return target;
12
+ }
13
+ const handler = {
14
+ /**
15
+ * Proxy "get" trap for magic proxy.
16
+ * - If accessing the special isMagicProxy symbol, return true to identify proxy.
17
+ * - If accessing the magicProxyTarget symbol, return the original target object.
18
+ * - If accessing "$ref-value" and the object has a local $ref, resolve and return the referenced value as a new magic proxy.
19
+ * - For all other properties, recursively wrap the returned value in a magic proxy (if applicable).
20
+ */
21
+ get(target2, prop, receiver) {
22
+ if (prop === isMagicProxy) {
13
23
  return true;
14
24
  }
15
- const value = Reflect.get(target, property, receiver);
16
- const deepResolveNestedRefs = (value2, originalRef) => {
17
- if (!isObject(value2) && !Array.isArray(value2)) {
18
- return value2;
19
- }
20
- if (typeof value2 === "object" && "$ref" in value2) {
21
- const ref = value2.$ref;
22
- if (isLocalRef(ref)) {
23
- const referencePath = parseJsonPointer(ref);
24
- const resolvedValue = getValueByPath(sourceDocument, referencePath);
25
- return deepResolveNestedRefs(resolvedValue, originalRef ?? ref);
26
- }
27
- }
28
- if (originalRef && typeof value2 === "object") {
29
- return createMagicProxy({ ...value2, "x-original-ref": originalRef }, sourceDocument, resolvedProxyCache);
25
+ if (prop === magicProxyTarget) {
26
+ return target2;
27
+ }
28
+ const ref = Reflect.get(target2, REF_KEY, receiver);
29
+ if (prop === REF_VALUE && typeof ref === "string" && isLocalRef(ref)) {
30
+ if (cache.has(ref)) {
31
+ return cache.get(ref);
30
32
  }
31
- return createMagicProxy(value2, sourceDocument, resolvedProxyCache);
32
- };
33
- return deepResolveNestedRefs(value);
33
+ const resolvedValue = getValueByPath(root, parseJsonPointer(ref));
34
+ const proxiedValue = createMagicProxy(resolvedValue, root, cache);
35
+ cache.set(ref, proxiedValue);
36
+ return proxiedValue;
37
+ }
38
+ const value = Reflect.get(target2, prop, receiver);
39
+ return createMagicProxy(value, root, cache);
34
40
  },
35
- set(target, property, newValue, receiver) {
36
- const rawTarget = isReactive(target) ? toRaw(target) : target;
37
- const currentValue = rawTarget[property];
38
- if (typeof currentValue === "object" && isObject(currentValue) && "$ref" in currentValue && typeof currentValue.$ref === "string" && isLocalRef(currentValue.$ref)) {
39
- const referencePath = parseJsonPointer(currentValue.$ref);
40
- const targetObject = getValueByPath(sourceDocument, referencePath.slice(0, -1));
41
- const lastPathSegment = referencePath[referencePath.length - 1];
42
- if (targetObject && lastPathSegment) {
43
- targetObject[lastPathSegment] = newValue;
41
+ /**
42
+ * Proxy "set" trap for magic proxy.
43
+ * Allows setting properties on the proxied object.
44
+ * This will update the underlying target object.
45
+ */
46
+ set(target2, prop, newValue, receiver) {
47
+ const ref = Reflect.get(target2, REF_KEY, receiver);
48
+ if (prop === REF_VALUE && typeof ref === "string" && isLocalRef(ref)) {
49
+ const segments = getSegmentsFromPath(ref);
50
+ if (segments.length === 0) {
51
+ return false;
52
+ }
53
+ const parentNode = getValueByPath(root, segments.slice(0, -1));
54
+ if (!parentNode || !isObject(parentNode) && !Array.isArray(parentNode)) {
55
+ return false;
44
56
  }
45
- } else {
46
- Reflect.set(rawTarget, property, newValue, receiver);
57
+ parentNode[segments.at(-1)] = newValue;
58
+ return true;
47
59
  }
48
- return true;
60
+ return Reflect.set(target2, prop, newValue, receiver);
49
61
  },
50
- has(target, key) {
51
- if (typeof key === "string" && key !== "$ref" && typeof target.$ref === "string" && isLocalRef(target.$ref)) {
52
- const referencePath = parseJsonPointer(target["$ref"]);
53
- const resolvedValue = getValueByPath(sourceDocument, referencePath);
54
- return resolvedValue ? key in resolvedValue : false;
62
+ /**
63
+ * Proxy "deleteProperty" trap for magic proxy.
64
+ * Allows deleting properties from the proxied object.
65
+ * This will update the underlying target object.
66
+ */
67
+ deleteProperty(target2, prop) {
68
+ return Reflect.deleteProperty(target2, prop);
69
+ },
70
+ /**
71
+ * Proxy "has" trap for magic proxy.
72
+ * - Pretend that "$ref-value" exists if "$ref" exists on the target.
73
+ * This allows expressions like `"$ref-value" in obj` to return true for objects with a $ref,
74
+ * even though "$ref-value" is a virtual property provided by the proxy.
75
+ * - For all other properties, defer to the default Reflect.has behavior.
76
+ */
77
+ has(target2, prop) {
78
+ if (prop === REF_VALUE && REF_KEY in target2) {
79
+ return true;
55
80
  }
56
- return key in target;
81
+ return Reflect.has(target2, prop);
57
82
  },
58
- ownKeys(target) {
59
- if ("$ref" in target && typeof target.$ref === "string" && isLocalRef(target.$ref)) {
60
- const referencePath = parseJsonPointer(target["$ref"]);
61
- const resolvedValue = getValueByPath(sourceDocument, referencePath);
62
- return resolvedValue ? Reflect.ownKeys(resolvedValue) : [];
83
+ /**
84
+ * Proxy "ownKeys" trap for magic proxy.
85
+ * - Returns the list of own property keys for the proxied object.
86
+ * - If the object has a "$ref" property, ensures that "$ref-value" is also included in the keys,
87
+ * even though "$ref-value" is a virtual property provided by the proxy.
88
+ * This allows Object.keys, Reflect.ownKeys, etc. to include "$ref-value" for objects with $ref.
89
+ */
90
+ ownKeys(target2) {
91
+ const keys = Reflect.ownKeys(target2);
92
+ if (REF_KEY in target2 && !keys.includes(REF_VALUE)) {
93
+ keys.push(REF_VALUE);
63
94
  }
64
- return Reflect.ownKeys(target);
95
+ return keys;
65
96
  },
66
- getOwnPropertyDescriptor(target, key) {
67
- if ("$ref" in target && key !== "$ref" && typeof target.$ref === "string" && isLocalRef(target.$ref)) {
68
- const referencePath = parseJsonPointer(target["$ref"]);
69
- const resolvedValue = getValueByPath(sourceDocument, referencePath);
70
- if (resolvedValue) {
71
- return Object.getOwnPropertyDescriptor(resolvedValue, key);
72
- }
97
+ /**
98
+ * Proxy "getOwnPropertyDescriptor" trap for magic proxy.
99
+ * - For the virtual "$ref-value" property, returns a descriptor that makes it appear as a regular property.
100
+ * - For all other properties, delegates to the default Reflect.getOwnPropertyDescriptor behavior.
101
+ * - This ensures that Object.getOwnPropertyDescriptor and similar methods work correctly with the virtual property.
102
+ */
103
+ getOwnPropertyDescriptor(target2, prop) {
104
+ const ref = Reflect.get(target2, REF_KEY);
105
+ if (prop === REF_VALUE && typeof ref === "string") {
106
+ return {
107
+ configurable: true,
108
+ enumerable: true,
109
+ value: void 0,
110
+ writable: false
111
+ };
73
112
  }
74
- return Object.getOwnPropertyDescriptor(target, key);
113
+ return Reflect.getOwnPropertyDescriptor(target2, prop);
75
114
  }
76
115
  };
77
- }
78
- function createMagicProxy(targetObject, sourceDocument = targetObject, resolvedProxyCache) {
79
- if (!isObject(targetObject) && !Array.isArray(targetObject)) {
80
- return targetObject;
81
- }
82
- const rawTarget = isReactive(targetObject) ? toRaw(targetObject) : targetObject;
83
- if (resolvedProxyCache?.has(rawTarget)) {
84
- const cachedValue = resolvedProxyCache.get(rawTarget);
85
- if (cachedValue) {
86
- return cachedValue;
87
- }
88
- }
89
- const handler = createProxyHandler(sourceDocument, resolvedProxyCache);
90
- const proxy = new Proxy(rawTarget, handler);
91
- if (resolvedProxyCache) {
92
- resolvedProxyCache.set(rawTarget, proxy);
93
- }
94
- return proxy;
95
- }
96
- const TARGET_SYMBOL = Symbol("magicProxyTarget");
116
+ return new Proxy(target, handler);
117
+ };
97
118
  function getRaw(obj) {
119
+ if (typeof obj !== "object" || obj === null) {
120
+ return obj;
121
+ }
98
122
  if (obj[isMagicProxy]) {
99
- return obj[TARGET_SYMBOL];
123
+ return obj[magicProxyTarget];
100
124
  }
101
125
  return obj;
102
126
  }
103
127
  export {
104
- TARGET_SYMBOL,
105
128
  createMagicProxy,
106
129
  getRaw
107
130
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/magic-proxy/proxy.ts"],
4
- "sourcesContent": ["import { isReactive, toRaw } from 'vue'\nimport { isLocalRef } from '@/bundle/bundle'\nimport type { UnknownObject } from '@/types'\nimport { isObject } from '@/utils/is-object'\nimport { getValueByPath, parseJsonPointer } from '@/utils/json-path-utils'\n\nconst isMagicProxy = Symbol('isMagicProxy')\n\n/**\n * Creates a proxy handler that automatically resolves JSON references ($ref) in an object.\n * The handler intercepts property access, assignment, and property enumeration to automatically\n * resolve any $ref references to their target values in the source document.\n *\n * @param sourceDocument - The source document containing the reference targets\n * @param resolvedProxyCache - Optional cache to store resolved proxies and prevent duplicate proxies\n * @returns A proxy handler that automatically resolves $ref references\n */\nfunction createProxyHandler(\n sourceDocument: UnknownObject | UnknownObject[],\n resolvedProxyCache?: WeakMap<object, UnknownObject | UnknownObject[]>,\n): ProxyHandler<UnknownObject | UnknownObject[]> {\n return {\n get(target, property, receiver) {\n if (property === TARGET_SYMBOL) {\n return target\n }\n\n if (property === isMagicProxy) {\n return true\n }\n\n const value = Reflect.get(target, property, receiver)\n\n /**\n * Recursively resolves nested references in an object.\n * If the value is not an object, returns it as is.\n * If the value has a $ref property:\n * - For local references: resolves the reference and continues resolving nested refs\n * - For all other objects: creates a proxy for lazy resolution\n */\n const deepResolveNestedRefs = (value: unknown, originalRef?: string) => {\n if (!isObject(value) && !Array.isArray(value)) {\n return value\n }\n\n if (typeof value === 'object' && '$ref' in value) {\n const ref = value.$ref as string\n\n if (isLocalRef(ref)) {\n const referencePath = parseJsonPointer(ref)\n const resolvedValue = getValueByPath(sourceDocument, referencePath)\n\n // preserve the first $ref to maintain the original reference\n return deepResolveNestedRefs(resolvedValue, originalRef ?? ref)\n }\n }\n\n if (originalRef && typeof value === 'object') {\n return createMagicProxy({ ...value, 'x-original-ref': originalRef }, sourceDocument, resolvedProxyCache)\n }\n\n return createMagicProxy(value as UnknownObject, sourceDocument, resolvedProxyCache)\n }\n\n return deepResolveNestedRefs(value)\n },\n\n set(target: UnknownObject, property: string, newValue: unknown, receiver: UnknownObject) {\n const rawTarget = isReactive(target) ? toRaw(target) : target\n const currentValue = rawTarget[property]\n\n if (\n typeof currentValue === 'object' &&\n isObject(currentValue) &&\n '$ref' in currentValue &&\n typeof currentValue.$ref === 'string' &&\n isLocalRef(currentValue.$ref)\n ) {\n const referencePath = parseJsonPointer(currentValue.$ref)\n const targetObject = getValueByPath(sourceDocument, referencePath.slice(0, -1)) as UnknownObject\n const lastPathSegment = referencePath[referencePath.length - 1]\n\n if (targetObject && lastPathSegment) {\n targetObject[lastPathSegment] = newValue\n }\n } else {\n Reflect.set(rawTarget, property, newValue, receiver)\n }\n return true\n },\n\n has(target: UnknownObject, key: string) {\n if (typeof key === 'string' && key !== '$ref' && typeof target.$ref === 'string' && isLocalRef(target.$ref)) {\n const referencePath = parseJsonPointer(target['$ref'])\n const resolvedValue = getValueByPath(sourceDocument, referencePath) as UnknownObject\n\n return resolvedValue ? key in resolvedValue : false\n }\n\n return key in target\n },\n\n ownKeys(target: UnknownObject) {\n if ('$ref' in target && typeof target.$ref === 'string' && isLocalRef(target.$ref)) {\n const referencePath = parseJsonPointer(target['$ref'])\n const resolvedValue = getValueByPath<UnknownObject>(sourceDocument, referencePath)\n\n return resolvedValue ? Reflect.ownKeys(resolvedValue) : []\n }\n\n return Reflect.ownKeys(target)\n },\n\n getOwnPropertyDescriptor(target: UnknownObject, key: string) {\n if ('$ref' in target && key !== '$ref' && typeof target.$ref === 'string' && isLocalRef(target.$ref)) {\n const referencePath = parseJsonPointer(target['$ref'])\n const resolvedValue = getValueByPath(sourceDocument, referencePath)\n\n if (resolvedValue) {\n return Object.getOwnPropertyDescriptor(resolvedValue, key)\n }\n }\n\n return Object.getOwnPropertyDescriptor(target, key)\n },\n }\n}\n\n/**\n * Creates a proxy that automatically resolves JSON references ($ref) in an object.\n * The proxy intercepts property access and automatically resolves any $ref references\n * to their target values in the source document.\n *\n * @param targetObject - The object to create a proxy for\n * @param sourceDocument - The source document containing the reference targets (defaults to targetObject)\n * @param resolvedProxyCache - Optional cache to store resolved proxies and prevent duplicate proxies\n * @returns A proxy that automatically resolves $ref references\n *\n * @example\n * // Basic usage with local references\n * const doc = {\n * components: {\n * schemas: {\n * User: { type: 'object', properties: { name: { type: 'string' } } }\n * }\n * },\n * paths: {\n * '/users': {\n * get: {\n * responses: {\n * 200: {\n * content: {\n * 'application/json': {\n * schema: { $ref: '#/components/schemas/User' }\n * }\n * }\n * }\n * }\n * }\n * }\n * }\n * }\n *\n * const proxy = createMagicProxy(doc)\n * // Accessing the schema will automatically resolve the $ref\n * console.log(proxy.paths['/users'].get.responses[200].content['application/json'].schema)\n * // Output: { type: 'object', properties: { name: { type: 'string' } } }\n *\n * @example\n * // Using with a cache to prevent duplicate proxies\n * const cache = new WeakMap()\n * const proxy1 = createMagicProxy(doc, doc, cache)\n * const proxy2 = createMagicProxy(doc, doc, cache)\n * // proxy1 and proxy2 are the same instance due to caching\n * console.log(proxy1 === proxy2) // true\n */\nexport function createMagicProxy<T extends UnknownObject | UnknownObject[]>(\n targetObject: T,\n sourceDocument: T = targetObject,\n resolvedProxyCache?: WeakMap<object, T>,\n): T {\n if (!isObject(targetObject) && !Array.isArray(targetObject)) {\n return targetObject\n }\n\n const rawTarget = isReactive(targetObject) ? toRaw(targetObject) : targetObject\n\n // check for cached results\n if (resolvedProxyCache?.has(rawTarget)) {\n const cachedValue = resolvedProxyCache.get(rawTarget)\n\n if (cachedValue) {\n return cachedValue\n }\n }\n\n // Create a handler with the correct context\n const handler = createProxyHandler(sourceDocument, resolvedProxyCache)\n const proxy = new Proxy<T>(rawTarget, handler)\n\n if (resolvedProxyCache) {\n resolvedProxyCache.set(rawTarget, proxy)\n }\n\n return proxy\n}\n\nexport const TARGET_SYMBOL = Symbol('magicProxyTarget')\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 extends UnknownObject>(obj: T): T {\n if ((obj as T & { [isMagicProxy]: boolean | undefined })[isMagicProxy]) {\n return (obj as T & { [TARGET_SYMBOL]: T })[TARGET_SYMBOL]\n }\n\n return obj\n}\n"],
5
- "mappings": "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",
6
- "names": ["value"]
4
+ "sourcesContent": ["import { isLocalRef } from '@/bundle/bundle'\nimport type { UnknownObject } from '@/types'\nimport { getSegmentsFromPath } from '@/utils/get-segments-from-path'\nimport { isObject } from '@/utils/is-object'\nimport { getValueByPath, parseJsonPointer } from '@/utils/json-path-utils'\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 * - If an object contains a `$ref` property, accessing the special `$ref-value` property\n * 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\n * works at any depth.\n * - Setting, deleting, and enumerating properties works as expected, including for proxied references.\n *\n * @param target - The object or array to wrap in a magic proxy\n * @param root - The root object for resolving local JSON references (defaults to target)\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 * }\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 * // 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 root: S | T = target,\n cache = new Map<string, unknown>(),\n) => {\n if (!isObject(target) && !Array.isArray(target)) {\n return 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 * - 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 const ref = Reflect.get(target, REF_KEY, receiver)\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' && isLocalRef(ref)) {\n // Check cache first for performance optimization\n if (cache.has(ref)) {\n return cache.get(ref)\n }\n\n // Resolve the reference and create a new magic proxy\n const resolvedValue = getValueByPath(root, parseJsonPointer(ref))\n const proxiedValue = createMagicProxy(resolvedValue, root, cache)\n\n // Store in cache for future lookups\n 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, root, cache)\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 set(target, prop, newValue, receiver) {\n const ref = Reflect.get(target, REF_KEY, receiver)\n\n if (prop === REF_VALUE && typeof ref === 'string' && isLocalRef(ref)) {\n const segments = getSegmentsFromPath(ref)\n\n if (segments.length === 0) {\n return false // Can not set top level $ref-value\n }\n\n const parentNode = getValueByPath(root, segments.slice(0, -1))\n\n // TODO: Maybe we create the path if it does not exist?\n // TODO: This can allow for invalid references to not throw errors\n if (!parentNode || (!isObject(parentNode) && !Array.isArray(parentNode))) {\n return false // Parent node does not exist, cannot set $ref-value\n }\n parentNode[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 * - For all other properties, defer to the default Reflect.has behavior.\n */\n has(target, prop) {\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 */\n ownKeys(target) {\n const keys = Reflect.ownKeys(target)\n if (REF_KEY in target && !keys.includes(REF_VALUE)) {\n keys.push(REF_VALUE)\n }\n return keys\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 * - 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 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 return new Proxy<T>(target, handler)\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
+ "names": ["target"]
7
7
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * Check if the given value is an object
3
3
  */
4
- export declare const isObject: (obj: any) => boolean;
4
+ export declare const isObject: (obj: any) => obj is object;
5
5
  //# sourceMappingURL=is-object.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"is-object.d.ts","sourceRoot":"","sources":["../../src/utils/is-object.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,QAAQ,QAAS,GAAG,YAAmE,CAAA"}
1
+ {"version":3,"file":"is-object.d.ts","sourceRoot":"","sources":["../../src/utils/is-object.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,QAAQ,QAAS,GAAG,KAAG,GAAG,IAAI,MAAwE,CAAA"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/is-object.ts"],
4
- "sourcesContent": ["/**\n * Check if the given value is an object\n */\nexport const isObject = (obj: any) => typeof obj === 'object' && !Array.isArray(obj) && obj !== null\n"],
5
- "mappings": "AAGO,MAAM,WAAW,CAAC,QAAa,OAAO,QAAQ,YAAY,CAAC,MAAM,QAAQ,GAAG,KAAK,QAAQ;",
4
+ "sourcesContent": ["/**\n * Check if the given value is an object\n */\nexport const isObject = (obj: any): obj is object => typeof obj === 'object' && !Array.isArray(obj) && obj !== null\n"],
5
+ "mappings": "AAGO,MAAM,WAAW,CAAC,QAA4B,OAAO,QAAQ,YAAY,CAAC,MAAM,QAAQ,GAAG,KAAK,QAAQ;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -10,20 +10,11 @@
10
10
  "url": "git+https://github.com/scalar/scalar.git",
11
11
  "directory": "packages/json-magic"
12
12
  },
13
- "version": "0.1.0",
13
+ "version": "0.3.0",
14
14
  "engines": {
15
15
  "node": ">=20"
16
16
  },
17
17
  "type": "module",
18
- "dependencies": {
19
- "yaml": "2.8.0",
20
- "vue": "^3.5.17"
21
- },
22
- "devDependencies": {
23
- "vite": "6.1.6",
24
- "fastify": "^5.3.3",
25
- "@scalar/build-tooling": "0.2.4"
26
- },
27
18
  "exports": {
28
19
  "./bundle": {
29
20
  "import": "./dist/bundle/index.js",
@@ -56,6 +47,16 @@
56
47
  "default": "./dist/magic-proxy/index.js"
57
48
  }
58
49
  },
50
+ "dependencies": {
51
+ "vue": "^3.5.17",
52
+ "yaml": "2.8.0",
53
+ "@scalar/helpers": "0.0.8"
54
+ },
55
+ "devDependencies": {
56
+ "fastify": "^5.3.3",
57
+ "vite": "6.1.6",
58
+ "@scalar/build-tooling": "0.2.6"
59
+ },
59
60
  "scripts": {
60
61
  "build": "scalar-build-esbuild",
61
62
  "test": "vitest test",