From ad3ec29b6bd26a7e367de8bef720b303c1c86c99 Mon Sep 17 00:00:00 2001 From: Cheun Marec Date: Thu, 4 Apr 2024 19:05:13 +0900 Subject: [PATCH] wip: package.json and uncellify renaming --- package.json | 49 +++---------- src/array.test.ts | 54 +++++++------- src/cell.ts | 10 +-- src/cellify.test.ts | 12 ++-- src/cellify.ts | 167 +++++++------------------------------------- src/index.ts | 7 +- src/object.test.ts | 22 +++--- 7 files changed, 90 insertions(+), 231 deletions(-) diff --git a/package.json b/package.json index c244d4e..0607adf 100644 --- a/package.json +++ b/package.json @@ -1,52 +1,21 @@ { "name": "@okcontract/cells", "version": "0.2.3", - "description": "Simplified reactive functional programming for the web", - "private": false, - "main": "dist/cells.umd.cjs", - "module": "dist/cells.js", + "description": "High-level Store", + "main": "src/index.ts", "type": "module", - "exports": { - ".": { - "import": "./dist/cells.js", - "require": "./dist/cells.umd.cjs", - "types": "./dist/index.d.ts" - } - }, - "files": [ - "dist/", - "assets/", - "README.md", - "LICENSE" - ], - "dependencies": { - "@okcontract/graph": "0.1.3" - }, + "dependencies": {}, "devDependencies": { - "@biomejs/biome": "^1.5.3", - "@types/node": "^20.11.5", - "@vitest/coverage-v8": "^1.3.1", - "happy-dom": "^13.0.2", - "immer": "^10.0.4", - "terser": "^5.26.0", - "typescript": "^5.3.3", - "vite": "^5.0.12", - "vitest": "^1.3.1" + "immer": "^10.0.2" }, "scripts": { - "build": "npm run format && vite build", - "test": "vitest run", - "coverage": "vitest run --coverage", - "definitions": "tsc --project tsconfig.build.json", - "prepublishOnly": "npm test && npm run build && npm run check && npm run definitions", - "check": "npx @biomejs/biome check src", - "format": "npx @biomejs/biome format src --write && npx @biomejs/biome check src --apply", - "formatReadme": "prettier README.md --prose-wrap always --print-width 78 -w" + "build": "vite build", + "test": "vitest run --passWithNoTests --test-timeout=10000" }, "repository": { "type": "git", - "url": "git+https://github.com/okcontract/cells.git" + "url": "git+https://github.com/hbbio/lambdascript.git" }, "author": "Henri Binsztok", - "license": "MIT" -} \ No newline at end of file + "license": "UNLICENSED" +} diff --git a/src/array.test.ts b/src/array.test.ts index 60341a1..4049f81 100644 --- a/src/array.test.ts +++ b/src/array.test.ts @@ -17,7 +17,7 @@ import { sort } from "./array"; import type { AnyCell } from "./cell"; -import { cellify, uncellify } from "./cellify"; +import { _cellify, _uncellify } from "./cellify"; import { delayed } from "./promise"; import { SheetProxy } from "./proxy"; import { Sheet } from "./sheet"; @@ -32,17 +32,17 @@ test("mapArray", async () => { expect(sheet.stats.count).toBe(8); // +4 cells // initial value - await expect(uncellify(m)).resolves.toEqual([2, 3, 4]); + await expect(_uncellify(m)).resolves.toEqual([2, 3, 4]); expect(sheet.stats.count).toBe(8); // unchanged // update one cell (await l.get())[0].set(4); - await expect(uncellify(m)).resolves.toEqual([5, 3, 4]); + await expect(_uncellify(m)).resolves.toEqual([5, 3, 4]); expect(sheet.stats.count).toBe(8); // @unchanged // add one cell l.update((arr) => [...arr, proxy.new(5)]); - await expect(uncellify(m)).resolves.toEqual([5, 3, 4, 6]); + await expect(_uncellify(m)).resolves.toEqual([5, 3, 4, 6]); expect(sheet.stats.count).toBe(10); // +1 original cell, +1 new mapped // get one cell @@ -50,7 +50,7 @@ test("mapArray", async () => { // delete one cell l.update((arr) => [...arr.slice(0, 1), ...arr.slice(1 + 1)]); - await expect(uncellify(m)).resolves.toEqual([5, 4, 6]); + await expect(_uncellify(m)).resolves.toEqual([5, 4, 6]); expect(await (await m.get())[2].get()).toBe(6); expect(sheet.stats.count).toBe(10); // @unchanged }); @@ -63,16 +63,16 @@ test("mapArrayCell function change", async () => { const fn = proxy.new((v: AnyCell) => v.map((v) => v + 1)); const m = mapArrayCell(proxy, l, fn); expect(sheet.stats.count).toBe(9); // +4 cells - await expect(uncellify(m)).resolves.toEqual([2, 3, 4]); + await expect(_uncellify(m)).resolves.toEqual([2, 3, 4]); // update one cell (await l.get())[0].set(4); - await expect(uncellify(m)).resolves.toEqual([5, 3, 4]); + await expect(_uncellify(m)).resolves.toEqual([5, 3, 4]); expect(sheet.stats.count).toBe(9); // @unchanged // function change fn.set((v: AnyCell) => v.map((v) => v - 1)); - await expect(uncellify(m)).resolves.toEqual([3, 1, 2]); + await expect(_uncellify(m)).resolves.toEqual([3, 1, 2]); expect(sheet.stats.count).toBe(12); // +3 new mapped cells }); @@ -86,19 +86,19 @@ test("mapArray async", async () => { expect(sheet.stats.count).toBe(8); // +4 cells // initial value - await expect(uncellify(m)).resolves.toEqual([2, 3, 4]); + await expect(_uncellify(m)).resolves.toEqual([2, 3, 4]); expect(sheet.stats.count).toBe(8); // unchanged // update one cell (await l.get())[0].set(4); await proxy.working.wait(); - await expect(uncellify(m)).resolves.toEqual([5, 3, 4]); + await expect(_uncellify(m)).resolves.toEqual([5, 3, 4]); expect(sheet.stats.count).toBe(8); // @unchanged // add one cell l.update((arr) => [...arr, proxy.new(5)]); await proxy.working.wait(); - await expect(uncellify(m)).resolves.toEqual([5, 3, 4, 6]); + await expect(_uncellify(m)).resolves.toEqual([5, 3, 4, 6]); expect(sheet.stats.count).toBe(10); // +1 original cell, +1 new mapped // get one cell @@ -106,7 +106,7 @@ test("mapArray async", async () => { // delete one cell l.update((arr) => [...arr.slice(0, 1), ...arr.slice(1 + 1)]); - await expect(uncellify(m)).resolves.toEqual([5, 4, 6]); + await expect(_uncellify(m)).resolves.toEqual([5, 4, 6]); expect(await (await m.get())[2].get()).toBe(6); expect(sheet.stats.count).toBe(10); // @unchanged }); @@ -161,18 +161,18 @@ test("sort array", async () => { const l = proxy.new([1, 5, 3].map((v) => proxy.new(v))); const s = sort(proxy, l); - await expect(uncellify(s)).resolves.toEqual([1, 3, 5]); + await expect(_uncellify(s)).resolves.toEqual([1, 3, 5]); expect(sheet.stats.count).toBe(6); // 3+1 array +1 sorted +1 pointer // update one cell (await l.get())[0].set(4); - await expect(uncellify(s)).resolves.toEqual([3, 4, 5]); + await expect(_uncellify(s)).resolves.toEqual([3, 4, 5]); expect(sheet.stats.count).toBe(6); // @unchanged // add one cell l.update((arr) => [...arr, proxy.new(1)]); - await expect(uncellify(l)).resolves.toEqual([4, 5, 3, 1]); - await expect(uncellify(s)).resolves.toEqual([1, 3, 4, 5]); + await expect(_uncellify(l)).resolves.toEqual([4, 5, 3, 1]); + await expect(_uncellify(s)).resolves.toEqual([1, 3, 4, 5]); expect(sheet.stats.count).toBe(8); // +1 original cell +1 sorted pointer }); @@ -189,18 +189,18 @@ test("filter array", async () => { // we wait before expecting await proxy.working.wait(); - await expect(uncellify(f)).resolves.toEqual([5, 3]); + await expect(_uncellify(f)).resolves.toEqual([5, 3]); // the removed cell is not deleted ("garbage collected" at proxy level) expect(sheet.stats.count).toBe(7); // unchanged // update one cell (await l.get())[0].set(4); - await expect(uncellify(f)).resolves.toEqual([4, 5, 3]); + await expect(_uncellify(f)).resolves.toEqual([4, 5, 3]); expect(sheet.stats.count).toBe(7); // unchanged // change predicate pred.set((v) => v > 3); - await expect(uncellify(f)).resolves.toEqual([4, 5]); + await expect(_uncellify(f)).resolves.toEqual([4, 5]); expect(sheet.stats).toEqual({ size: 7, count: 8 }); // one pointer updated }); @@ -217,11 +217,11 @@ test("filterPredicateCell array", async () => { // we wait before expecting await proxy.working.wait(); - await expect(uncellify(f)).resolves.toEqual([5, 3]); + await expect(_uncellify(f)).resolves.toEqual([5, 3]); pred.set((v: AnyCell) => v.map((_v) => _v > 3)); await proxy.working.wait(); - await expect(uncellify(f)).resolves.toEqual([5]); + await expect(_uncellify(f)).resolves.toEqual([5]); expect(sheet.stats).toEqual({ size: 11, count: 15 }); // we should not recreate mapped cells }); @@ -259,20 +259,20 @@ test("sort array remapped", async () => { Promise.resolve(0) ); - await expect(uncellify(s)).resolves.toEqual([1, 3, 5]); + await expect(_uncellify(s)).resolves.toEqual([1, 3, 5]); expect(sheet.stats).toEqual({ size: 9, count: 9 }); // 3+1 array +1 sorted +1 pointer +1 sum +1 pointer await expect(sum.get()).resolves.toBe(9); // update one cell (await l.get())[0].set(4); - await expect(uncellify(s)).resolves.toEqual([3, 4, 5]); + await expect(_uncellify(s)).resolves.toEqual([3, 4, 5]); await expect(sum.get()).resolves.toBe(12); expect(sheet.stats).toEqual({ size: 9, count: 10 }); // size unchanged, one pointer changed // add one cell l.update((arr) => [...arr, proxy.new(1)]); - await expect(uncellify(l)).resolves.toEqual([4, 5, 3, 1]); - await expect(uncellify(s)).resolves.toEqual([1, 3, 4, 5]); + await expect(_uncellify(l)).resolves.toEqual([4, 5, 3, 1]); + await expect(_uncellify(s)).resolves.toEqual([1, 3, 4, 5]); await expect(sum.get()).resolves.toBe(13); expect(sheet.stats).toEqual({ size: 10, count: 13 }); // +1 original cell, changed 2 pointers }); @@ -300,7 +300,7 @@ test("basic find", async () => { test("mapArrayRec array", async () => { const sheet = new Sheet(); const proxy = new SheetProxy(sheet); - const arr = cellify(proxy, [1, 2, [3, [4]]]) as CellArray; + const arr = _cellify(proxy, [1, 2, [3, [4]]]) as CellArray; const fl = mapArrayRec(proxy, arr, (x) => x + 1); - await expect(uncellify(fl)).resolves.toEqual([2, 3, [4, [5]]]); + await expect(_uncellify(fl)).resolves.toEqual([2, 3, [4, [5]]]); }); diff --git a/src/cell.ts b/src/cell.ts index 3e099ea..262ed60 100644 --- a/src/cell.ts +++ b/src/cell.ts @@ -962,11 +962,11 @@ export class MapCell extends Cell { false, false ); - // this.sheet._debug && - console.warn( - "cancelled", - simplifier({ paramsResults, firstCanceled, cell: this.id }) - ); + this.sheet._debug && + console.warn( + "cancelled", + simplifier({ paramsResults, firstCanceled, cell: this.id }) + ); return cancelComputation; } // set to CellError when depending on an Error diff --git a/src/cellify.test.ts b/src/cellify.test.ts index dcc8f09..fff5310 100644 --- a/src/cellify.test.ts +++ b/src/cellify.test.ts @@ -3,8 +3,8 @@ import { expect, test } from "vitest"; import { type Cellified, type Uncellified, - cellify, - uncellify + _cellify, + _uncellify } from "./cellify"; import { SheetProxy } from "./proxy"; import { Sheet } from "./sheet"; @@ -37,16 +37,16 @@ test("fix point", async () => { for (let i = 0; i < tests.length; i++) { const v = tests[i]; - const c = cellify(proxy, v); - const u = await uncellify(c); + const c = _cellify(proxy, v); + const u = await _uncellify(c); expect(u).toEqual(v); } }); -test("cellify one", async () => { +test("_cellify one", async () => { const sheet = new Sheet(); const proxy = new SheetProxy(sheet); - const res = cellify(proxy, { a: 1 }); + const res = _cellify(proxy, { a: 1 }); const cell = await res.get(); await expect(cell.a.get()).resolves.toBe(1); }); diff --git a/src/cellify.ts b/src/cellify.ts index 48b1105..a970620 100644 --- a/src/cellify.ts +++ b/src/cellify.ts @@ -1,11 +1,7 @@ -import { type CellArray, mapArray } from "./array"; -import { type AnyCell, Cell, type MapCell, type ValueCell } from "./cell"; -import { collector } from "./gc"; -import { type CellObject, mapObject } from "./object"; +import { simplifier } from "."; +import { type AnyCell, Cell, MapCell, type ValueCell } from "./cell"; import type { SheetProxy } from "./proxy"; -export type Path = (string | number)[]; - // Cellified computes a cellified type. export type Cellified = T extends object ? T extends Array @@ -34,24 +30,27 @@ export const isObject = ( typeof v === "object" && v !== null && v.constructor?.name === "Object"; /** - * cellify converts any value to a Cellified value where each array or record + * _cellify converts any value to a Cellified value where each array or record * becomes a Cell in canonical form. * @param proxy * @param v any defined value (v must not be undefined) * @returns * @todo cell reuses */ -export const cellify = (proxy: SheetProxy, v: T): Cellified => { +export const _cellify = (proxy: SheetProxy, v: T): Cellified => { if (v instanceof Cell) throw new Error("cell"); return proxy.new( Array.isArray(v) - ? v.map((vv) => cellify(proxy, vv)) + ? v.map((vv) => _cellify(proxy, vv), "_cellify.[]") : isObject(v) ? Object.fromEntries( - Object.entries(v).map(([k, vv]) => [k, cellify(proxy, vv)]) + Object.entries(v).map( + ([k, vv]) => [k, _cellify(proxy, vv)], + "_cellify.{}" + ) ) : v, - v?.constructor?.name ? `รง(${v.constructor.name})` : `c(${typeof v})` + "_cellify" ) as Cellified; }; @@ -60,145 +59,31 @@ export const cellify = (proxy: SheetProxy, v: T): Cellified => { * @param v any value * @returns value without cells */ -export const uncellify = async ( - v: T | AnyCell, - filter: { - excludePaths: Path[]; - currentPath: Path; - } = { excludePaths: [], currentPath: [] } +export const _uncellify = async ( + v: T | AnyCell ): Promise> => { - const isFiltered = (path: Path, excludePaths: Path[]): boolean => - excludePaths.some( - (excludePath) => - excludePath.length === path.length && - excludePath.every((p, index) => p === path[index]) - ); - if (isFiltered(filter.currentPath, filter.excludePaths)) - return v as Uncellified; - const value = v instanceof Cell ? await v.consolidatedValue : v; + console.log("zero", v.constructor.prototype); + const value = v instanceof Cell ? await v.get() : v; + // console.log("_uncellify", { value: simplifier(value) }); + console.log("_uncellify", { + // simplifier: simplifier(v), + isCell: v instanceof Cell, + isCell2: value instanceof Cell, + isCell3: value instanceof MapCell, + isCell4: v instanceof MapCell, + isError: value instanceof Error + }); if (value instanceof Error) throw value; if (Array.isArray(value)) return Promise.all( - value.map( - async (element, index) => - await uncellify(element, { - excludePaths: filter.excludePaths, - currentPath: filter.currentPath.concat(index) - }) - ) + value.map(async (_element) => await _uncellify(_element)) ) as Promise>; - if (isObject(value)) return Object.fromEntries( await Promise.all( - Object.entries(value).map(async ([k, vv]) => [ - k, - await uncellify(vv, { - excludePaths: filter.excludePaths, - currentPath: filter.currentPath.concat(k) - }) - ]) + Object.entries(value).map(async ([k, vv]) => [k, await _uncellify(vv)]) ) ); - - // Classes, null or base types (string, number, ..) + // Classes, null or base types (string, number, ...) return value as Uncellified; }; - -/** - * follow a static path for a Cellified value. - */ -export const follow = ( - proxy: SheetProxy, - v: Cellified, - path: Path, - name = "follow" -) => { - const aux = (v: Cellified, path: Path, name: string) => { - // @todo multi collector? - const coll = collector>(proxy); - return proxy.map( - [v], - (_v) => { - const key = path[0]; - const isContainer = Array.isArray(_v) || isObject(_v); - if (isContainer && _v[key]) - return coll(aux(_v[key], path.slice(1), `${name}.${key}`)); - if (isContainer) throw new Error(`path not found: ${key}`); - return _v; // pointer - }, - name - ); - }; - return aux(v, path, name); -}; - -export const pathsTree = (proxy, v) => - mapPartialTree(proxy, v, (path, _) => path); - -export const mapPartialTree = ( - proxy: SheetProxy, - v: Cellified>, - fn: (path: Path, v: unknown) => unknown -) => { - // @todo collector at all levels? - // step 1: compute paths - const pathTreeAux = ( - v: Cellified, - path: Path, - name = "path" - ): MapCell => { - console.log({ aux: path, v: v !== undefined }); - const coll = collector>(proxy); - // if (!v) throw new Error("arg"); - return proxy.map( - [v], - (_v, prev) => { - if (prev) return prev; - - if (Array.isArray(_v)) { - return ( - v.pointed || - mapArray( - proxy, - v as CellArray, - (_, i) => - coll(pathTreeAux(_v[i], [...path, i], [...path, i].join(":"))), - "path[]" - ) - ); - } - if (isObject(_v)) { - return v.pointed - ? proxy.get(v.pointed) - : mapObject( - proxy, - v as CellObject, - (fk, _, fv) => { - // const fv = fv2 _v[fk]; - console.log({ - fk, - fv: fv?.constructor?.name - // fv2: fv2?.constructor?.name - }); - return coll( - pathTreeAux( - fv as ValueCell, - [...path, fk], - [...path, fk].join(":") - ) - ); - }, - "path{}" - ); - } - return proxy.new(fn(path, _v), `leaf:${path.join(".")}`); // leaf - }, - name - ); - }; - const pt = pathTreeAux(v, [], "root"); - - // step 2: flatten - return pt; -}; diff --git a/src/index.ts b/src/index.ts index 041b3ed..296935d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,7 +46,12 @@ export { clock, clockWork, type Clock } from "./clock"; export { Debugger } from "./debug"; export { jsonStringify } from "./json"; export { nextSubscriber } from "./next"; -export { uncellify, cellify } from "./cellify"; +export { + _uncellify, + _cellify, + type Cellified, + type Uncellified +} from "./cellify"; export { mapObject, reduceObject, diff --git a/src/object.test.ts b/src/object.test.ts index 13be605..da1c5c7 100644 --- a/src/object.test.ts +++ b/src/object.test.ts @@ -1,7 +1,7 @@ import { expect, test } from "vitest"; import type { AnyCell } from "./cell"; -import { cellify, uncellify } from "./cellify"; +import { _cellify, _uncellify } from "./cellify"; import { filterObjectValue, findObjectValue, @@ -15,7 +15,7 @@ test("mapObject", async () => { const sheet = new Sheet(); const proxy = new SheetProxy(sheet); - const obj = cellify(proxy, { a: 1, b: "foo", c: "bar" }); + const obj = _cellify(proxy, { a: 1, b: "foo", c: "bar" }); expect(sheet.stats).toEqual({ count: 4, size: 4 }); const m = mapObject( proxy, @@ -25,24 +25,24 @@ test("mapObject", async () => { ); // initial value - await expect(uncellify(m)).resolves.toEqual({ a: 1, b: 3, c: 3 }); + await expect(_uncellify(m)).resolves.toEqual({ a: 1, b: 3, c: 3 }); expect(sheet.stats).toEqual({ count: 8, size: 8 }); // update a field (await (await obj.get()).a).set(4); - await expect(uncellify(m)).resolves.toEqual({ a: 4, b: 3, c: 3 }); + await expect(_uncellify(m)).resolves.toEqual({ a: 4, b: 3, c: 3 }); expect(sheet.stats).toEqual({ count: 8, size: 8 }); // add a field obj.update((rec) => ({ ...rec, h: proxy.new("hello") })); - await expect(uncellify(obj)).resolves.toEqual({ + await expect(_uncellify(obj)).resolves.toEqual({ a: 4, b: "foo", c: "bar", h: "hello" }); - console.log(await uncellify(m)); - await expect(uncellify(m)).resolves.toEqual({ a: 4, b: 3, c: 3, h: 5 }); + console.log(await _uncellify(m)); + await expect(_uncellify(m)).resolves.toEqual({ a: 4, b: 3, c: 3, h: 5 }); expect(sheet.stats).toEqual({ count: 10, size: 10 }); // delete a field @@ -52,7 +52,7 @@ test("mapObject", async () => { delete copy.a; return copy; }); - await expect(uncellify(m)).resolves.toEqual({ b: 3, c: 3, h: 5 }); + await expect(_uncellify(m)).resolves.toEqual({ b: 3, c: 3, h: 5 }); expect(sheet.stats).toEqual({ count: 10, size: 9 }); // gc works }); @@ -60,7 +60,7 @@ test("reduceObject", async () => { const sheet = new Sheet(); const proxy = new SheetProxy(sheet); - const l = cellify(proxy, { a: 1, b: 2, c: 3 }); + const l = _cellify(proxy, { a: 1, b: 2, c: 3 }); const v = reduceObject(proxy, l, (acc, [_key, v]) => acc + (v as number), 0); await expect(v.get()).resolves.toBe(6); expect(sheet.stats).toEqual({ count: 6, size: 6 }); // 3+1 array +1 sum +1 pointer @@ -103,10 +103,10 @@ test("filterObjectValue ", async () => { // we wait before expecting await proxy.working.wait(); - await expect(uncellify(f)).resolves.toEqual(["bar"]); + await expect(_uncellify(f)).resolves.toEqual(["bar"]); pred.set((v: AnyCell) => v.map((_v) => proxy.new(_v === "foo"))); await proxy.working.wait(); - await expect(uncellify(f)).resolves.toEqual([]); + await expect(_uncellify(f)).resolves.toEqual([]); expect(sheet.stats).toEqual({ size: 10, count: 12 }); // we should not recreate mapped cells });