• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

okcontract / cells / 8101603136

29 Feb 2024 07:29PM UTC coverage: 86.737% (-0.09%) from 86.822%
8101603136

push

github

web-flow
Garbage collector (#13)

* array: test sorted array pointer

* sheet: newProxy

* package: update lock

* sheet: garbage collector

* array: use garbage collector

* sheet: do not log deletions by default

* array: CellArray, collector

* gc update with 0.1.7 (#16)

* array: test sort array remapped

* array: reduce accepts Promises

* array: update remapped test

* cleanup: log messages

* clean: log messages

* Pointers map calls (#4)

* sheet: implement newProxy

* pointers: test call counts

* sheet: restore newProxy from bc3a9de8b

* package: update lock

* sheet: do not recompute on roots from @J-Ata
941d6e3ac

* package: bump to graph 0.1.3

* test: fix non-returning error cascade test (not passing)

* error: improve tests

* Jata@pointer map calls (#15)

* More debug Logs

squash! WIP test

* Print all when DEV

* Fix concurrency issue on canceled computations

* cell, sheet: debug false

---------

Co-authored-by: J-Ata <135155454+J-Ata@users.noreply.github.com>

* package: bump version to 0.1.7

---------

Co-authored-by: J-Ata <135155454+J-Ata@users.noreply.github.com>

* sheet: release after GC collection

* cell: manage invalidation first for _setValueOnComputationCompletion

* cell: do not update localStorage if !needUpdate

---------

Co-authored-by: J-Ata <135155454+J-Ata@users.noreply.github.com>

* array: fix type error

* gitignore: coverage

* array: rewrite filter, improve gc use, find

* package: fix exports

* package: bump version

* filterAsync: remove

* array: tests for first, last, find

---------

Co-authored-by: J-Ata <135155454+J-Ata@users.noreply.github.com>

364 of 437 branches covered (83.3%)

Branch coverage included in aggregate %.

353 of 421 new or added lines in 4 files covered. (83.85%)

2 existing lines in 1 file now uncovered.

2834 of 3250 relevant lines covered (87.2%)

880.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.55
/src/array.ts
1
import { type AnyCell, MapCell, ValueCell } from "./cell";
1✔
2
import { collector, reuseOrCreate } from "./gc";
1✔
3
import type { SheetProxy } from "./proxy";
1✔
4

1✔
5
export type CellArray<T> = AnyCell<AnyCell<T>[]>;
1✔
6

1✔
7
/**
1✔
8
 * mapArray implements .map() for a cellified array.
1✔
9
 *
1✔
10
 * @param proxy
1✔
11
 * @param arr canonical form cell array
1✔
12
 * @param fn to map each element cell
1✔
13
 * @returns mapped array cell
1✔
14
 *
1✔
15
 * @description This function reuses existing mapped cells.
1✔
16
 * @todo Delete unused mapped cells
1✔
17
 */
1✔
18
export const mapArray = <T, U>(
1✔
19
  proxy: SheetProxy,
2✔
20
  arr: CellArray<T>,
2✔
21
  fn: (v: T, index?: number) => U | Promise<U>,
2✔
22
  name = "map"
2✔
23
): MapCell<MapCell<U, false>[], false> =>
2✔
24
  proxy.map(
2✔
25
    [arr],
2✔
26
    (cells, prev) => {
2✔
27
      const set = new Set((prev || []).map((cell) => cell.id));
6✔
28
      const res = cells.map((cell, index) => {
6✔
29
        // reuse previously mapped cell
20✔
30
        const reuse = prev?.find((_c) => _c.dependencies?.[0] === cell.id);
20✔
31
        if (reuse !== undefined) set.delete(reuse.id);
20✔
32
        return (
20✔
33
          reuse ||
20✔
34
          // create new map
8✔
35
          proxy.map([cell], (_cell) => fn(_cell, index), `[${index}]`)
8✔
36
        );
20✔
37
      });
6✔
38
      // collect unused previously mapped cells
6✔
39
      proxy._sheet.collect(...[...set]);
6✔
40
      return res;
6✔
41
    },
6✔
42
    name
2✔
43
  );
2✔
44

1✔
45
/**
1✔
46
 * implementation of sort for a cellified array.
1✔
47
 * @description this implementation relies on pointers but reuses the original cells.
1✔
48
 * @param proxy
1✔
49
 * @param arr
1✔
50
 * @param compare comparison function
1✔
51
 */
1✔
52
export const sort = <T>(
1✔
53
  proxy: SheetProxy,
3✔
54
  arr: CellArray<T>,
3✔
55
  compare: (a: T, b: T) => number = (a, b) => (a > b ? 1 : a < b ? -1 : 0)
3✔
56
): CellArray<T> => {
3✔
57
  const coll = collector<CellArray<T>>(proxy);
3✔
58
  return proxy.map(
3✔
59
    [arr],
3✔
60
    (cells) =>
3✔
61
      coll(
6✔
62
        proxy.mapNoPrevious(
6✔
63
          cells,
6✔
64
          (..._cells) =>
6✔
65
            _cells
8✔
66
              .map((_, index) => index)
8✔
67
              .sort((indexA, indexB) => compare(_cells[indexA], _cells[indexB]))
8✔
68
              .map((index) => cells[index]),
8✔
69
          "_sort"
6✔
70
        )
6✔
71
      ),
6✔
72
    "sort"
3✔
73
  );
3✔
74
};
3✔
75

1✔
76
/**
1✔
77
 * mapArrayCell is a variant of `mapArray` with a function taking
1✔
78
 * element cells as arguments.
1✔
79
 * @param proxy
1✔
80
 * @param arr
1✔
81
 * @param fn
1✔
82
 * @returns mapped array cell
1✔
83
 */
1✔
84
export const mapArrayCell = <T, U, NF extends boolean = false>(
1✔
85
  proxy: SheetProxy,
1✔
86
  arr: CellArray<T>,
1✔
87
  fn: AnyCell<(v: AnyCell<T>, idx?: number) => AnyCell<U>>,
1✔
88
  name = "map",
1✔
89
  nf?: NF
1✔
90
): MapCell<MapCell<U, NF>[], NF> => {
1✔
91
  let prevFn: (v: AnyCell<T>) => AnyCell<U>;
1✔
92
  return proxy.map(
1✔
93
    [arr, fn],
1✔
94
    (cells, _fn, prev) => {
1✔
95
      const arr = cells.map((cell, i) =>
2✔
96
        // @todo collect previously mapped cells
6✔
97
        reuseOrCreate(
6✔
98
          _fn === prevFn, // function unchanged
6✔
99
          () => prev?.find((_c) => _c.dependencies?.[0] === cell.id), // reuse
6✔
100
          () => _fn(cell, i) as MapCell<U, NF> // create new map
6✔
101
        )
6✔
102
      );
2✔
103
      prevFn = _fn;
2✔
104
      return arr;
2✔
105
    },
2✔
106
    name,
1✔
107
    nf
1✔
108
  );
1✔
109
};
1✔
110

1✔
111
/**
1✔
112
 * first element of recursive arrays.
1✔
113
 */
1✔
114
export const first = <T>(
1✔
115
  proxy: SheetProxy,
1✔
116
  arr: AnyCell<T | AnyCell<T>[]>,
1✔
117
  name = "last"
1✔
118
): MapCell<T, boolean> => {
1✔
119
  // @todo collectors (on path?)
1✔
120
  const aux = (arr: AnyCell<T | AnyCell<T>[]>) =>
1✔
121
    proxy.mapNoPrevious(
2✔
122
      [arr],
2✔
123
      (_arr) => {
2✔
124
        if (!Array.isArray(_arr)) return arr as AnyCell<T>;
2✔
125
        if (_arr.length === 0) return null; // @todo pointer to nullCell?
2✔
126
        return aux(_arr[0]);
1✔
127
      },
2✔
128
      name
2✔
129
    );
2✔
130
  return aux(arr);
1✔
131
};
1✔
132

1✔
133
/**
1✔
134
 * last element of recursive arrays.
1✔
135
 */
1✔
136
export const last = <T>(
1✔
137
  proxy: SheetProxy,
1✔
138
  arr: AnyCell<T | AnyCell<T>[]>,
1✔
139
  name = "last"
1✔
140
): MapCell<T, boolean> => {
1✔
141
  // @todo collectors (on path?)
1✔
142
  const aux = (arr: AnyCell<T | AnyCell<T>[]>) =>
1✔
143
    proxy.mapNoPrevious(
2✔
144
      [arr],
2✔
145
      (_arr) => {
2✔
146
        if (Array.isArray(_arr)) return aux(_arr[_arr.length - 1]);
2✔
147
        return arr as AnyCell<T>;
1✔
148
      },
2✔
149
      name
2✔
150
    );
2✔
151
  return aux(arr);
1✔
152
};
1✔
153

1✔
154
/**
1✔
155
 * reduce a cellified array.
1✔
156
 * @param proxy
1✔
157
 * @param arr
1✔
158
 * @param fn
1✔
159
 * @param init
1✔
160
 * @returns reduced cell
1✔
161
 */
1✔
162
export const reduce = <
1✔
163
  T,
2✔
164
  R extends unknown | Promise<unknown>,
2✔
165
  NF extends boolean = false
2✔
166
>(
2✔
167
  proxy: SheetProxy,
2✔
168
  arr: CellArray<T>,
2✔
169
  fn: (acc: R, elt: T, index?: number) => R,
2✔
170
  init: R,
2✔
171
  name = "reduce",
2✔
172
  nf?: NF
2✔
173
): MapCell<R, NF> => {
2✔
174
  const coll = collector<MapCell<R, NF>>(proxy);
2✔
175
  return proxy.map(
2✔
176
    [arr],
2✔
177
    (cells) =>
2✔
178
      coll(
5✔
179
        proxy.mapNoPrevious(
5✔
180
          cells,
5✔
181
          (..._cells) => _cells.reduce(fn, init),
5✔
182
          "_reduce"
5✔
183
        )
5✔
184
      ),
5✔
185
    name,
2✔
186
    nf
2✔
187
  );
2✔
188
};
2✔
189

1✔
190
export const find = <T, NF extends boolean = false>(
1✔
191
  proxy: SheetProxy,
1✔
192
  arr: CellArray<T>,
1✔
193
  predicate: (v: T) => boolean,
1✔
194
  name = "find",
1✔
195
  nf?: NF
1✔
196
) => {
1✔
197
  const coll = collector<MapCell<T, NF>>(proxy);
1✔
198
  return proxy.map(
1✔
199
    [arr],
1✔
200
    (cells) =>
1✔
201
      coll(proxy.mapNoPrevious(cells, (..._cells) => _cells.find(predicate))),
1✔
202
    name,
1✔
203
    nf
1✔
204
  );
1✔
205
};
1✔
206

1✔
207
// @todo generalize
1✔
208
export const findCell = <T, NF extends boolean = false>(
1✔
UNCOV
209
  proxy: SheetProxy,
×
NEW
210
  arr: CellArray<T>,
×
NEW
211
  predicate: AnyCell<(v: AnyCell<T>) => AnyCell<boolean>>,
×
NEW
212
  name = "find",
×
NEW
213
  nf?: NF
×
NEW
214
) => {
×
NEW
215
  const coll = collector<MapCell<T, NF>>(proxy);
×
NEW
216
  // Since predicate is a reactive function, we have to instantiate
×
NEW
217
  // the computation for each cell.
×
NEW
218
  // const keep = mapArrayCell(proxy, arr, predicate, "keep", nf);
×
NEW
219
  let prevFn: (elt: AnyCell<T>) => AnyCell<boolean>;
×
NEW
220
  const keep = proxy.map(
×
NEW
221
    [predicate, arr],
×
NEW
222
    (fn, cells, _prev: AnyCell<boolean>[]) => {
×
NEW
223
      // console.log({ keep: cells.length });
×
NEW
224
      // @todo if the predicate function has changed, collect previous mapped cells
×
NEW
225
      const keep = cells.map((cell) => {
×
NEW
226
        // We can reuse a cell only if the predicate hasn't changed.
×
NEW
227
        // @todo collect previously mapped cells for deleted cells in arr
×
NEW
228
        const reuse =
×
NEW
229
          prevFn === fn &&
×
NEW
230
          _prev?.find((_c) => _c.dependencies?.[0] === cell.id);
×
NEW
231
        return reuse || fn(cell);
×
NEW
232
      });
×
NEW
233
      prevFn = fn;
×
NEW
234
      return keep;
×
NEW
235
    },
×
NEW
236
    "keep",
×
NEW
237
    nf
×
UNCOV
238
  );
×
NEW
239
  return proxy.map(
×
NEW
240
    [arr, keep],
×
NEW
241
    (cells, _keep) =>
×
NEW
242
      coll(
×
NEW
243
        proxy.mapNoPrevious(_keep, (..._flat) => cells.find((_, i) => _flat[i]))
×
NEW
244
      ),
×
NEW
245
    name,
×
NEW
246
    nf
×
NEW
247
  );
×
NEW
248
};
×
249

1✔
250
export const findIndex = <T, NF extends boolean = false>(
1✔
251
  proxy: SheetProxy,
1✔
252
  arr: CellArray<T>,
1✔
253
  fn: (v: T) => boolean,
1✔
254
  name = "find",
1✔
255
  nf?: NF
1✔
256
) => {
1✔
257
  const coll = collector<MapCell<number, NF>>(proxy);
1✔
258
  return proxy.map(
1✔
259
    [arr],
1✔
260
    (cells) =>
1✔
261
      coll(proxy.mapNoPrevious(cells, (..._cells) => _cells.findIndex(fn))),
2✔
262
    name,
1✔
263
    nf
1✔
264
  );
1✔
265
};
1✔
266

1✔
267
/**
1✔
268
 * filter updates a cellified array in a `ValueCell` using a predicate function as filter.
1✔
269
 * @param arr
1✔
270
 * @param predicate function that returns `true` for kept values.
1✔
271
 * @returns nothing
1✔
272
 * @description filtered out cells are _not_ deleted
1✔
273
 */
1✔
274
export const filter = <T, NF extends boolean = false>(
1✔
275
  proxy: SheetProxy,
1✔
276
  predicate: AnyCell<(elt: T) => boolean>,
1✔
277
  arr: CellArray<T>,
1✔
278
  name = "filter",
1✔
279
  nf?: NF
1✔
280
) => {
1✔
281
  const coll = collector<MapCell<AnyCell<T>[], NF>>(proxy);
1✔
282
  return proxy.map(
1✔
283
    [predicate, arr],
1✔
284
    (fn, cells) =>
1✔
285
      coll(
2✔
286
        proxy.mapNoPrevious(
2✔
287
          cells,
2✔
288
          (..._cells) => cells.filter((_, i) => fn(_cells[i])),
2✔
289
          "_filter"
2✔
290
        )
2✔
291
      ),
2✔
292
    name,
1✔
293
    nf
1✔
294
  );
1✔
295
};
1✔
296

1✔
297
export const mapFlat = <T, NF extends boolean = false>(
1✔
NEW
298
  proxy: SheetProxy,
×
NEW
299
  arr: CellArray<T>,
×
NEW
300
  name = "flatten",
×
NEW
301
  nf?: NF
×
NEW
302
) => {
×
NEW
303
  const coll = collector<MapCell<T[], NF>>(proxy);
×
NEW
304
  return proxy.map(
×
NEW
305
    [arr],
×
NEW
306
    (cells) => coll(proxy.mapNoPrevious(cells, (..._cells) => _cells)),
×
NEW
307
    name,
×
NEW
308
    nf
×
NEW
309
  );
×
NEW
310
};
×
311

1✔
312
/**
1✔
313
 * filterPredicateCell updates a cellified array in a `ValueCell` using a predicate function as filter.
1✔
314
 * @param arr
1✔
315
 * @param predicate as Cell function that returns `true` for kept values.
1✔
316
 * @returns nothing
1✔
317
 * @description filtered out cells are _not_ deleted
1✔
318
 */
1✔
319
export const filterPredicateCell = <T, NF extends boolean = false>(
1✔
320
  proxy: SheetProxy,
1✔
321
  predicate: AnyCell<(elt: AnyCell<T>) => AnyCell<boolean>>,
1✔
322
  arr: CellArray<T>,
1✔
323
  name = "filter",
1✔
324
  nf?: NF
1✔
325
) => {
1✔
326
  const coll = collector<MapCell<AnyCell<T>[], NF>>(proxy);
1✔
327
  // Since predicate is a reactive function, we have to instantiate
1✔
328
  // the computation for each cell.
1✔
329
  // const keep = mapArrayCell(proxy, arr, predicate, "keep", nf);
1✔
330
  let prevFn: (elt: AnyCell<T>) => AnyCell<boolean>;
1✔
331
  const keep = proxy.map(
1✔
332
    [predicate, arr],
1✔
333
    (fn, cells, _prev: AnyCell<boolean>[]) => {
1✔
334
      // console.log({ keep: cells.length });
2✔
335
      // @todo if the predicate function has changed, collect previous mapped cells
2✔
336
      const keep = cells.map((cell) => {
2✔
337
        // We can reuse a cell only if the predicate hasn't changed.
6✔
338
        // @todo collect previously mapped cells for deleted cells in arr
6✔
339
        const reuse =
6✔
340
          prevFn === fn &&
6!
NEW
341
          _prev?.find((_c) => _c.dependencies?.[0] === cell.id);
×
342
        return reuse || fn(cell);
6✔
343
      });
2✔
344
      prevFn = fn;
2✔
345
      return keep;
2✔
346
    },
2✔
347
    "keep",
1✔
348
    nf
1✔
349
  );
1✔
350
  return proxy.map(
1✔
351
    [arr, keep],
1✔
352
    (cells, _keep) =>
1✔
353
      coll(
2✔
354
        proxy.mapNoPrevious(
2✔
355
          _keep,
2✔
356
          (..._flat) => cells.filter((_, i) => _flat[i]),
2✔
357
          "filter.map"
2✔
358
        )
2✔
359
      ),
2✔
360
    name,
1✔
361
    nf
1✔
362
  );
1✔
363
};
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc