object.ts 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js"
  2. import { isBlockedMember } from "../tool-runtime.js"
  3. import { isSandboxValue, SandboxMap, SandboxURLSearchParams } from "../values.js"
  4. import { boundedData, coerceToString } from "./value.js"
  5. export const objectStatics = new Set(["keys", "values", "entries", "hasOwn", "assign", "fromEntries"])
  6. export const invokeObjectMethod = (name: string, args: Array<unknown>, node: AstNode): unknown => {
  7. if (!objectStatics.has(name)) throw new InterpreterRuntimeError(`Object.${name} is not available in CodeMode.`, node)
  8. const requireObject = (): Record<string, unknown> => {
  9. const value = boundedData(args[0], `Object.${name} input`)
  10. if (isSandboxValue(value)) return {}
  11. if (value === null || typeof value !== "object" || Array.isArray(value)) {
  12. throw new InterpreterRuntimeError(`Object.${name} expects a data object.`, node)
  13. }
  14. return value as Record<string, unknown>
  15. }
  16. const guardedSet = (out: Record<string, unknown>, key: string, item: unknown): void => {
  17. if (isBlockedMember(key)) throw new InterpreterRuntimeError(`Property '${key}' is not available in CodeMode.`, node)
  18. out[key] = item
  19. }
  20. switch (name) {
  21. case "keys": {
  22. const value = boundedData(args[0], "Object.keys input")
  23. if (isSandboxValue(value)) return []
  24. if (Array.isArray(value)) return Object.keys(value)
  25. if (value === null || typeof value !== "object") {
  26. throw new InterpreterRuntimeError("Object.keys expects a data object or array.", node)
  27. }
  28. return Object.keys(value)
  29. }
  30. case "values":
  31. return Object.values(requireObject())
  32. case "entries":
  33. return Object.entries(requireObject()).map(([key, item]) => [key, item])
  34. case "hasOwn":
  35. return Object.hasOwn(requireObject(), String(args[1]))
  36. case "assign": {
  37. const out: Record<string, unknown> = Object.create(null)
  38. for (const source of args) {
  39. if (source === null || source === undefined) continue
  40. const value = boundedData(source, "Object.assign input")
  41. if (isSandboxValue(value)) continue
  42. if (value === null || typeof value !== "object" || Array.isArray(value)) {
  43. throw new InterpreterRuntimeError("Object.assign expects data objects.", node)
  44. }
  45. for (const [key, item] of Object.entries(value)) guardedSet(out, key, item)
  46. }
  47. return out
  48. }
  49. case "fromEntries": {
  50. if (args[0] instanceof SandboxMap) {
  51. const out: Record<string, unknown> = Object.create(null)
  52. for (const [key, item] of args[0].map.entries()) guardedSet(out, coerceToString(key), item)
  53. return out
  54. }
  55. if (args[0] instanceof SandboxURLSearchParams) {
  56. const out: Record<string, unknown> = Object.create(null)
  57. for (const [key, value] of args[0].params.entries()) guardedSet(out, key, value)
  58. return out
  59. }
  60. const pairs = boundedData(args[0], "Object.fromEntries input")
  61. if (!Array.isArray(pairs)) {
  62. throw new InterpreterRuntimeError("Object.fromEntries expects an array of [key, value] pairs.", node)
  63. }
  64. const out: Record<string, unknown> = Object.create(null)
  65. for (const pair of pairs) {
  66. if (!Array.isArray(pair)) {
  67. throw new InterpreterRuntimeError("Object.fromEntries expects [key, value] pairs.", node)
  68. }
  69. guardedSet(out, String(pair[0]), pair[1])
  70. }
  71. return out
  72. }
  73. }
  74. throw new InterpreterRuntimeError(`Object.${name} is not available in CodeMode.`, node)
  75. }