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

okcontract / cells / 20256130503

16 Dec 2025 04:06AM UTC coverage: 88.241% (-4.7%) from 92.948%
20256130503

push

github

web-flow
Merge pull request #41 from okcontract/vite7

Vite 7

175 of 195 branches covered (89.74%)

Branch coverage included in aggregate %.

808 of 919 relevant lines covered (87.92%)

3291.03 hits per line

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

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

5
export type CellArray<T> = AnyCell<AnyCell<T>[]>;
6
export type ValueCellArray<T> = ValueCell<ValueCell<T>[]>;
7

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

57
/**
58
 * Compares two values and returns a number indicating their order.
59
 *
60
 * This function is designed to be used as a comparator in sorting functions.
61
 * It returns:
62
 * - `1` if `a` is greater than `b`
63
 * - `-1` if `a` is less than `b`
64
 * - `0` if `a` is equal to `b`
65
 *
66
 * @param {*} a - The first value to compare.
67
 * @param {*} b - The second value to compare.
68
 * @returns {number} - Order.
69
 *
70
 * @example
71
 * const array = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];
72
 * array.sort(defaultComparator);
73
 */
74
export const defaultComparator = <T>(a: T, b: T): number =>
1✔
75
  a > b ? 1 : a < b ? -1 : 0;
30✔
76

77
/**
78
 * implementation of sort for a cellified array.
79
 * @description this implementation relies on pointers but reuses the original cells.
80
 * @param proxy
81
 * @param arr
82
 * @param compare comparison function
83
 * @param noFail should be true when the following conditions are met:
84
 * 1. compare must be defined
85
 * 2. cells must be an array (possibly empty)
86
 * 3. compare should never fail
87
 */
88
export const sort = <T, NF extends boolean = false>(
1✔
89
  proxy: SheetProxy,
90
  arr: CellArray<T>,
91
  compare: (a: T, b: T) => number = defaultComparator,
92
  noFail?: NF
93
): MapCell<typeof arr extends AnyCell<infer U> ? U : never, NF> => {
94
  const coll =
3✔
95
    collector<MapCell<typeof arr extends AnyCell<infer U> ? U : never, NF>>(
96
      proxy
97
    );
98
  return proxy.mapNoPrevious(
3✔
99
    [arr],
100
    (cells) =>
101
      coll(
6✔
102
        proxy.mapNoPrevious(
103
          cells,
104
          (..._cells) =>
105
            _cells
8✔
106
              .map((_, index) => index)
27✔
107
              .sort((indexA, indexB) => compare(_cells[indexA], _cells[indexB]))
30✔
108
              .map((index) => cells[index]),
27✔
109
          "_sort",
110
          noFail || false
111
        )
112
      ),
113
    "sort",
114
    noFail || false
115
  );
116
};
117

118
/**
119
 * mapArrayCell is a variant of `mapArray` with a function taking
120
 * element cells as arguments.
121
 * @param proxy
122
 * @param arr
123
 * @param fn
124
 * @returns mapped array cell
125
 */
126
export const mapArrayCell = <T, U, NF extends boolean = false>(
1✔
127
  proxy: SheetProxy,
128
  arr: CellArray<T>,
129
  fn: AnyCell<(v: AnyCell<T>, idx?: number) => AnyCell<U>>,
130
  name = "map",
131
  nf?: NF
132
): MapCell<MapCell<U, NF>[], NF> => {
133
  let prevFn: (v: AnyCell<T>) => AnyCell<U>;
134
  return proxy.map(
1✔
135
    [arr, fn],
136
    (cells, _fn, prev) => {
137
      const arr = cells.map((cell, i) =>
6✔
138
        // @todo collect previously mapped cells
139
        reuseOrCreate(
140
          _fn === prevFn, // function unchanged
141
          () => prev?.find((_c) => _c.dependencies?.[0] === cell.id), // reuse
×
142
          () => _fn(cell, i) as MapCell<U, NF> // create new map
6✔
143
        )
144
      );
145
      prevFn = _fn;
2✔
146
      return arr;
2✔
147
    },
148
    name,
149
    nf
150
  );
151
};
152

153
/**
154
 * first element of recursive arrays.
155
 */
156
export const first = <T>(
1✔
157
  proxy: SheetProxy,
158
  arr: AnyCell<T | AnyCell<T>[]>,
159
  name = "last"
160
): MapCell<T, boolean> => {
161
  // @todo collectors (on path?)
162
  const aux = (arr: AnyCell<T | AnyCell<T>[]>) =>
1✔
163
    proxy.mapNoPrevious(
2✔
164
      [arr],
165
      (_arr) => {
166
        if (!Array.isArray(_arr)) return arr as AnyCell<T>;
2✔
167
        if (_arr.length === 0) return null; // @todo pointer to nullCell?
1✔
168
        return aux(_arr[0]);
1✔
169
      },
170
      name
171
    );
172
  return aux(arr);
1✔
173
};
174

175
/**
176
 * last element of recursive arrays.
177
 */
178
export const last = <T>(
1✔
179
  proxy: SheetProxy,
180
  arr: AnyCell<T | AnyCell<T>[]>,
181
  name = "last"
182
): MapCell<T, boolean> => {
183
  // @todo collectors (on path?)
184
  const aux = (arr: AnyCell<T | AnyCell<T>[]>) =>
1✔
185
    proxy.mapNoPrevious(
2✔
186
      [arr],
187
      (_arr) => {
188
        if (Array.isArray(_arr)) return aux(_arr[_arr.length - 1]);
2✔
189
        return arr as AnyCell<T>;
1✔
190
      },
191
      name
192
    );
193
  return aux(arr);
1✔
194
};
195

196
/**
197
 * reduce a cellified array.
198
 * @param proxy
199
 * @param arr
200
 * @param fn
201
 * @param init
202
 * @returns reduced cell
203
 */
204
export const reduce = <
1✔
205
  T,
206
  R extends unknown | Promise<unknown>,
207
  NF extends boolean = false
208
>(
209
  proxy: SheetProxy,
210
  arr: CellArray<T>,
211
  fn: (acc: R, elt: T, index?: number, cell?: AnyCell<T>) => R,
212
  init: R,
213
  name = "reduce",
214
  nf?: NF
215
): MapCell<R, NF> => {
216
  const coll = collector<MapCell<R, NF>>(proxy);
2✔
217
  return proxy.map(
2✔
218
    [arr],
219
    (cells) =>
220
      coll(
5✔
221
        proxy.mapNoPrevious(
222
          cells,
223
          (..._cells) =>
224
            _cells.reduce((acc, elt, i) => fn(acc, elt, i, cells[i]), init),
23✔
225
          "_reduce"
226
        )
227
      ),
228
    name,
229
    nf
230
  );
231
};
232

233
export const find = <T, NF extends boolean = false>(
1✔
234
  proxy: SheetProxy,
235
  arr: CellArray<T>,
236
  predicate: (v: T) => boolean,
237
  name = "find",
238
  nf?: NF
239
) => {
240
  const coll = collector<MapCell<T, NF>>(proxy);
1✔
241
  return proxy.map(
1✔
242
    [arr],
243
    (cells) =>
244
      coll(proxy.mapNoPrevious(cells, (..._cells) => _cells.find(predicate))),
1✔
245
    name,
246
    nf
247
  );
248
};
249

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

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

297
/**
298
 * `flattenCellArray` is a utility function that maps and flattens an array
299
 * of cells into a single cell that contains a flattened array of values.
300
 * It creates a new cell that reacts to changes in the input array and updates
301
 * its value accordingly.
302
 *
303
 * @template T - The type of the elements in the input cell array.
304
 * @template NF - Type indicating if the cells are failsafe.
305
 *
306
 * @param {SheetProxy} proxy - An instance of `SheetProxy` used to create new
307
 * cells and manage dependencies.
308
 * @param {CellArray<T>} arr - An array of cells, where each cell contains a
309
 * value of type `T`.
310
 * @param {string} [name="flatten"] - An optional name for the resulting cell.
311
 * Defaults to "flatten".
312
 * @param {NF} [nf] - An optional flag indicating whether the cells can't fail.
313
 * Defaults to `false`.
314
 *
315
 * @returns {MapCell<T[], NF>} - A cell containing a flattened array of values.
316
 */
317
export const flattenCellArray = <T, NF extends boolean = false>(
1✔
318
  proxy: SheetProxy,
319
  arr: CellArray<T>,
320
  name = "flatten",
321
  nf?: NF
322
) => {
323
  const coll = collector<MapCell<T[], NF>>(proxy);
×
324
  return proxy.mapNoPrevious(
×
325
    [arr],
326
    (cells) =>
327
      cells === null
×
328
        ? null
329
        : coll(proxy.mapNoPrevious(cells, (..._cells) => _cells)),
×
330
    name,
331
    nf
332
  );
333
};
334

335
/**
336
 * filterPredicateCell updates a cellified array in a `ValueCell` using a predicate function as filter.
337
 * @param arr
338
 * @param predicate as Cell function that returns `true` for kept values.
339
 * @returns nothing
340
 * @description filtered out cells are _not_ deleted
341
 */
342
export const filterPredicateCell = <T, NF extends boolean = false>(
1✔
343
  proxy: SheetProxy,
344
  predicate: AnyCell<(elt: AnyCell<T>) => AnyCell<boolean>>,
345
  arr: CellArray<T>,
346
  name = "filter",
347
  nf?: NF
348
) => {
349
  const coll = collector<MapCell<AnyCell<T>[], NF>>(proxy);
1✔
350
  // Since predicate is a reactive function, we have to instantiate
351
  // the computation for each cell.
352
  // const keep = mapArrayCell(proxy, arr, predicate, "keep", nf);
353
  let prevFn: (elt: AnyCell<T>) => AnyCell<boolean>;
354
  const keep = proxy.map(
1✔
355
    [predicate, arr],
356
    (fn, cells, _prev: AnyCell<boolean>[]) => {
357
      // console.log({ keep: cells.length });
358
      // @todo if the predicate function has changed, collect previous mapped cells
359
      const keep = cells.map((cell) => {
2✔
360
        // We can reuse a cell only if the predicate hasn't changed.
361
        // @todo collect previously mapped cells for deleted cells in arr
362
        const reuse =
363
          prevFn === fn &&
6✔
364
          _prev?.find((_c) => _c.dependencies?.[0] === cell.id);
×
365
        return reuse || fn(cell);
6✔
366
      });
367
      prevFn = fn;
2✔
368
      return keep;
2✔
369
    },
370
    "keep",
371
    nf
372
  );
373
  return proxy.map(
1✔
374
    [arr, keep],
375
    (cells, _keep) =>
376
      coll(
2✔
377
        proxy.mapNoPrevious(
378
          _keep,
379
          (..._flat) => cells.filter((_, i) => _flat[i]),
6✔
380
          "filter.map"
381
        )
382
      ),
383
    name,
384
    nf
385
  );
386
};
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