diff --git a/skiplang/skjson/ts/src/skjson.ts b/skiplang/skjson/ts/src/skjson.ts
index da7c9ca7a..13053bf42 100644
--- a/skiplang/skjson/ts/src/skjson.ts
+++ b/skiplang/skjson/ts/src/skjson.ts
@@ -151,7 +151,9 @@ type ObjectProxy = {
keys: IterableIterator;
} & Base;
-function isObjectProxy(x: any): x is ObjectProxy<{ [k: string]: Exportable }> {
+export function isObjectProxy(
+ x: any,
+): x is ObjectProxy<{ [k: string]: Exportable }> {
return sk_isObjectProxy in x && (x[sk_isObjectProxy] as boolean);
}
diff --git a/skipruntime-ts/wasm/src/internals/skipruntime_module.ts b/skipruntime-ts/wasm/src/internals/skipruntime_module.ts
index adc6b637f..ce1759ad4 100644
--- a/skipruntime-ts/wasm/src/internals/skipruntime_module.ts
+++ b/skipruntime-ts/wasm/src/internals/skipruntime_module.ts
@@ -37,6 +37,7 @@ import { NonUniqueValueException } from "@skipruntime/api";
import { Frozen, type Constant } from "@skipruntime/api/internals.js";
import type { Exportable, SKJSON } from "@skip-wasm/json";
+import { isObjectProxy } from "@skip-wasm/json";
import { UnknownCollectionError } from "@skipruntime/helpers/errors.js";
export type Handle = Internal.Opaque;
@@ -64,22 +65,20 @@ abstract class SkFrozen extends Frozen {
}
}
-export function check(value: T): void {
+function checkOrCloneParam(value: T): T {
if (
typeof value == "string" ||
typeof value == "number" ||
typeof value == "boolean"
- ) {
- return;
- } else if (typeof value == "object") {
- if (value === null || isSkFrozen(value)) {
- return;
- } else {
- throw new Error("Invalid object: must be deep-frozen.");
- }
- } else {
- throw new Error(`'${typeof value}' cannot be deep-frozen.`);
+ )
+ return value;
+ if (typeof value == "object") {
+ if (value === null) return value;
+ if (isObjectProxy(value)) return value.clone() as T;
+ if (isSkFrozen(value)) return value;
+ throw new Error("Invalid object: must be deep-frozen.");
}
+ throw new Error(`'${typeof value}' cannot be deep-frozen.`);
}
/**
@@ -985,8 +984,8 @@ class EagerCollectionImpl
mapper: new (...params: Params) => Mapper,
...params: Params
): EagerCollection {
- params.forEach(check);
- const mapperObj = new mapper(...params);
+ const mapperParams = params.map(checkOrCloneParam) as Params;
+ const mapperObj = new mapper(...mapperParams);
Object.freeze(mapperObj);
if (!mapperObj.constructor.name) {
throw new Error("Mapper classes must be defined at top-level.");
@@ -1011,8 +1010,8 @@ class EagerCollectionImpl
reducer: Reducer,
...params: Params
) {
- params.forEach(check);
- const mapperObj = new mapper(...params);
+ const mapperParams = params.map(checkOrCloneParam) as Params;
+ const mapperObj = new mapper(...mapperParams);
Object.freeze(mapperObj);
if (!mapperObj.constructor.name) {
throw new Error("Mapper classes must be defined at top-level.");
@@ -1116,8 +1115,8 @@ class ContextImpl extends SkFrozen implements Context {
compute: new (...params: Params) => LazyCompute,
...params: Params
): LazyCollection {
- params.forEach(check);
- const computeObj = new compute(...params);
+ const mapperParams = params.map(checkOrCloneParam) as Params;
+ const computeObj = new compute(...mapperParams);
Object.freeze(computeObj);
if (!computeObj.constructor.name) {
throw new Error("LazyCompute classes must be defined at top-level.");