From 69a8f934bf1875c98169a9670c12f72056d31cce Mon Sep 17 00:00:00 2001
From: Ellyse
Date: Wed, 16 Jul 2025 14:05:52 -0700
Subject: [PATCH 1/2] some fixes with NaN for keys but mostly logging
---
.../runner/integration/array_push.test.ts | 27 ++++++----
.../runner/integration/array_push.test.tsx | 53 +++++++++++++++----
packages/runner/src/data-updating.ts | 23 +++++++-
.../runner/src/storage/transaction-shim.ts | 38 ++++++++++++-
4 files changed, 117 insertions(+), 24 deletions(-)
diff --git a/packages/runner/integration/array_push.test.ts b/packages/runner/integration/array_push.test.ts
index e7a0a5d96..b57baab06 100644
--- a/packages/runner/integration/array_push.test.ts
+++ b/packages/runner/integration/array_push.test.ts
@@ -26,11 +26,12 @@ const MEMORY_WS_URL = `${
const SPACE_NAME = "runner_integration";
const TOTAL_COUNT = 100; // how many elements we push to the array
-const TIMEOUT_MS = 30000; // timeout for the test in ms
+const TIMEOUT_MS = 60000; // timeout for the test in ms
console.log("Array Push Test");
console.log(`Connecting to: ${MEMORY_WS_URL}`);
console.log(`Toolshed URL: ${TOOLSHED_URL}`);
+console.log(`Space Name: ${SPACE_NAME}`);
// Set up timeout
const timeoutPromise = new Promise((_, reject) => {
@@ -92,18 +93,22 @@ async function runTest() {
console.log("Result charm ID:", getEntityId(charm));
// Wait so we can load the page on the browser
- // await new Promise((resolve) => setTimeout(resolve, 10000));
+ await new Promise((resolve) => setTimeout(resolve, 15000));
- // Get the handler stream and send some numbers
+ // BATCH VERSION - causes errors
const pushHandlerStream = charm.key("pushHandler");
- let sendCount = 0;
-
- // Loop, sending numbers one by one
- for (let i = 0; i < TOTAL_COUNT; i++) {
- console.log(`Sending value: ${i}`);
- pushHandlerStream.send({ value: i });
- sendCount++;
- }
+ console.log("Sending event to push all items at once");
+ pushHandlerStream.send({});
+
+ // ORIGINAL VERSION - works without errors
+ // const pushHandlerStream = charm.key("pushHandler");
+ // let sendCount = 0;
+ // // Loop, sending numbers one by one
+ // for (let i = 0; i < TOTAL_COUNT; i++) {
+ // console.log(`Sending value: ${i}`);
+ // pushHandlerStream.send({ value: i });
+ // sendCount++;
+ // }
console.log("Waiting for runtime to finish and storage to sync...");
await runtime.idle();
diff --git a/packages/runner/integration/array_push.test.tsx b/packages/runner/integration/array_push.test.tsx
index 8978f3e26..3e868854a 100644
--- a/packages/runner/integration/array_push.test.tsx
+++ b/packages/runner/integration/array_push.test.tsx
@@ -9,6 +9,8 @@ import {
UI,
} from "commontools";
+const ARRAY_LENGTH=1;
+
// dummy input schema
const InputSchema = {
type: "object",
@@ -21,7 +23,13 @@ const OutputSchema = {
properties: {
my_array: {
type: "array",
- items: { type: "number" },
+ items: {
+ type: "object",
+ properties: {
+ name: { type: "string" },
+ value: { type: "number" },
+ },
+ },
default: [],
asCell: true,
},
@@ -34,14 +42,41 @@ export default recipe(
InputSchema,
OutputSchema,
() => {
- const my_array = cell([]);
+ const my_array = cell<{ name: string; value: number }[]>([]);
- const pushHandler = handler(
- ({ value }: { value: number }, { array }: { array: number[] }) => {
- console.log("Pushing value:", value);
- array.push(value);
+ // BATCH VERSION - causes "Path must not be empty" errors
+ const pushHandler = handler({}, {
+ type: "object",
+ properties: {
+ array: {
+ type: "array",
+ asCell: true,
+ items: {
+ type: "object",
+ properties: { name: { type: "string" }, value: { type: "number" } },
+ },
+ },
},
- );
+ required: ["array"],
+ }, (_, { array }) => {
+ console.log("[pushHandler] Pushing all items at once");
+ const itemsToAdd = Array.from({ length: ARRAY_LENGTH }, (_, i) => ({
+ name: `Item ${i}`,
+ value: i,
+ }));
+ console.log("[pushHandler] Before push - array:", array);
+ console.log("[pushHandler] Items to add:", itemsToAdd);
+ array.push(...itemsToAdd);
+ // array.push(itemsToAdd[0]);
+ console.log("[pushHandler] After push - array.get():", array.get());
+ });
+
+ // const pushHandler = handler(
+ // ({ value }: { value: number }, { array }: { array: { name: string; value: number }[] }) => {
+ // console.log("Pushing value:", value);
+ // array.push({ name: `Item ${value}`, value: value });
+ // },
+ // );
// Return the recipe
return {
@@ -52,9 +87,7 @@ export default recipe(
Array length: {derive(my_array, (arr) => arr.length)}
- Current values: {my_array.map((e) => (
- - {e}
- ))}
+ Current values: {my_array.map((e) => - {e.name}
)}
diff --git a/packages/runner/src/data-updating.ts b/packages/runner/src/data-updating.ts
index 19b264ac0..e0524df7e 100644
--- a/packages/runner/src/data-updating.ts
+++ b/packages/runner/src/data-updating.ts
@@ -229,6 +229,10 @@ export function normalizeAndDiff(
// Handle arrays
if (Array.isArray(newValue)) {
+ console.log("[normalizeAndDiff] Array case - newValue:", newValue);
+ console.log("[normalizeAndDiff] Array case - currentValue:", currentValue);
+ console.log("[normalizeAndDiff] Array case - link:", link);
+
// If the current value is not an array, set it to an empty array
if (!Array.isArray(currentValue)) {
changes.push({ location: link, value: [] });
@@ -320,28 +324,43 @@ export function normalizeAndDiff(
if (
link.path.length > 0 && link.path[link.path.length - 1] === "length"
) {
+ console.log("[normalizeAndDiff] Length property case");
+ console.log("[normalizeAndDiff] link.path:", link.path);
+ console.log("[normalizeAndDiff] newValue:", newValue);
+
const maybeCurrentArray = tx.readValueOrThrow({
...link,
path: link.path.slice(0, -1),
});
+ console.log("[normalizeAndDiff] Parent array:", maybeCurrentArray);
+
if (Array.isArray(maybeCurrentArray)) {
const currentLength = maybeCurrentArray.length;
const newLength = newValue as number;
+ console.log("[normalizeAndDiff] currentLength:", currentLength);
+ console.log("[normalizeAndDiff] newLength:", newLength);
+
if (currentLength !== newLength) {
changes.push({ location: link, value: newLength });
+ console.log("[normalizeAndDiff] Array length changed, need to update elements");
+ console.log("[normalizeAndDiff] Loop from", Math.min(currentLength, newLength), "to", Math.max(currentLength, newLength));
+
for (
let i = Math.min(currentLength, newLength);
i < Math.max(currentLength, newLength);
i++
) {
+ console.log("[normalizeAndDiff] Adding undefined at path:", [...link.path.slice(0, -1), i.toString()]);
changes.push({
- location: { ...link, path: [...link.path, i.toString()] },
+ location: { ...link, path: [...link.path.slice(0, -1), i.toString()] },
value: undefined,
});
}
return changes;
}
- } // else, i.e. parent is not an array: fall through to the primitive case
+ } else {
+ console.log("[normalizeAndDiff] Parent is not an array, falling through");
+ }
}
// Handle primitive values and other cases (Object.is handles NaN and -0)
diff --git a/packages/runner/src/storage/transaction-shim.ts b/packages/runner/src/storage/transaction-shim.ts
index 71264c106..4848044ae 100644
--- a/packages/runner/src/storage/transaction-shim.ts
+++ b/packages/runner/src/storage/transaction-shim.ts
@@ -205,6 +205,7 @@ class TransactionReader implements ITransactionReader {
// Path-based logic
if (!address.path.length) {
+ console.error("Path must not be empty for address:", address);
const notFoundError: INotFoundError = new Error(
`Path must not be empty`,
) as INotFoundError;
@@ -310,6 +311,20 @@ class TransactionWriter extends TransactionReader
throw new Error(`Failed to get or create document: ${address.id}`);
}
+ if (!address.path.length) {
+ console.error("Path must not be empty for address:", address);
+ console.error("Value: ", value);
+ console.error("value is object:", isObject(value));
+ console.error("'value' in value:", "value" in value);
+ console.error("value.value:", value?.value);
+ }
+
+ // Rewrite creating new documents as setting the value
+ if (address.path.length === 0 && isObject(value) && "value" in value) {
+ address = { ...address, path: ["value"] };
+ value = value.value;
+ }
+
// Path-based logic
if (!address.path.length) {
const notFoundError: INotFoundError = new Error(
@@ -565,31 +580,48 @@ export class ExtendedStorageTransaction implements IExtendedStorageTransaction {
): void {
const writeResult = this.tx.write(address, value);
if (writeResult.error && writeResult.error.name === "NotFoundError") {
+ console.log("[writeOrThrow] NotFoundError retry logic");
+ console.log("[writeOrThrow] address:", address);
+ console.log("[writeOrThrow] value:", value);
+ console.log("[writeOrThrow] error.path:", writeResult.error.path);
+
// Create parent entries if needed
const lastValidPath = writeResult.error.path;
const valueObj = lastValidPath
? this.readValueOrThrow({ ...address, path: lastValidPath })
: {};
+ console.log("[writeOrThrow] valueObj after read:", valueObj);
+
if (!isRecord(valueObj)) {
throw new Error(
`Value at path ${address.path.join("/")} is not an object`,
);
}
const remainingPath = address.path.slice(lastValidPath?.length ?? 0);
+ console.log("[writeOrThrow] remainingPath:", remainingPath);
+
if (remainingPath.length === 0) {
throw new Error(
`Invalid error path: ${lastValidPath?.join("/")}`,
);
}
const lastKey = remainingPath.pop()!;
+ console.log("[writeOrThrow] lastKey:", lastKey);
+
let nextValue = valueObj;
for (const key of remainingPath) {
+ console.log("[writeOrThrow] Creating structure for key:", key);
+ console.log("[writeOrThrow] typeof Number(key):", typeof Number(key));
+ console.log("[writeOrThrow] Number.isNaN(Number(key)):", Number.isNaN(Number(key)));
nextValue =
nextValue[key] =
- (typeof Number(key) === "number" ? [] : {}) as typeof nextValue;
+ (!Number.isNaN(Number(key)) ? [] : {}) as typeof nextValue;
}
nextValue[lastKey] = value;
+ console.log("[writeOrThrow] Final valueObj to write:", valueObj);
+
const parentAddress = { ...address, path: lastValidPath ?? [] };
+ console.log("[writeOrThrow] parentAddress:", parentAddress);
const writeResultRetry = this.tx.write(parentAddress, valueObj);
if (writeResultRetry.error) {
throw writeResultRetry.error;
@@ -603,6 +635,10 @@ export class ExtendedStorageTransaction implements IExtendedStorageTransaction {
address: IMemorySpaceAddress,
value: JSONValue | undefined,
): void {
+ console.log("[writeValueOrThrow] Input address:", address);
+ console.log("[writeValueOrThrow] Input path:", address.path);
+ console.log("[writeValueOrThrow] Input value:", value);
+ console.log("[writeValueOrThrow] Transformed path:", ["value", ...address.path]);
this.writeOrThrow({ ...address, path: ["value", ...address.path] }, value);
}
From 62f09ca9922ac0a1594303e32edd8214ff8ddaf6 Mon Sep 17 00:00:00 2001
From: Ellyse
Date: Wed, 16 Jul 2025 14:33:16 -0700
Subject: [PATCH 2/2] fixed the path generation when setting array length to 0,
fixed array detection in retry logic, cherrypick bernis change for handling
for root writes with value property
---
.../runner/integration/array_push.test.ts | 27 ++++------
.../runner/integration/array_push.test.tsx | 53 ++++---------------
packages/runner/src/data-updating.ts | 21 +-------
.../runner/src/storage/transaction-shim.ts | 30 -----------
4 files changed, 22 insertions(+), 109 deletions(-)
diff --git a/packages/runner/integration/array_push.test.ts b/packages/runner/integration/array_push.test.ts
index b57baab06..e7a0a5d96 100644
--- a/packages/runner/integration/array_push.test.ts
+++ b/packages/runner/integration/array_push.test.ts
@@ -26,12 +26,11 @@ const MEMORY_WS_URL = `${
const SPACE_NAME = "runner_integration";
const TOTAL_COUNT = 100; // how many elements we push to the array
-const TIMEOUT_MS = 60000; // timeout for the test in ms
+const TIMEOUT_MS = 30000; // timeout for the test in ms
console.log("Array Push Test");
console.log(`Connecting to: ${MEMORY_WS_URL}`);
console.log(`Toolshed URL: ${TOOLSHED_URL}`);
-console.log(`Space Name: ${SPACE_NAME}`);
// Set up timeout
const timeoutPromise = new Promise((_, reject) => {
@@ -93,22 +92,18 @@ async function runTest() {
console.log("Result charm ID:", getEntityId(charm));
// Wait so we can load the page on the browser
- await new Promise((resolve) => setTimeout(resolve, 15000));
+ // await new Promise((resolve) => setTimeout(resolve, 10000));
- // BATCH VERSION - causes errors
+ // Get the handler stream and send some numbers
const pushHandlerStream = charm.key("pushHandler");
- console.log("Sending event to push all items at once");
- pushHandlerStream.send({});
-
- // ORIGINAL VERSION - works without errors
- // const pushHandlerStream = charm.key("pushHandler");
- // let sendCount = 0;
- // // Loop, sending numbers one by one
- // for (let i = 0; i < TOTAL_COUNT; i++) {
- // console.log(`Sending value: ${i}`);
- // pushHandlerStream.send({ value: i });
- // sendCount++;
- // }
+ let sendCount = 0;
+
+ // Loop, sending numbers one by one
+ for (let i = 0; i < TOTAL_COUNT; i++) {
+ console.log(`Sending value: ${i}`);
+ pushHandlerStream.send({ value: i });
+ sendCount++;
+ }
console.log("Waiting for runtime to finish and storage to sync...");
await runtime.idle();
diff --git a/packages/runner/integration/array_push.test.tsx b/packages/runner/integration/array_push.test.tsx
index 3e868854a..8978f3e26 100644
--- a/packages/runner/integration/array_push.test.tsx
+++ b/packages/runner/integration/array_push.test.tsx
@@ -9,8 +9,6 @@ import {
UI,
} from "commontools";
-const ARRAY_LENGTH=1;
-
// dummy input schema
const InputSchema = {
type: "object",
@@ -23,13 +21,7 @@ const OutputSchema = {
properties: {
my_array: {
type: "array",
- items: {
- type: "object",
- properties: {
- name: { type: "string" },
- value: { type: "number" },
- },
- },
+ items: { type: "number" },
default: [],
asCell: true,
},
@@ -42,41 +34,14 @@ export default recipe(
InputSchema,
OutputSchema,
() => {
- const my_array = cell<{ name: string; value: number }[]>([]);
+ const my_array = cell([]);
- // BATCH VERSION - causes "Path must not be empty" errors
- const pushHandler = handler({}, {
- type: "object",
- properties: {
- array: {
- type: "array",
- asCell: true,
- items: {
- type: "object",
- properties: { name: { type: "string" }, value: { type: "number" } },
- },
- },
+ const pushHandler = handler(
+ ({ value }: { value: number }, { array }: { array: number[] }) => {
+ console.log("Pushing value:", value);
+ array.push(value);
},
- required: ["array"],
- }, (_, { array }) => {
- console.log("[pushHandler] Pushing all items at once");
- const itemsToAdd = Array.from({ length: ARRAY_LENGTH }, (_, i) => ({
- name: `Item ${i}`,
- value: i,
- }));
- console.log("[pushHandler] Before push - array:", array);
- console.log("[pushHandler] Items to add:", itemsToAdd);
- array.push(...itemsToAdd);
- // array.push(itemsToAdd[0]);
- console.log("[pushHandler] After push - array.get():", array.get());
- });
-
- // const pushHandler = handler(
- // ({ value }: { value: number }, { array }: { array: { name: string; value: number }[] }) => {
- // console.log("Pushing value:", value);
- // array.push({ name: `Item ${value}`, value: value });
- // },
- // );
+ );
// Return the recipe
return {
@@ -87,7 +52,9 @@ export default recipe(
Array length: {derive(my_array, (arr) => arr.length)}
- Current values: {my_array.map((e) => - {e.name}
)}
+ Current values: {my_array.map((e) => (
+ - {e}
+ ))}
diff --git a/packages/runner/src/data-updating.ts b/packages/runner/src/data-updating.ts
index e0524df7e..c457a15fc 100644
--- a/packages/runner/src/data-updating.ts
+++ b/packages/runner/src/data-updating.ts
@@ -229,10 +229,6 @@ export function normalizeAndDiff(
// Handle arrays
if (Array.isArray(newValue)) {
- console.log("[normalizeAndDiff] Array case - newValue:", newValue);
- console.log("[normalizeAndDiff] Array case - currentValue:", currentValue);
- console.log("[normalizeAndDiff] Array case - link:", link);
-
// If the current value is not an array, set it to an empty array
if (!Array.isArray(currentValue)) {
changes.push({ location: link, value: [] });
@@ -324,33 +320,20 @@ export function normalizeAndDiff(
if (
link.path.length > 0 && link.path[link.path.length - 1] === "length"
) {
- console.log("[normalizeAndDiff] Length property case");
- console.log("[normalizeAndDiff] link.path:", link.path);
- console.log("[normalizeAndDiff] newValue:", newValue);
-
const maybeCurrentArray = tx.readValueOrThrow({
...link,
path: link.path.slice(0, -1),
});
- console.log("[normalizeAndDiff] Parent array:", maybeCurrentArray);
-
if (Array.isArray(maybeCurrentArray)) {
const currentLength = maybeCurrentArray.length;
const newLength = newValue as number;
- console.log("[normalizeAndDiff] currentLength:", currentLength);
- console.log("[normalizeAndDiff] newLength:", newLength);
-
if (currentLength !== newLength) {
changes.push({ location: link, value: newLength });
- console.log("[normalizeAndDiff] Array length changed, need to update elements");
- console.log("[normalizeAndDiff] Loop from", Math.min(currentLength, newLength), "to", Math.max(currentLength, newLength));
-
for (
let i = Math.min(currentLength, newLength);
i < Math.max(currentLength, newLength);
i++
) {
- console.log("[normalizeAndDiff] Adding undefined at path:", [...link.path.slice(0, -1), i.toString()]);
changes.push({
location: { ...link, path: [...link.path.slice(0, -1), i.toString()] },
value: undefined,
@@ -358,9 +341,7 @@ export function normalizeAndDiff(
}
return changes;
}
- } else {
- console.log("[normalizeAndDiff] Parent is not an array, falling through");
- }
+ } // else, i.e. parent is not an array: fall through to the primitive case
}
// Handle primitive values and other cases (Object.is handles NaN and -0)
diff --git a/packages/runner/src/storage/transaction-shim.ts b/packages/runner/src/storage/transaction-shim.ts
index 4848044ae..fffc246b1 100644
--- a/packages/runner/src/storage/transaction-shim.ts
+++ b/packages/runner/src/storage/transaction-shim.ts
@@ -205,7 +205,6 @@ class TransactionReader implements ITransactionReader {
// Path-based logic
if (!address.path.length) {
- console.error("Path must not be empty for address:", address);
const notFoundError: INotFoundError = new Error(
`Path must not be empty`,
) as INotFoundError;
@@ -311,14 +310,6 @@ class TransactionWriter extends TransactionReader
throw new Error(`Failed to get or create document: ${address.id}`);
}
- if (!address.path.length) {
- console.error("Path must not be empty for address:", address);
- console.error("Value: ", value);
- console.error("value is object:", isObject(value));
- console.error("'value' in value:", "value" in value);
- console.error("value.value:", value?.value);
- }
-
// Rewrite creating new documents as setting the value
if (address.path.length === 0 && isObject(value) && "value" in value) {
address = { ...address, path: ["value"] };
@@ -580,48 +571,31 @@ export class ExtendedStorageTransaction implements IExtendedStorageTransaction {
): void {
const writeResult = this.tx.write(address, value);
if (writeResult.error && writeResult.error.name === "NotFoundError") {
- console.log("[writeOrThrow] NotFoundError retry logic");
- console.log("[writeOrThrow] address:", address);
- console.log("[writeOrThrow] value:", value);
- console.log("[writeOrThrow] error.path:", writeResult.error.path);
-
// Create parent entries if needed
const lastValidPath = writeResult.error.path;
const valueObj = lastValidPath
? this.readValueOrThrow({ ...address, path: lastValidPath })
: {};
- console.log("[writeOrThrow] valueObj after read:", valueObj);
-
if (!isRecord(valueObj)) {
throw new Error(
`Value at path ${address.path.join("/")} is not an object`,
);
}
const remainingPath = address.path.slice(lastValidPath?.length ?? 0);
- console.log("[writeOrThrow] remainingPath:", remainingPath);
-
if (remainingPath.length === 0) {
throw new Error(
`Invalid error path: ${lastValidPath?.join("/")}`,
);
}
const lastKey = remainingPath.pop()!;
- console.log("[writeOrThrow] lastKey:", lastKey);
-
let nextValue = valueObj;
for (const key of remainingPath) {
- console.log("[writeOrThrow] Creating structure for key:", key);
- console.log("[writeOrThrow] typeof Number(key):", typeof Number(key));
- console.log("[writeOrThrow] Number.isNaN(Number(key)):", Number.isNaN(Number(key)));
nextValue =
nextValue[key] =
(!Number.isNaN(Number(key)) ? [] : {}) as typeof nextValue;
}
nextValue[lastKey] = value;
- console.log("[writeOrThrow] Final valueObj to write:", valueObj);
-
const parentAddress = { ...address, path: lastValidPath ?? [] };
- console.log("[writeOrThrow] parentAddress:", parentAddress);
const writeResultRetry = this.tx.write(parentAddress, valueObj);
if (writeResultRetry.error) {
throw writeResultRetry.error;
@@ -635,10 +609,6 @@ export class ExtendedStorageTransaction implements IExtendedStorageTransaction {
address: IMemorySpaceAddress,
value: JSONValue | undefined,
): void {
- console.log("[writeValueOrThrow] Input address:", address);
- console.log("[writeValueOrThrow] Input path:", address.path);
- console.log("[writeValueOrThrow] Input value:", value);
- console.log("[writeValueOrThrow] Transformed path:", ["value", ...address.path]);
this.writeOrThrow({ ...address, path: ["value", ...address.path] }, value);
}