@opencode/codemode 2.0.15 → 2.0.16

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.
@@ -14,7 +14,7 @@ import { uriGlobal, urlGlobal, urlSearchParamsGlobal } from "../stdlib/url.js";
14
14
  import { headersGlobal } from "../stdlib/headers.js";
15
15
  import { iteratorGlobals } from "../stdlib/iterator.js";
16
16
  import { coercion } from "../stdlib/value.js";
17
- import { base64Global, cryptoGlobal } from "../stdlib/web.js";
17
+ import { base64Global, cryptoGlobal, structuredCloneGlobal } from "../stdlib/web.js";
18
18
  import { ToolReference } from "../tool-runtime.js";
19
19
  import { errorGlobal } from "./errors.js";
20
20
  import { errorTypes } from "./intrinsics.js";
@@ -85,6 +85,7 @@ const table = {
85
85
  atob: (ctx) => base64Global(ctx, "atob"),
86
86
  btoa: (ctx) => base64Global(ctx, "btoa"),
87
87
  crypto: (ctx) => cryptoGlobal(ctx),
88
+ structuredClone: (ctx) => structuredCloneGlobal(ctx),
88
89
  ...Object.fromEntries(errorTypes.map((type) => [type, (ctx) => errorGlobal(type, ctx)])),
89
90
  };
90
91
  /** Names bound in every program before extensions apply. */
@@ -9,7 +9,7 @@ import { globals } from "./globals.js";
9
9
  import { assign, Callable, define, frozen, get, hostCursor, IteratorObj, has, hidden, hasPrototype, keys, Native, parseArrayIndex, Arr, Fn, GeneratorObj, Obj, PromiseObj, record, remove, set, coerceToNumber, coerceToString, } from "./objects.js";
10
10
  import { preserveConsumerError } from "./callback.js";
11
11
  import { Pending, resolvePromise, resolvePromiseValue } from "./promises.js";
12
- import { containsOpaqueReference, describeValue, rejectCircularInsertion, typeofValue } from "./references.js";
12
+ import { describeValue, isOpaque, rejectCircularInsertion, typeofValue } from "./references.js";
13
13
  import { ScopeStack } from "./scope.js";
14
14
  import { constructRegExp } from "../stdlib/regexp.js";
15
15
  import { enumerableSource } from "../stdlib/object.js";
@@ -388,9 +388,6 @@ class Frame {
388
388
  const self = this;
389
389
  return Effect.gen(function* () {
390
390
  const discriminant = yield* self.evaluateExpression(node.discriminant);
391
- if (containsOpaqueReference(discriminant)) {
392
- throw invalidData("Switch discriminants must be data values.", node);
393
- }
394
391
  self.scopes.push();
395
392
  return yield* Effect.gen(function* () {
396
393
  const cases = node.cases;
@@ -405,11 +402,7 @@ class Frame {
405
402
  defaultIndex = index;
406
403
  continue;
407
404
  }
408
- const candidate = yield* self.evaluateExpression(test);
409
- if (containsOpaqueReference(candidate)) {
410
- throw invalidData("Switch case values must be data values.", test);
411
- }
412
- if (candidate === discriminant) {
405
+ if ((yield* self.evaluateExpression(test)) === discriminant) {
413
406
  selected = index;
414
407
  break;
415
408
  }
@@ -1077,16 +1070,19 @@ class Frame {
1077
1070
  return lhs === rhs;
1078
1071
  if (operator === "!==")
1079
1072
  return lhs !== rhs;
1080
- if (operator === "in" && rhs instanceof Obj && !containsOpaqueReference(lhs)) {
1073
+ if (operator === "==")
1074
+ return this.looselyEqual(lhs, rhs, node);
1075
+ if (operator === "!=")
1076
+ return !this.looselyEqual(lhs, rhs, node);
1077
+ if (operator === "in" && rhs instanceof Obj && !isOpaque(lhs)) {
1081
1078
  return has(rhs, lhs !== null && typeof lhs === "object" ? coerceToString(lhs) : lhs);
1082
1079
  }
1083
- if (containsOpaqueReference(lhs) || containsOpaqueReference(rhs)) {
1080
+ if (isOpaque(lhs) || isOpaque(rhs)) {
1084
1081
  throw invalidData("Binary operators require data values.", node);
1085
1082
  }
1086
- // Addition and loose equality use the default hint; every other operator asks for a number.
1087
- const hint = operator === "+" || operator === "==" || operator === "!=" ? "default" : "number";
1083
+ // Addition uses the default hint; every other operator asks for a number.
1084
+ const hint = operator === "+" ? "default" : "number";
1088
1085
  const coerceOperand = (operand) => (operand instanceof Obj ? operand.toPrimitive(hint) : operand);
1089
- const bothObjects = lhs !== null && typeof lhs === "object" && rhs !== null && typeof rhs === "object";
1090
1086
  const l = coerceOperand(lhs);
1091
1087
  const r = coerceOperand(rhs);
1092
1088
  switch (operator) {
@@ -1106,10 +1102,6 @@ class Frame {
1106
1102
  return l % r;
1107
1103
  case "**":
1108
1104
  return l ** r;
1109
- case "==":
1110
- return bothObjects ? lhs === rhs : l == r;
1111
- case "!=":
1112
- return bothObjects ? lhs !== rhs : l != r;
1113
1105
  case "<":
1114
1106
  return l < r;
1115
1107
  case "<=":
@@ -1139,6 +1131,22 @@ class Frame {
1139
1131
  throw typeError(`Unsupported binary operator '${operator}'.`, node);
1140
1132
  }
1141
1133
  }
1134
+ // IsLooselyEqual: objects (including functions and tool references) compare by identity, and only a
1135
+ // data object facing a non-nullish primitive needs to coerce, so an opaque value is rejected only there.
1136
+ looselyEqual(lhs, rhs, node) {
1137
+ const lhsObject = lhs !== null && typeof lhs === "object";
1138
+ const rhsObject = rhs !== null && typeof rhs === "object";
1139
+ if (lhsObject === rhsObject)
1140
+ return lhsObject ? lhs === rhs : lhs == rhs;
1141
+ const object = lhsObject ? lhs : rhs;
1142
+ const primitive = lhsObject ? rhs : lhs;
1143
+ if (primitive === null || primitive === undefined)
1144
+ return false;
1145
+ if (!(object instanceof Obj) || isOpaque(object)) {
1146
+ throw invalidData("Binary operators require data values.", node);
1147
+ }
1148
+ return object.toPrimitive("default") == primitive;
1149
+ }
1142
1150
  evaluateLogicalExpression(node) {
1143
1151
  const operator = node.operator;
1144
1152
  return Effect.flatMap(this.evaluateExpression(node.left), (left) => {
@@ -1167,7 +1175,7 @@ class Frame {
1167
1175
  return !value;
1168
1176
  if (operator === "void")
1169
1177
  return undefined;
1170
- if (containsOpaqueReference(value)) {
1178
+ if (isOpaque(value)) {
1171
1179
  throw invalidData("Unary operators require data values.", node);
1172
1180
  }
1173
1181
  const operand = value instanceof Obj ? value.toPrimitive("number") : value;
@@ -1257,7 +1265,7 @@ class Frame {
1257
1265
  // CodeMode numeric coercion, not host Number(): null-prototype data objects would make
1258
1266
  // the host throw during ToPrimitive, and opaque runtime references must reject clearly.
1259
1267
  const operand = (current) => {
1260
- if (containsOpaqueReference(current)) {
1268
+ if (isOpaque(current)) {
1261
1269
  throw invalidData(`'${operator}' requires a data value.`, argument);
1262
1270
  }
1263
1271
  return coerceToNumber(current);
@@ -1841,8 +1849,6 @@ class Frame {
1841
1849
  })();
1842
1850
  if (written)
1843
1851
  return;
1844
- if (target instanceof Arr && key === "length")
1845
- throw rangeError("Invalid array length", node);
1846
1852
  throw typeError(`Cannot assign to read only property '${String(key)}'.`, node);
1847
1853
  }
1848
1854
  // ToPropertyKey: anything else becomes its string form, so `counts[row.category]` works when the field is null.
@@ -38,6 +38,8 @@ export declare const frozen: Attributes;
38
38
  export declare class Obj {
39
39
  proto: Obj | null;
40
40
  readonly props: Map<string | symbol, Slot>;
41
+ /** [[Extensible]]: cleared by `Object.preventExtensions`, `seal`, and `freeze`. */
42
+ extensible: boolean;
41
43
  constructor(proto: Obj | null);
42
44
  /** The class name `Object.prototype.toString` reports: `[object Map]`. */
43
45
  readonly tag: string;
@@ -59,6 +61,8 @@ export declare class Obj {
59
61
  export declare class Arr extends Obj {
60
62
  readonly items: Array<Value>;
61
63
  readonly tag = "Array";
64
+ /** The attributes every live element shares; `seal` and `freeze` narrow them since elements have no slots. */
65
+ elements: Attributes;
62
66
  constructor(proto: Obj, items?: Array<Value>);
63
67
  get describe(): string;
64
68
  toString(): string;
@@ -232,6 +236,8 @@ export declare const has: (target: Obj, key: PropertyKey) => boolean;
232
236
  export declare const hasPrototype: (value: Value, proto: Obj) => boolean;
233
237
  /** [[Set]]: an inherited setter or read-only property decides before an own data property is created. */
234
238
  export declare const set: (target: Obj, key: PropertyKey, value: Value) => boolean;
239
+ /** Creating a property on a non-extensible object is the one [[Set]] failure with its own message. */
240
+ export declare const rejectAddition: (target: Obj, key: string | symbol | number) => void;
235
241
  /** [[DefineOwnProperty]] for a data property, ignoring the chain. */
236
242
  export declare const define: (target: Obj, key: PropertyKey, value: Value, attrs?: Attributes) => void;
237
243
  export declare const defineAccessor: (target: Obj, key: PropertyKey, get: Getter | undefined, set?: Setter) => void;
@@ -1,7 +1,7 @@
1
1
  import { Effect } from "effect";
2
2
  import { ToolReference } from "../tool-runtime.js";
3
3
  import { checkArrayLength } from "./limits.js";
4
- import { AsyncIteratorSymbol, IteratorSymbol, } from "./model.js";
4
+ import { AsyncIteratorSymbol, IteratorSymbol, rangeError, typeError, } from "./model.js";
5
5
  /** Ordinary assignment: writable, enumerable, configurable. */
6
6
  export const data = { writable: true, enumerable: true, configurable: true };
7
7
  /** Built-in methods and `constructor`: writable and configurable but hidden from enumeration. */
@@ -17,6 +17,8 @@ export const frozen = { writable: false, enumerable: false, configurable: false
17
17
  export class Obj {
18
18
  proto;
19
19
  props = new Map();
20
+ /** [[Extensible]]: cleared by `Object.preventExtensions`, `seal`, and `freeze`. */
21
+ extensible = true;
20
22
  constructor(proto) {
21
23
  this.proto = proto;
22
24
  }
@@ -60,6 +62,8 @@ export class Obj {
60
62
  export class Arr extends Obj {
61
63
  items;
62
64
  tag = "Array";
65
+ /** The attributes every live element shares; `seal` and `freeze` narrow them since elements have no slots. */
66
+ elements = data;
63
67
  constructor(proto, items = []) {
64
68
  super(proto);
65
69
  this.items = items;
@@ -363,10 +367,12 @@ export const own = (target, key) => {
363
367
  const at = index(target, name);
364
368
  if (at !== undefined) {
365
369
  const items = elements(target);
366
- return at in items ? { value: items[at], ...data } : undefined;
370
+ if (!(at in items))
371
+ return undefined;
372
+ return { value: items[at], ...(target instanceof Arr ? target.elements : data) };
367
373
  }
368
374
  if (target instanceof Arr && name === "length") {
369
- return { value: target.items.length, writable: true, enumerable: false, configurable: false };
375
+ return { value: target.items.length, writable: target.elements.writable, enumerable: false, configurable: false };
370
376
  }
371
377
  }
372
378
  return target.props.get(name);
@@ -403,16 +409,22 @@ export const hasPrototype = (value, proto) => {
403
409
  const writeElement = (target, name, value) => {
404
410
  const at = index(target, name);
405
411
  if (at !== undefined) {
406
- if (target instanceof Bytes)
412
+ if (target instanceof Bytes) {
407
413
  target.bytes[at] = typeof value === "number" ? value : Number(value);
408
- else
409
- target.items[at] = value;
414
+ return true;
415
+ }
416
+ if (!(at in target.items))
417
+ rejectAddition(target, at);
418
+ target.items[at] = value;
410
419
  return true;
411
420
  }
412
421
  if (!(target instanceof Arr) || name !== "length")
413
422
  return undefined;
414
423
  const length = typeof value === "number" ? value : Number(value);
415
424
  if (!Number.isInteger(length) || length < 0)
425
+ throw rangeError("Invalid array length");
426
+ // Shrinking deletes elements, which a sealed array forbids.
427
+ if (length < target.items.length && !target.elements.configurable)
416
428
  return false;
417
429
  checkArrayLength(length);
418
430
  target.items.length = length;
@@ -448,9 +460,16 @@ export const set = (target, key, value) => {
448
460
  if (written !== undefined)
449
461
  return written;
450
462
  }
463
+ rejectAddition(target, name);
451
464
  target.props.set(name, { value, ...data });
452
465
  return true;
453
466
  };
467
+ /** Creating a property on a non-extensible object is the one [[Set]] failure with its own message. */
468
+ export const rejectAddition = (target, key) => {
469
+ if (target.extensible)
470
+ return;
471
+ throw typeError(`Cannot add property ${String(key)}, object is not extensible.`);
472
+ };
454
473
  /** [[DefineOwnProperty]] for a data property, ignoring the chain. */
455
474
  export const define = (target, key, value, attrs = data) => {
456
475
  const name = canonical(key);
@@ -465,8 +484,13 @@ export const remove = (target, key) => {
465
484
  const name = canonical(key);
466
485
  if (isIndexed(target)) {
467
486
  const at = index(target, name);
468
- if (at !== undefined)
469
- return target instanceof Bytes ? !(at in target.bytes) : delete target.items[at];
487
+ if (at !== undefined) {
488
+ if (target instanceof Bytes)
489
+ return !(at in target.bytes);
490
+ if (at in target.items && !target.elements.configurable)
491
+ return false;
492
+ return delete target.items[at];
493
+ }
470
494
  if (target instanceof Arr && name === "length")
471
495
  return false;
472
496
  }
@@ -1,5 +1,11 @@
1
1
  import { Effect } from "effect";
2
- import { type Value } from "../interpreter/objects.js";
2
+ import { type Method } from "../interpreter/native.js";
3
+ import { Obj, type Value } from "../interpreter/objects.js";
3
4
  import type { Interpreter } from "../interpreter/interpreter.js";
4
5
  export declare const sortArray: <R>(ctx: Interpreter<R>, target: Array<Value>, comparator: Value, name: string) => Effect.Effect<Array<Value>, unknown, R>;
5
6
  export declare const arrayGlobal: <R>(ctx: Interpreter<R>) => import("../interpreter/objects.js").Native<R>;
7
+ /**
8
+ * The callback methods Array and Uint8Array share. They fix the iteration length while reading existing elements
9
+ * live; `wrap` builds the collection `map` and `filter` return.
10
+ */
11
+ export declare const callbackMethods: <R, T extends Obj>(ctx: Interpreter<R>, label: string, self: (thisValue: Value, name: string) => T, elements: (target: T) => ArrayLike<Value>, wrap: (values: Array<Value>) => Value) => Array<Method>;
@@ -2,7 +2,7 @@ import { Effect } from "effect";
2
2
  import { constructor, methods, prototypeFrom, receiver } from "../interpreter/native.js";
3
3
  import { checkArrayLength, checkStringLength, MAX_ARRAY_LENGTH } from "../interpreter/limits.js";
4
4
  import { invalidData, IteratorSymbol, rangeError, typeError } from "../interpreter/model.js";
5
- import { define, get, hidden, Arr, GeneratorObj, hostIterator, Obj, PromiseObj, coerceToInteger, coerceToNumber, coerceToString, } from "../interpreter/objects.js";
5
+ import { define, get, hidden, Arr, GeneratorObj, hostIterator, Obj, PromiseObj, coerceToInteger, coerceToNumber, coerceToString, rejectAddition, } from "../interpreter/objects.js";
6
6
  import { describeValue, rejectCircularInsertion } from "../interpreter/references.js";
7
7
  import { applyCollectionCallback, invoke, preserveConsumerError } from "../interpreter/callback.js";
8
8
  import { compareText } from "../tool-runtime.js";
@@ -102,16 +102,22 @@ export const arrayGlobal = (ctx) => {
102
102
  ["from", 1, (_, args) => arrayFrom(ctx, args)],
103
103
  ]);
104
104
  const self = (thisValue, name) => receiver(Arr, thisValue, `Array.prototype.${name}`);
105
+ // A mutating method fails where its first element write, delete, or `length` write would on a frozen, sealed, or
106
+ // non-extensible array. `growth` is given by the methods that always write `length`, even when it is 0; holes a
107
+ // method would fill on a non-extensible array are not checked.
108
+ const mutable = (target, growth) => {
109
+ const length = target.items.length;
110
+ if ((growth ?? 0) > 0)
111
+ rejectAddition(target, length);
112
+ if ((growth ?? 0) < 0 && length > 0 && !target.elements.configurable) {
113
+ throw typeError(`Cannot delete property '${length - 1}'.`);
114
+ }
115
+ if (!target.elements.writable && (length > 0 || growth !== undefined)) {
116
+ throw typeError(`Cannot assign to read only property '${length > 0 ? 0 : "length"}'.`);
117
+ }
118
+ return target;
119
+ };
105
120
  const optNumber = (value) => (value === undefined ? undefined : coerceToInteger(value));
106
- // Callback methods fix the iteration length while reading existing elements live.
107
- const iterate = (name, length, body) => [
108
- name,
109
- length,
110
- (thisValue, args) => {
111
- const target = self(thisValue, name);
112
- return body(target.items, target, applyCollectionCallback(ctx, args[0], `Array.${name}`), args);
113
- },
114
- ];
115
121
  methods(builtins, proto, [
116
122
  [
117
123
  "join",
@@ -144,7 +150,8 @@ export const arrayGlobal = (ctx) => {
144
150
  1,
145
151
  (thisValue, args) => {
146
152
  const target = self(thisValue, "lastIndexOf").items;
147
- return args[1] === undefined ? target.lastIndexOf(args[0]) : target.lastIndexOf(args[0], optNumber(args[1]));
153
+ // An explicit undefined is a fromIndex of 0, unlike omitting it.
154
+ return args.length < 2 ? target.lastIndexOf(args[0]) : target.lastIndexOf(args[0], optNumber(args[1]));
148
155
  },
149
156
  ],
150
157
  ["at", 1, (thisValue, args) => self(thisValue, "at").items.at(optNumber(args[0]) ?? 0)],
@@ -177,6 +184,9 @@ export const arrayGlobal = (ctx) => {
177
184
  0,
178
185
  (thisValue) => {
179
186
  const target = self(thisValue, "reverse");
187
+ // Fewer than two elements means no writes at all, so a frozen one-element array reverses fine.
188
+ if (target.items.length > 1)
189
+ mutable(target);
180
190
  target.items.reverse();
181
191
  return target;
182
192
  },
@@ -185,7 +195,7 @@ export const arrayGlobal = (ctx) => {
185
195
  "sort",
186
196
  1,
187
197
  (thisValue, args) => {
188
- const target = self(thisValue, "sort");
198
+ const target = mutable(self(thisValue, "sort"));
189
199
  const items = target.items;
190
200
  const length = items.length;
191
201
  const holeCount = Array.from({ length }, (_, index) => Object.hasOwn(items, index)).filter((o) => !o).length;
@@ -225,7 +235,7 @@ export const arrayGlobal = (ctx) => {
225
235
  "push",
226
236
  1,
227
237
  (thisValue, args) => {
228
- const target = self(thisValue, "push");
238
+ const target = mutable(self(thisValue, "push"), args.length);
229
239
  // Validate all insertions before mutating to avoid partial cyclic updates.
230
240
  for (const item of args)
231
241
  rejectCircularInsertion(target, item, "Array.push result");
@@ -236,29 +246,28 @@ export const arrayGlobal = (ctx) => {
236
246
  "unshift",
237
247
  1,
238
248
  (thisValue, args) => {
239
- const target = self(thisValue, "unshift");
249
+ const target = mutable(self(thisValue, "unshift"), args.length);
240
250
  for (const item of args)
241
251
  rejectCircularInsertion(target, item, "Array.unshift result");
242
252
  return target.items.unshift(...args);
243
253
  },
244
254
  ],
245
- ["pop", 0, (thisValue) => self(thisValue, "pop").items.pop()],
246
- ["shift", 0, (thisValue) => self(thisValue, "shift").items.shift()],
255
+ ["pop", 0, (thisValue) => mutable(self(thisValue, "pop"), -1).items.pop()],
256
+ ["shift", 0, (thisValue) => mutable(self(thisValue, "shift"), -1).items.shift()],
247
257
  [
248
258
  "splice",
249
259
  2,
250
260
  (thisValue, args) => {
251
261
  const target = self(thisValue, "splice");
252
- if (args.length === 0)
253
- return wrap(target.items.splice(0, 0));
262
+ const length = target.items.length;
254
263
  const start = optNumber(args[0]) ?? 0;
255
- if (args.length === 1)
256
- return wrap(target.items.splice(start));
257
- const deleteCount = optNumber(args[1]) ?? 0;
264
+ const from = start < 0 ? Math.max(length + start, 0) : Math.min(start, length);
265
+ const deleteCount = args.length === 1 ? length - from : Math.min(Math.max(optNumber(args[1]) ?? 0, 0), length - from);
258
266
  const inserted = args.slice(2);
259
267
  for (const item of inserted)
260
268
  rejectCircularInsertion(target, item, "Array.splice result");
261
- return wrap(target.items.splice(start, deleteCount, ...inserted));
269
+ mutable(target, inserted.length - deleteCount);
270
+ return wrap(target.items.splice(from, deleteCount, ...inserted));
262
271
  },
263
272
  ],
264
273
  [
@@ -280,7 +289,7 @@ export const arrayGlobal = (ctx) => {
280
289
  "fill",
281
290
  1,
282
291
  (thisValue, args) => {
283
- const target = self(thisValue, "fill");
292
+ const target = mutable(self(thisValue, "fill"));
284
293
  rejectCircularInsertion(target, args[0], "Array.fill result");
285
294
  target.items.fill(args[0], optNumber(args[1]), optNumber(args[2]));
286
295
  return target;
@@ -290,7 +299,7 @@ export const arrayGlobal = (ctx) => {
290
299
  "copyWithin",
291
300
  2,
292
301
  (thisValue, args) => {
293
- const target = self(thisValue, "copyWithin");
302
+ const target = mutable(self(thisValue, "copyWithin"));
294
303
  target.items.copyWithin(optNumber(args[0]) ?? 0, optNumber(args[1]) ?? 0, optNumber(args[2]));
295
304
  return target;
296
305
  },
@@ -311,6 +320,47 @@ export const arrayGlobal = (ctx) => {
311
320
  .items.entries()
312
321
  .map(([index, item]) => wrap([index, item]))),
313
322
  ],
323
+ [
324
+ "flatMap",
325
+ 1,
326
+ (thisValue, args) => {
327
+ const target = self(thisValue, "flatMap");
328
+ const apply = applyCollectionCallback(ctx, args[0], "Array.flatMap");
329
+ return Effect.gen(function* () {
330
+ const length = target.items.length;
331
+ const values = [];
332
+ for (let index = 0; index < length; index += 1) {
333
+ if (!(index in target.items))
334
+ continue;
335
+ const mapped = yield* apply([target.items[index], index, target]);
336
+ if (mapped instanceof Arr)
337
+ values.push(...mapped.items);
338
+ else
339
+ values.push(mapped);
340
+ }
341
+ return wrap(values);
342
+ });
343
+ },
344
+ ],
345
+ ...callbackMethods(ctx, "Array", self, (target) => target.items, wrap),
346
+ ]);
347
+ define(proto, IteratorSymbol, get(proto, "values"), hidden);
348
+ return array;
349
+ };
350
+ /**
351
+ * The callback methods Array and Uint8Array share. They fix the iteration length while reading existing elements
352
+ * live; `wrap` builds the collection `map` and `filter` return.
353
+ */
354
+ export const callbackMethods = (ctx, label, self, elements, wrap) => {
355
+ const iterate = (name, length, body) => [
356
+ name,
357
+ length,
358
+ (thisValue, args) => {
359
+ const target = self(thisValue, name);
360
+ return body(elements(target), target, applyCollectionCallback(ctx, args[0], `${label}.${name}`), args);
361
+ },
362
+ ];
363
+ return [
314
364
  iterate("map", 1, (target, receiver, apply) => Effect.gen(function* () {
315
365
  const length = target.length;
316
366
  const values = [];
@@ -322,20 +372,6 @@ export const arrayGlobal = (ctx) => {
322
372
  }
323
373
  return wrap(values);
324
374
  })),
325
- iterate("flatMap", 1, (target, receiver, apply) => Effect.gen(function* () {
326
- const length = target.length;
327
- const values = [];
328
- for (let index = 0; index < length; index += 1) {
329
- if (!(index in target))
330
- continue;
331
- const mapped = yield* apply([target[index], index, receiver]);
332
- if (mapped instanceof Arr)
333
- values.push(...mapped.items);
334
- else
335
- values.push(mapped);
336
- }
337
- return wrap(values);
338
- })),
339
375
  iterate("filter", 1, (target, receiver, apply) => Effect.gen(function* () {
340
376
  const length = target.length;
341
377
  const values = [];
@@ -416,7 +452,7 @@ export const arrayGlobal = (ctx) => {
416
452
  while (start < length && !(start in target))
417
453
  start += 1;
418
454
  if (start === length) {
419
- throw typeError("Array.reduce of an empty array with no initial value.");
455
+ throw typeError(`${label}.reduce of an empty array with no initial value.`);
420
456
  }
421
457
  accumulator = target[start];
422
458
  start += 1;
@@ -435,7 +471,7 @@ export const arrayGlobal = (ctx) => {
435
471
  while (start >= 0 && !(start in target))
436
472
  start -= 1;
437
473
  if (start < 0) {
438
- throw typeError("Array.reduceRight of an empty array with no initial value.");
474
+ throw typeError(`${label}.reduceRight of an empty array with no initial value.`);
439
475
  }
440
476
  accumulator = target[start];
441
477
  start -= 1;
@@ -447,7 +483,5 @@ export const arrayGlobal = (ctx) => {
447
483
  }
448
484
  return accumulator;
449
485
  })),
450
- ]);
451
- define(proto, IteratorSymbol, get(proto, "values"), hidden);
452
- return array;
486
+ ];
453
487
  };
@@ -4,6 +4,7 @@ import { constructor, methods, prototypeFrom, receiver, requiresNew } from "../i
4
4
  import { IteratorSymbol, rangeError, syntaxError, typeError } from "../interpreter/model.js";
5
5
  import { define, defineAccessor, get, hidden, Arr, Bytes, hostIterator, Obj, coerceToInteger, coerceToNumber, coerceToString, } from "../interpreter/objects.js";
6
6
  import { describeValue } from "../interpreter/references.js";
7
+ import { callbackMethods, sortArray } from "./array.js";
7
8
  /** The bytes a Uint8Array, array, or other iterable of numbers describes; the host array clamps each value. */
8
9
  const collectBytes = (ctx, source, name) => {
9
10
  if (source instanceof Bytes)
@@ -110,6 +111,22 @@ export const uint8ArrayGlobal = (ctx) => {
110
111
  return target;
111
112
  },
112
113
  ],
114
+ [
115
+ "sort",
116
+ 1,
117
+ (thisValue, args) => {
118
+ const target = self(thisValue, "sort");
119
+ // Without a comparator, typed arrays sort numerically rather than by string.
120
+ if (args[0] === undefined) {
121
+ target.bytes.sort();
122
+ return target;
123
+ }
124
+ return Effect.map(sortArray(ctx, [...target.bytes], args[0], "Uint8Array.sort"), (sorted) => {
125
+ target.bytes.set(Uint8Array.from(sorted, coerceToNumber));
126
+ return target;
127
+ });
128
+ },
129
+ ],
113
130
  [
114
131
  "indexOf",
115
132
  1,
@@ -120,7 +137,7 @@ export const uint8ArrayGlobal = (ctx) => {
120
137
  1,
121
138
  (thisValue, args) => {
122
139
  const target = self(thisValue, "lastIndexOf").bytes;
123
- return args[1] === undefined
140
+ return args.length < 2
124
141
  ? target.lastIndexOf(coerceToNumber(args[0]))
125
142
  : target.lastIndexOf(coerceToNumber(args[0]), optNumber(args[1]));
126
143
  },
@@ -151,6 +168,7 @@ export const uint8ArrayGlobal = (ctx) => {
151
168
  .bytes.entries()
152
169
  .map(([index, byte]) => wrapAll([index, byte]))),
153
170
  ],
171
+ ...callbackMethods(ctx, "Uint8Array", self, (target) => target.bytes, (values) => wrap(Uint8Array.from(values, coerceToNumber))),
154
172
  ]);
155
173
  define(proto, IteratorSymbol, get(proto, "values"), hidden);
156
174
  return uint8Array;
@@ -1,7 +1,8 @@
1
1
  import { Effect } from "effect";
2
2
  import { constructor, methods, receiver } from "../interpreter/native.js";
3
- import { AsyncIteratorSymbol, invalidData, IteratorSymbol, rangeError, typeError, } from "../interpreter/model.js";
4
- import { define, entries, enumerableKeys, getOwn, hasOwn, hasPrototype, hidden, keys, own, Arr, Obj, PromiseObj, set, coerceToString, } from "../interpreter/objects.js";
3
+ import { AsyncIteratorSymbol, invalidData, IteratorSymbol, typeError } from "../interpreter/model.js";
4
+ import { define, entries, enumerableKeys, getOwn, hasOwn, hasPrototype, hidden, keys, own, ownKeys, Arr, Bytes, Obj, PromiseObj, set, coerceToString, } from "../interpreter/objects.js";
5
+ import { primitivePrototype } from "../interpreter/intrinsics.js";
5
6
  import { containsOpaqueReference, describeValue, rejectCircularInsertion } from "../interpreter/references.js";
6
7
  import { invoke, preserveConsumerError } from "../interpreter/callback.js";
7
8
  import { ToolReference } from "../tool-runtime.js";
@@ -37,8 +38,6 @@ export const objectAssign = (ctx, args) => {
37
38
  for (const key of enumerableKeys(from)) {
38
39
  rejectCircularInsertion(target, getOwn(from, key), "Object.assign result", seen);
39
40
  if (!set(target, key, getOwn(from, key))) {
40
- if (target instanceof Arr && key === "length")
41
- throw rangeError("Invalid array length");
42
41
  throw typeError(`Cannot assign to read only property '${String(key)}'.`);
43
42
  }
44
43
  }
@@ -81,6 +80,38 @@ const classTag = (value) => {
81
80
  return "Object";
82
81
  };
83
82
  const propertyKey = (value) => value === AsyncIteratorSymbol || value === IteratorSymbol ? value : coerceToString(value);
83
+ // SetIntegrityLevel: primitives pass through. A typed array's bytes cannot carry attributes, so JS throws after
84
+ // already making it non-extensible.
85
+ const restrict = (level, value) => {
86
+ if (!(value instanceof Obj))
87
+ return value;
88
+ value.extensible = false;
89
+ if (level === "preventExtensions")
90
+ return value;
91
+ if (value instanceof Bytes && value.bytes.length > 0) {
92
+ throw typeError(`Cannot ${level} array buffer views with elements.`);
93
+ }
94
+ for (const slot of value.props.values()) {
95
+ slot.configurable = false;
96
+ if (level === "freeze" && "value" in slot)
97
+ slot.writable = false;
98
+ }
99
+ if (value instanceof Arr)
100
+ value.elements = { writable: level === "seal", enumerable: true, configurable: false };
101
+ return value;
102
+ };
103
+ // TestIntegrityLevel: array elements and `length` answer through `own`, so a non-extensible empty array is sealed
104
+ // but not frozen, as in JS.
105
+ const integrity = (value, frozen) => {
106
+ if (!(value instanceof Obj))
107
+ return true;
108
+ if (value.extensible)
109
+ return false;
110
+ return ownKeys(value).every((key) => {
111
+ const slot = own(value, key);
112
+ return slot !== undefined && !slot.configurable && !(frozen && "value" in slot && slot.writable);
113
+ });
114
+ };
84
115
  // Object constructs identically with or without new, like JS. Only `keys` copies its result into the
85
116
  // program; `values`, `entries`, `assign`, and `fromEntries` hand back the program's own values.
86
117
  export const objectGlobal = (ctx) => {
@@ -118,18 +149,41 @@ export const objectGlobal = (ctx) => {
118
149
  (_, args) => new Arr(builtins.Array, entries(enumerableSource(ctx, "Object.entries(...)", args[0])).map((entry) => new Arr(builtins.Array, entry))),
119
150
  ],
120
151
  ["hasOwn", 2, (_, args) => hasOwn(enumerableSource(ctx, "Object.hasOwn(...)", args[0]), propertyKey(args[1]))],
152
+ ["is", 2, (_, args) => Object.is(args[0], args[1])],
153
+ ["assign", 2, (_, args) => objectAssign(ctx, args)],
154
+ ["fromEntries", 1, (_, args) => objectFromEntries(ctx, args[0])],
155
+ ["freeze", 1, (_, args) => restrict("freeze", args[0])],
156
+ ["seal", 1, (_, args) => restrict("seal", args[0])],
157
+ ["preventExtensions", 1, (_, args) => restrict("preventExtensions", args[0])],
158
+ ["isFrozen", 1, (_, args) => integrity(args[0], true)],
159
+ ["isSealed", 1, (_, args) => integrity(args[0], false)],
160
+ ["isExtensible", 1, (_, args) => args[0] instanceof Obj && args[0].extensible],
121
161
  [
122
- "is",
162
+ "getPrototypeOf",
163
+ 1,
164
+ (_, args) => {
165
+ if (args[0] instanceof Obj)
166
+ return args[0].proto;
167
+ const proto = primitivePrototype(builtins, args[0]);
168
+ if (proto === undefined) {
169
+ throw typeError(`Object.getPrototypeOf cannot convert ${describeValue(args[0])} to an object.`);
170
+ }
171
+ return proto;
172
+ },
173
+ ],
174
+ [
175
+ "create",
123
176
  2,
124
177
  (_, args) => {
125
- if (containsOpaqueReference(args[0]) || containsOpaqueReference(args[1])) {
126
- throw invalidData("Object.is requires data values.");
178
+ if (args[0] !== null && !(args[0] instanceof Obj)) {
179
+ throw typeError("Object prototype may only be an Object or null.");
180
+ }
181
+ if (args[1] !== undefined) {
182
+ throw typeError("Object.create property descriptors are not supported; assign the fields after creating the object.");
127
183
  }
128
- return Object.is(args[0], args[1]);
184
+ return new Obj(args[0]);
129
185
  },
130
186
  ],
131
- ["assign", 2, (_, args) => objectAssign(ctx, args)],
132
- ["fromEntries", 1, (_, args) => objectFromEntries(ctx, args[0])],
133
187
  ]);
134
188
  define(object, "groupBy", groupBy(ctx, "Object"), hidden);
135
189
  methods(builtins, builtins.Object, [
@@ -1,4 +1,5 @@
1
1
  import { Obj } from "../interpreter/objects.js";
2
2
  import type { Interpreter } from "../interpreter/interpreter.js";
3
3
  export declare const base64Global: <R>(ctx: Interpreter<R>, name: "atob" | "btoa") => import("../interpreter/objects.js").Native<R>;
4
+ export declare const structuredCloneGlobal: <R>(ctx: Interpreter<R>) => import("../interpreter/objects.js").Native<R>;
4
5
  export declare const cryptoGlobal: <R>(ctx: Interpreter<R>) => Obj;
@@ -1,7 +1,9 @@
1
1
  import { fn, methods } from "../interpreter/native.js";
2
2
  import { typeError } from "../interpreter/model.js";
3
- import { Bytes, Obj, coerceToString } from "../interpreter/objects.js";
3
+ import { define, entries, get, getOwn, hasOwn, hidden, isRuntimeReference, Arr, Bytes, DateObj, ErrorObj, MapObj, Obj, RegExpObj, SetObj, coerceToString, } from "../interpreter/objects.js";
4
+ import { createErrorValue, isErrorType } from "../interpreter/intrinsics.js";
4
5
  import { describeValue } from "../interpreter/references.js";
6
+ import { ToolReference } from "../tool-runtime.js";
5
7
  // WebIDL DOMString conversion: a missing argument is a TypeError, anything else stringifies. Invalid input is a
6
8
  // TypeError as well; browsers throw a DOMException named InvalidCharacterError, which CodeMode does not have.
7
9
  export const base64Global = (ctx, name) => fn(ctx.builtins, name, 1, (_, args) => {
@@ -15,6 +17,65 @@ export const base64Global = (ctx, name) => fn(ctx.builtins, name, 1, (_, args) =
15
17
  throw typeError("The string contains invalid characters.");
16
18
  }
17
19
  });
20
+ // HTML structured clone over CodeMode's data kinds. Shared references survive through the memo; prototypes and
21
+ // frozen state do not, as in JS. Without a DOMException, DataCloneError is a TypeError carrying that prefix.
22
+ export const structuredCloneGlobal = (ctx) => fn(ctx.builtins, "structuredClone", 1, (_, args) => {
23
+ if (args.length === 0)
24
+ throw typeError("structuredClone requires 1 argument");
25
+ const builtins = ctx.builtins;
26
+ const memo = new Map();
27
+ const clone = (value) => {
28
+ if (typeof value === "symbol" || value instanceof ToolReference) {
29
+ throw typeError(`DataCloneError: ${describeValue(value)} could not be cloned.`);
30
+ }
31
+ if (!(value instanceof Obj))
32
+ return value;
33
+ const seen = memo.get(value);
34
+ if (seen !== undefined)
35
+ return seen;
36
+ const remember = (copy) => {
37
+ memo.set(value, copy);
38
+ return copy;
39
+ };
40
+ if (value instanceof MapObj) {
41
+ const copy = remember(new MapObj(builtins.Map));
42
+ for (const [key, item] of value.map)
43
+ copy.map.set(clone(key), clone(item));
44
+ return copy;
45
+ }
46
+ if (value instanceof SetObj) {
47
+ const copy = remember(new SetObj(builtins.Set));
48
+ for (const item of value.set)
49
+ copy.set.add(clone(item));
50
+ return copy;
51
+ }
52
+ if (value instanceof DateObj)
53
+ return remember(new DateObj(builtins.Date, value.time));
54
+ if (value instanceof RegExpObj) {
55
+ return remember(new RegExpObj(builtins.RegExp, value.regex.source, value.regex.flags));
56
+ }
57
+ if (value instanceof Bytes)
58
+ return remember(new Bytes(builtins.Uint8Array, new Uint8Array(value.bytes)));
59
+ if (value instanceof ErrorObj) {
60
+ const name = get(value, "name");
61
+ const message = get(value, "message");
62
+ const copy = remember(createErrorValue(builtins[typeof name === "string" && isErrorType(name) ? name : "Error"], message === undefined ? undefined : coerceToString(message)));
63
+ const stack = getOwn(value, "stack");
64
+ if (typeof stack === "string")
65
+ define(copy, "stack", stack, hidden);
66
+ if (hasOwn(value, "cause"))
67
+ define(copy, "cause", clone(getOwn(value, "cause")), hidden);
68
+ return copy;
69
+ }
70
+ if (isRuntimeReference(value))
71
+ throw typeError(`DataCloneError: ${describeValue(value)} could not be cloned.`);
72
+ const copy = remember(value instanceof Arr ? new Arr(builtins.Array, new Array(value.items.length)) : new Obj(builtins.Object));
73
+ for (const [key, item] of entries(value))
74
+ define(copy, key, clone(item));
75
+ return copy;
76
+ };
77
+ return clone(args[0]);
78
+ });
18
79
  export const cryptoGlobal = (ctx) => {
19
80
  const object = new Obj(ctx.builtins.Object);
20
81
  methods(ctx.builtins, object, [
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@opencode/codemode",
4
- "version": "2.0.15",
4
+ "version": "2.0.16",
5
5
  "description": "Effect-native confined code execution over schema-described tools",
6
6
  "type": "module",
7
7
  "license": "MIT",