From 71d656eb5961b9ecce97bc60fa1ce50222526ad8 Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Mon, 13 May 2024 10:23:26 +0800 Subject: [PATCH 01/23] object: flattenObject --- src/object.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/object.ts b/src/object.ts index 3d9df55..bbda775 100644 --- a/src/object.ts +++ b/src/object.ts @@ -104,3 +104,26 @@ export const reduceObject = ( nf ); }; + +export const flattenObject = ( + proxy: SheetProxy, + obj: CellObject, + name = "flatten", + nf?: NF +) => { + const coll = collector, NF>>(proxy); + return proxy.map( + [obj], + (cells) => { + const keys = Object.keys(cells); + const values = Object.values(cells); + coll( + proxy.mapNoPrevious(values, (..._cells) => + Object.fromEntries(_cells.map((v, i) => [keys[i], v])) + ) + ); + }, + name, + nf + ); +}; From 546aa4543c7a92de47d68a56e04a45cf07c7cf26 Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Mon, 13 May 2024 10:26:09 +0800 Subject: [PATCH 02/23] object: use async in outer maps --- src/object.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/object.ts b/src/object.ts index bbda775..4cac675 100644 --- a/src/object.ts +++ b/src/object.ts @@ -11,7 +11,6 @@ export type CellObject = AnyCell>>; export const mapObject = ( proxy: SheetProxy, obj: CellObject, - // @todo return type fn: ( key: string, value: T, @@ -22,7 +21,7 @@ export const mapObject = ( ): MapCell>, NF> => proxy.map( [obj], - (cells, prev) => { + async (cells, prev) => { const set = new Set(Object.keys(prev || {})); const res = Object.fromEntries( Object.entries(cells).map(([k, v]) => { @@ -83,7 +82,7 @@ export const reduceObject = ( const coll = collector>(proxy); return proxy.mapNoPrevious( [obj], - (cells) => { + async (cells) => { const keys = Object.keys(cells); const values = Object.values(cells); // console.log({ reduce: keys, name, count: proxy._sheet.stats.count }); @@ -112,9 +111,9 @@ export const flattenObject = ( nf?: NF ) => { const coll = collector, NF>>(proxy); - return proxy.map( + return proxy.mapNoPrevious( [obj], - (cells) => { + async (cells) => { const keys = Object.keys(cells); const values = Object.values(cells); coll( From 96e64d1f20e05de4baa554ee01f4e8bb50279b24 Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Mon, 13 May 2024 10:43:05 +0800 Subject: [PATCH 03/23] object: fix flattenObject, add test --- src/object.test.ts | 17 ++++++++++++++--- src/object.ts | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/object.test.ts b/src/object.test.ts index 6050607..d08135a 100644 --- a/src/object.test.ts +++ b/src/object.test.ts @@ -4,13 +4,13 @@ import { expect, test } from "vitest"; import { cellify, uncellify } from "./cellify"; import { Debugger } from "./debug"; import { isEqual } from "./isEqual.test"; -import { asyncReduce, mapObject, reduceObject } from "./object"; +import { asyncReduce, flattenObject, mapObject, reduceObject } from "./object"; import { delayed } from "./promise"; import { SheetProxy } from "./proxy"; import { Sheet } from "./sheet"; test("mapObject", async () => { - const sheet = new Sheet(); + const sheet = new Sheet(isEqual); const proxy = new SheetProxy(sheet); const obj = cellify(proxy, { a: 1, b: "foo", c: "bar" }); @@ -60,7 +60,7 @@ test("asyncReduce", async () => { test( "reduceObject", async () => { - const sheet = new Sheet(); + const sheet = new Sheet(isEqual); const debug = new Debugger(sheet); const proxy = new SheetProxy(sheet); @@ -89,3 +89,14 @@ test( }, { timeout: 1000 } ); + +test("flattenObject", async () => { + const sheet = new Sheet(isEqual); + const debug = new Debugger(sheet); + const proxy = new SheetProxy(sheet); + + const l = cellify(proxy, { a: 1, b: 2, c: 3 }); + const f = flattenObject(proxy, l); + await expect(f.get()).resolves.toEqual({ a: 1, b: 2, c: 3 }); + expect(sheet.stats).toEqual({ count: 6, size: 6 }); // 3+1 array +1 sum +1 pointer +}); diff --git a/src/object.ts b/src/object.ts index 4cac675..cb2f8e8 100644 --- a/src/object.ts +++ b/src/object.ts @@ -116,7 +116,7 @@ export const flattenObject = ( async (cells) => { const keys = Object.keys(cells); const values = Object.values(cells); - coll( + return coll( proxy.mapNoPrevious(values, (..._cells) => Object.fromEntries(_cells.map((v, i) => [keys[i], v])) ) From 62cdf9c2754cf66dcad985e175fbe3880814388e Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Mon, 13 May 2024 10:45:37 +0800 Subject: [PATCH 04/23] object: improve flattenObject test --- src/object.test.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/object.test.ts b/src/object.test.ts index d08135a..09bc4d6 100644 --- a/src/object.test.ts +++ b/src/object.test.ts @@ -95,8 +95,12 @@ test("flattenObject", async () => { const debug = new Debugger(sheet); const proxy = new SheetProxy(sheet); - const l = cellify(proxy, { a: 1, b: 2, c: 3 }); - const f = flattenObject(proxy, l); - await expect(f.get()).resolves.toEqual({ a: 1, b: 2, c: 3 }); - expect(sheet.stats).toEqual({ count: 6, size: 6 }); // 3+1 array +1 sum +1 pointer + const obj = cellify(proxy, { a: 1, b: 2, c: 3 }); + const f = flattenObject(proxy, obj); + await expect(f.consolidatedValue).resolves.toEqual({ a: 1, b: 2, c: 3 }); + expect(sheet.stats).toEqual({ count: 6, size: 6 }); // 3+1 obj +1 flatten +1 pointer + + await (await obj.consolidatedValue).a.set(4); + await expect(f.get()).resolves.toEqual({ a: 4, b: 2, c: 3 }); + expect(sheet.stats).toEqual({ count: 6, size: 6 }); // unchanged }); From e8ec084cfd3f92d9425ee343716648cd050bb225 Mon Sep 17 00:00:00 2001 From: Cheun Marec Date: Mon, 13 May 2024 12:20:02 +0900 Subject: [PATCH 05/23] export flattenObject --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index f0d515b..1bbc61f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -59,6 +59,7 @@ export { asyncReduce, mapObject, reduceObject, + flattenObject, type CellObject } from "./object"; export { simplifier } from "./printer"; From d2231b7718caa90467544c5eeb8ecd2c432329f4 Mon Sep 17 00:00:00 2001 From: Cheun Marec Date: Mon, 13 May 2024 12:20:18 +0900 Subject: [PATCH 06/23] extend map signature --- src/proxy.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/proxy.ts b/src/proxy.ts index 64ff258..804e524 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -167,6 +167,31 @@ export class SheetProxy { name?: string, noFail?: NF ): MapCell; + map( + dependencies: [ + AnyCell, + AnyCell, + AnyCell, + AnyCell, + AnyCell, + AnyCell, + AnyCell, + AnyCell + ], + computeFn: ( + arg1: D1, + arg2: D2, + arg3: D3, + arg4: D4, + arg5: D5, + arg6: D6, + arg7: D7, + arg8: D8, + prev?: V + ) => V | Promise> | AnyCell, + name?: string, + noFail?: NF + ): MapCell; /** * map a list to cells to a new cells using the compute function. From bb0534a31c5ec4ccedf099dd0caa147e6c2632d0 Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Mon, 13 May 2024 16:29:19 +0800 Subject: [PATCH 07/23] index: sort imports --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 1bbc61f..c3226a9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -57,9 +57,9 @@ export { jsonStringify } from "./json"; export { nextSubscriber } from "./next"; export { asyncReduce, + flattenObject, mapObject, reduceObject, - flattenObject, type CellObject } from "./object"; export { simplifier } from "./printer"; From c1a0eaa8c5b7d7e76b2b624a980118f077a41c23 Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Tue, 14 May 2024 11:10:59 +0800 Subject: [PATCH 08/23] cellify: UncellifyOptions --- src/cellify.ts | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/cellify.ts b/src/cellify.ts index 83eded4..20fcdd5 100644 --- a/src/cellify.ts +++ b/src/cellify.ts @@ -1,4 +1,11 @@ -import { type AnyCell, Cell, type MapCell, type ValueCell } from "./cell"; +import { + type AnyCell, + Cell, + type CellResult, + type MapCell, + type Pending, + type ValueCell +} from "./cell"; import { collector } from "./gc"; import type { SheetProxy } from "./proxy"; @@ -63,24 +70,32 @@ export const cellify = ( ) as Cellified; }; +export type UncellifyOptions = { + getter: (c: AnyCell) => Pending | CellResult; +}; + /** * uncellify is used in tests to flatten a value tree that contains multiple cells. * @param v any value * @returns value without cells */ export const uncellify = async ( - v: T | AnyCell + v: T | AnyCell, + options: UncellifyOptions = { getter: (cell) => cell.value } ): Promise> => { - const value = v instanceof Cell ? await v.consolidatedValue : v; + const value = v instanceof Cell ? await options.getter(v) : v; if (value instanceof Error) throw value; if (Array.isArray(value)) - return Promise.all(value.map((_element) => uncellify(_element))) as Promise< - Uncellified - >; + return Promise.all( + value.map((_element) => uncellify(_element, options)) + ) as Promise>; if (isObject(value)) return Object.fromEntries( await Promise.all( - Object.entries(value).map(async ([k, vv]) => [k, await uncellify(vv)]) + Object.entries(value).map(async ([k, vv]) => [ + k, + await uncellify(vv, options) + ]) ) ); // Classes, null or base types (string, number, ...) From e87e16406472c5fe59e845d233f0d7c7f184ed9a Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Wed, 15 May 2024 20:19:20 +0800 Subject: [PATCH 09/23] initial: new helper --- src/initial.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/initial.ts diff --git a/src/initial.ts b/src/initial.ts new file mode 100644 index 0000000..4ddd19d --- /dev/null +++ b/src/initial.ts @@ -0,0 +1,19 @@ +import type { AnyCell, MapCell } from "./cell"; +import type { SheetProxy } from "./proxy"; + +export const initialValue = ( + proxy: SheetProxy, + v0: T, + v: AnyCell, + name = "initial" +): MapCell => { + const cell = proxy.new(v0, name); + v.subscribe((v) => { + // We do not propagate errors yet. + if (v instanceof Error) return; + cell.set(v); + }); + // We fake being a MapCell to prevent setting the cell + // outside of this function. + return cell as unknown as MapCell; +}; From bc37a892b55429670d64dfcd90e02c0d800b6fef Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Wed, 15 May 2024 20:24:32 +0800 Subject: [PATCH 10/23] index: export initialValue --- src/index.ts | 1 + src/initial.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index c3226a9..de688eb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -53,6 +53,7 @@ export { export { clock, clockWork, type Clock } from "./clock"; export { copy } from "./copy"; export { Debugger } from "./debug"; +export { initialValue } from "./initial"; export { jsonStringify } from "./json"; export { nextSubscriber } from "./next"; export { diff --git a/src/initial.ts b/src/initial.ts index 4ddd19d..457be71 100644 --- a/src/initial.ts +++ b/src/initial.ts @@ -3,7 +3,7 @@ import type { SheetProxy } from "./proxy"; export const initialValue = ( proxy: SheetProxy, - v0: T, + v0: T | AnyCell, v: AnyCell, name = "initial" ): MapCell => { From b6f8fed54be7a0a152f124ea9d62ab0e52d98009 Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Sun, 19 May 2024 08:29:13 +0800 Subject: [PATCH 11/23] array: mapFlat using mapNoPrevious --- src/array.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/array.ts b/src/array.ts index 89091d1..c1dfd15 100644 --- a/src/array.ts +++ b/src/array.ts @@ -273,7 +273,7 @@ export const mapFlat = ( nf?: NF ) => { const coll = collector>(proxy); - return proxy.map( + return proxy.mapNoPrevious( [arr], (cells) => coll(proxy.mapNoPrevious(cells, (..._cells) => _cells)), name, From b01bd1c497aa7a336c09c2232406d76efbe9cc2c Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Sun, 19 May 2024 08:29:30 +0800 Subject: [PATCH 12/23] cellify: update comment --- src/cellify.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cellify.ts b/src/cellify.ts index 20fcdd5..3070e9a 100644 --- a/src/cellify.ts +++ b/src/cellify.ts @@ -29,14 +29,15 @@ export type Uncellified = T extends AnyCell : U : T; -// @todo is type only if true -// exclude classes +// isObject returns true if the value is a regular JavaScript Object, +// but not null neither a custom Class instance. export const isObject = ( v: unknown ): v is Record => typeof v === "object" && v !== null && v.constructor?.name === "Object"; const errIsCell = new Error("value is cell"); + /** * cellify converts any value to a Cellified value where each array or record * becomes a Cell in canonical form. From 7061c14d660c6ed8bca5bf604543416fb8a55044 Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Sun, 19 May 2024 08:29:53 +0800 Subject: [PATCH 13/23] proxy: destroy() uses collect --- src/proxy.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/proxy.ts b/src/proxy.ts index 804e524..f220d8c 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -275,13 +275,9 @@ export class SheetProxy { /** * destroy the Proxy and free memory. - * @todo check for memory leaks */ destroy() { - // if (!this?._sheet) { - // throw new Error(`missing: ${this?._name}`); - // } - this._sheet.delete(...this._list); + this._sheet.collect(...this._list); this._list = []; } } From 202546631f3c25f271b3ea140e032424dc208e61 Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Mon, 20 May 2024 09:27:48 +0800 Subject: [PATCH 14/23] sheet: comment collect log --- src/sheet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sheet.ts b/src/sheet.ts index 39fa50b..2aff5bd 100644 --- a/src/sheet.ts +++ b/src/sheet.ts @@ -957,7 +957,7 @@ export class Sheet { this.debug(ids, "collect", { collecting: ids, cells: input }); for (const id of ids) { const deps = this.g.partialTopologicalSort(id); - this.debug(undefined, "collect", { deps }); + // this.debug(undefined, "collect", { deps }); for (const dep of deps) this._gc.add(dep); } } From e0bb3016176d05b5f28e741b5378b7c620c9e3e3 Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Tue, 21 May 2024 10:53:37 +0800 Subject: [PATCH 15/23] json: serialize as number up to 1e9 --- src/json.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/json.ts b/src/json.ts index de88b88..e03e147 100644 --- a/src/json.ts +++ b/src/json.ts @@ -57,7 +57,7 @@ export const jsonStringify = ( case "symbol": break; case "bigint": - out += v < 1_000_000n ? Number(v) : `"${v.toString()}"`; + out += v < 1_000_000_000n ? Number(v) : `"${v.toString()}"`; break; default: out += JSON.stringify(v); From c20e4c844ae46686a2dc46b8d7b6b57afc695ced Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Tue, 21 May 2024 10:54:15 +0800 Subject: [PATCH 16/23] debouncer: re-instate utility function --- src/debouncer.ts | 30 ++++++++++++++++++++++++++++++ src/index.ts | 1 + 2 files changed, 31 insertions(+) create mode 100644 src/debouncer.ts diff --git a/src/debouncer.ts b/src/debouncer.ts new file mode 100644 index 0000000..a35553c --- /dev/null +++ b/src/debouncer.ts @@ -0,0 +1,30 @@ +import type { ValueCell } from "./cell"; + +export type Debouncer = (cb: (v: T) => void | Promise, v: T) => void; + +/** + * debouncer creates a debounce function that will execute a callback after a _delay_. + * + * Create with `const debounce = debouncer()` + * and use as `debounce(cb, v, delay)`. + * @param cb callback + * @param v value passed to callback + * @param delay optional delay in ms, default: 750 + */ +export const debouncer = ( + delay = 750, + working: ValueCell | undefined = undefined +): Debouncer => { + // console.log({ setting: delay }); + let timer: ReturnType; + return (cb: (v: T) => void | Promise, v: T) => { + // console.log({ called: delay }); + if (working !== undefined) working.set(true); + clearTimeout(timer); + timer = setTimeout(async () => { + // console.log({ deb: delay }); + await cb(v); + if (working !== undefined) working.set(false); + }, delay); + }; +}; diff --git a/src/index.ts b/src/index.ts index de688eb..9409b49 100644 --- a/src/index.ts +++ b/src/index.ts @@ -52,6 +52,7 @@ export { } from "./cellify"; export { clock, clockWork, type Clock } from "./clock"; export { copy } from "./copy"; +export { debouncer, type Debouncer } from "./debouncer"; export { Debugger } from "./debug"; export { initialValue } from "./initial"; export { jsonStringify } from "./json"; From a0197b7ae3c4a177c5b852498f8230416cf69a46 Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Fri, 24 May 2024 19:19:55 +0800 Subject: [PATCH 17/23] cellify: errorsAsValues option --- src/cellify.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/cellify.ts b/src/cellify.ts index 3070e9a..4ee386a 100644 --- a/src/cellify.ts +++ b/src/cellify.ts @@ -73,6 +73,7 @@ export const cellify = ( export type UncellifyOptions = { getter: (c: AnyCell) => Pending | CellResult; + errorsAsValues?: boolean; }; /** @@ -85,7 +86,10 @@ export const uncellify = async ( options: UncellifyOptions = { getter: (cell) => cell.value } ): Promise> => { const value = v instanceof Cell ? await options.getter(v) : v; - if (value instanceof Error) throw value; + if (value instanceof Error) { + if (options?.errorsAsValues) return value as Uncellified; + throw value; + } if (Array.isArray(value)) return Promise.all( value.map((_element) => uncellify(_element, options)) From 8e48ca8a4c92ffb27140ae239b06ca32fc4b8752 Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Sun, 26 May 2024 12:46:09 +0800 Subject: [PATCH 18/23] sheet: collection() forces collection, fix proxy tests --- src/proxy.test.ts | 27 ++++++++++++++++++--------- src/sheet.ts | 16 ++++++++++------ 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/proxy.test.ts b/src/proxy.test.ts index 7523e49..3357f4b 100644 --- a/src/proxy.test.ts +++ b/src/proxy.test.ts @@ -29,23 +29,26 @@ test("native proxy", () => { expect(trigger).toBeTruthy(); }); -test("SheetProxy", () => { - const store = new Sheet(); - const value = store.new(2); - const proxy = new SheetProxy(store); +test("SheetProxy", async () => { + const sheet = new Sheet(); + const value = sheet.new(2); + const proxy = new SheetProxy(sheet); const double = proxy.map([value], (x) => 2 * x); const add = proxy.map([double], (x) => x + 1); proxy.destroy(); + // Force GC collection + sheet.collection(); + await proxy.working.wait(); value.set(3); // will not update detached/deleted cells expect(double.value).toBe(4); expect(add.value).toBe(5); }); test("Sheet multiple async updates", async () => { - const store = new Sheet(); - const value = store.new(2); + const sheet = new Sheet(); + const value = sheet.new(2); const double = value.map(async (x) => delayed(2 * x, 50)); - const add = store.map([value, double], (value, double) => + const add = sheet.map([value, double], (value, double) => delayed(value + double + 1, 30) ); // console.log("value", value.id, "double", double.id, "add", add.id); @@ -54,7 +57,7 @@ test("Sheet multiple async updates", async () => { value.set(3); expect(await add.consolidatedValue).toBe(10); value.set(4); - await store.wait(); + await sheet.wait(); expect(await add.consolidatedValue).toBe(13); }); @@ -98,9 +101,13 @@ test("proxy deletion", async () => { const c = sub.map([a, b], async (a, b) => a + b); expect(sheet.stats).toEqual({ count: 3, size: 3 }); sub.destroy(); + await proxy.working.wait(); expect(sheet.stats).toEqual({ count: 3, size: 2 }); await expect(b.get()).resolves.toBe(2); proxy.destroy(); + // Force collection. + sheet.collection(); + // Collection is not happening yet. expect(sheet.stats).toEqual({ count: 3, size: 0 }); }); @@ -113,5 +120,7 @@ test("proxy deletion with loop", async () => { const c = sub.map([a, b], async (a, b) => delayed(a + b, 5)); const d = proxy.map([c], async (v) => delayed(v * 2, 15)); expect(sheet.stats).toEqual({ count: 4, size: 4 }); - expect(() => sub.destroy()).toThrow("Cell has references"); + // Since we now collect the whole subgraph, there is no error. + sub.destroy(); + // expect(() => sub.destroy()).toThrow("Cell has references"); }); diff --git a/src/sheet.ts b/src/sheet.ts index 2aff5bd..e7f6906 100644 --- a/src/sheet.ts +++ b/src/sheet.ts @@ -567,12 +567,7 @@ export class Sheet { this._internalNotify(_result.done); // Collect garbage - if (this._gc.size) { - const l = Array.from(this._gc); - this.debug(l, "gc", { deleting: l }); - this._gc = new Set(); - this.delete(...l); - } + this.collection(); // End of the update release(); @@ -592,6 +587,15 @@ export class Sheet { ); } + collection() { + if (this._gc.size) { + const l = Array.from(this._gc); + this.debug(l, "gc", { deleting: l }); + this._gc = new Set(); + this.delete(...l); + } + } + private registerCancelAndDone( updatable: number[], computations: (V | Canceled | Error)[], From d56f679ad1adf8394b9185c66463d6670950331b Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Sun, 26 May 2024 12:48:28 +0800 Subject: [PATCH 19/23] cellify: uncellify uses consolidatedValue as getter by default --- src/cellify.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cellify.ts b/src/cellify.ts index 4ee386a..bd95fda 100644 --- a/src/cellify.ts +++ b/src/cellify.ts @@ -83,7 +83,7 @@ export type UncellifyOptions = { */ export const uncellify = async ( v: T | AnyCell, - options: UncellifyOptions = { getter: (cell) => cell.value } + options: UncellifyOptions = { getter: (cell) => cell.consolidatedValue } ): Promise> => { const value = v instanceof Cell ? await options.getter(v) : v; if (value instanceof Error) { From f0cba9ff8e95f6a34672632af3addcaf60b4ab50 Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Sun, 26 May 2024 13:03:46 +0800 Subject: [PATCH 20/23] cellify: test uncellify options --- src/cellify.test.ts | 18 +++++++++++++++++- src/cellify.ts | 9 ++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/cellify.test.ts b/src/cellify.test.ts index 3d638f5..65d099b 100644 --- a/src/cellify.test.ts +++ b/src/cellify.test.ts @@ -12,7 +12,7 @@ import { Sheet } from "./sheet"; type IsEqual = [T] extends [U] ? ([U] extends [T] ? true : false) : false; -test("", () => { +test("IsEqual type", () => { type T = { a: string[] }[]; type C = Cellified; type U = Uncellified; @@ -83,3 +83,19 @@ test("cellify failOnCell", async () => { const v = { a: [1, 2, 3], b: { c: { foo: proxy.new(1, "1"), bar: 1 } } }; expect(() => cellify(proxy, v, "cv", true)).toThrowError("value is cell"); }); + +test("cellify failOnError", async () => { + const sheet = new Sheet(); + const proxy = new SheetProxy(sheet); + const v = proxy.new(1); + // @ts-expect-error intentional + const m = v.map((v) => v.toLowerCase()); + // The standard uncellify call throws. + await expect(() => uncellify(m)).rejects.toThrow( + "toLowerCase is not a function" + ); + // But we retrieve the error with errorsAsValues. + await expect(uncellify(m, { errorsAsValues: true })).resolves.toBeInstanceOf( + Error + ); +}); diff --git a/src/cellify.ts b/src/cellify.ts index bd95fda..67671fd 100644 --- a/src/cellify.ts +++ b/src/cellify.ts @@ -72,7 +72,7 @@ export const cellify = ( }; export type UncellifyOptions = { - getter: (c: AnyCell) => Pending | CellResult; + getter?: (c: AnyCell) => Pending | CellResult; errorsAsValues?: boolean; }; @@ -83,9 +83,12 @@ export type UncellifyOptions = { */ export const uncellify = async ( v: T | AnyCell, - options: UncellifyOptions = { getter: (cell) => cell.consolidatedValue } + options: UncellifyOptions = {} ): Promise> => { - const value = v instanceof Cell ? await options.getter(v) : v; + const getter = options?.getter + ? options.getter + : (cell: AnyCell) => cell.consolidatedValue; + const value = v instanceof Cell ? await getter(v) : v; if (value instanceof Error) { if (options?.errorsAsValues) return value as Uncellified; throw value; From aa937d825d0365b5c97153f14edf0b133a1878dd Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Sun, 26 May 2024 14:57:49 +0800 Subject: [PATCH 21/23] initial: add test --- src/initial.test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/initial.test.ts diff --git a/src/initial.test.ts b/src/initial.test.ts new file mode 100644 index 0000000..27992f3 --- /dev/null +++ b/src/initial.test.ts @@ -0,0 +1,16 @@ +import { expect, test } from "vitest"; + +import { initialValue } from "./initial"; +import { isEqual } from "./isEqual.test"; +import { delayed, sleep } from "./promise"; +import { Sheet } from "./sheet"; + +test("initialValue", async () => { + const proxy = new Sheet(isEqual).newProxy(); + const a = proxy.new(1); + const b = proxy.new(delayed(2, 50)); + const c = initialValue(proxy, a, b); + expect(c.consolidatedValue).toBe(1); + await sleep(60); + expect(c.consolidatedValue).toBe(2); +}); From 2bb4fd5b74a176883de8e9bdddd64fe8af5998f8 Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Sun, 26 May 2024 15:01:57 +0800 Subject: [PATCH 22/23] debouncer: add test --- src/debouncer.test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/debouncer.test.ts diff --git a/src/debouncer.test.ts b/src/debouncer.test.ts new file mode 100644 index 0000000..d715d51 --- /dev/null +++ b/src/debouncer.test.ts @@ -0,0 +1,20 @@ +import { expect, test } from "vitest"; + +import { debouncer } from "./debouncer"; +import { isEqual } from "./isEqual.test"; +import { sleep } from "./promise"; +import { Sheet } from "./sheet"; + +test("debouncer", async () => { + const proxy = new Sheet(isEqual).newProxy(); + const waiting = proxy.new(false); + const deb = debouncer(20, waiting); + const v = proxy.new(0); + for (let i = 1; i <= 10; i++) { + deb((i) => v.set(i), i); + await sleep(5); + expect(v.consolidatedValue).toBe(0); + } + await sleep(30); + expect(v.consolidatedValue).toBe(10); +}); From 8662a867f0a7071dd845dee670420bf4a7dc6243 Mon Sep 17 00:00:00 2001 From: Henri Binsztok <808274+hbbio@users.noreply.github.com> Date: Sun, 26 May 2024 15:03:27 +0800 Subject: [PATCH 23/23] debouncer: also test waiting --- src/debouncer.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/debouncer.test.ts b/src/debouncer.test.ts index d715d51..0bbc838 100644 --- a/src/debouncer.test.ts +++ b/src/debouncer.test.ts @@ -10,11 +10,16 @@ test("debouncer", async () => { const waiting = proxy.new(false); const deb = debouncer(20, waiting); const v = proxy.new(0); + expect(waiting.consolidatedValue).toBe(false); + for (let i = 1; i <= 10; i++) { deb((i) => v.set(i), i); await sleep(5); expect(v.consolidatedValue).toBe(0); + expect(waiting.consolidatedValue).toBe(true); } + await sleep(30); expect(v.consolidatedValue).toBe(10); + expect(waiting.consolidatedValue).toBe(false); });