tinybase 1.0.5 → 1.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- const e=(e,t)=>e.includes(t),t=(e,t)=>e.forEach(t),n=e=>e.length,s=e=>0==n(e),o=e=>e.slice(1),r=e=>null==e,c=(e,t,n)=>r(e)?n?.():t(e),l=(e,t)=>e?.has(t)??!1,i=e=>r(e)||0==(e=>e.size)(e),d=(e,t)=>e?.forEach(t),a=(e,t)=>e?.delete(t),p=e=>new Map(e),h=(e,t)=>e?.get(t),u=(e,t,n)=>r(n)?(a(e,t),e):e?.set(t,n),C=(e,t,n,s)=>(l(e,t)||(s?.(n),e.set(t,n)),h(e,t)),g=e=>new Set(e),k=(e,t,r)=>n(r)<2?((e,t)=>e?.add(t))(s(r)?e:C(e,r[0],g()),t):k(C(e,r[0],p()),t,o(r)),f=e=>{const n=(r,l,...i)=>c(r,(r=>s(i)?e(r,l):t([i[0],null],(e=>n(h(r,e),l,...o(i))))));return n},L=Object.freeze,v=(e=>{const t=new WeakMap;return n=>(t.has(n)||t.set(n,e(n)),t.get(n))})((o=>{let v,w,S,z=100,E=p(),I=1;const M=g(),b=p(),[j,x,y]=(e=>{let s,o=0;const l=[],i=p();return[(t,n,r=[])=>{s??=e();const c=l.pop()??""+o++;return u(i,c,[t,n,r]),k(n,c,r),c},(e,t=[],...n)=>f(d)(e,(e=>c(h(i,e),(([e])=>e(s,...t,...n)))),...t),e=>c(h(i,e),(([,t,s])=>(f(a)(t,e,...s),u(i,e),n(l)<1e3&&l.push(e),s)),(()=>[])),(e,o,l)=>c(h(i,e),(([e,,c])=>{const i=(...d)=>{const a=n(d);a==n(c)?e(s,...d,...l(d)):r(c[a])?t(o[a](...d),(e=>i(...d,e))):i(...d,c[a])};i()}))]})((()=>R)),B=p(),F=p(),O=[],T=[],W=(e,t)=>{I=0,o.transaction((()=>d(h(B,t),((t,n)=>d(t,((t,s)=>d(t,((t,c)=>r(t[e])?o.delCell(n,s,c,!0):o.setCell(n,s,c,t[e]))))))))),I=1},m=e=>{u(B,e),u(F,e),x(b,[e])},q=(e,s)=>t(((e,t)=>e.splice(0,t))(e,s??n(e)),m),A=()=>q(O,n(O)-z),D=o.addCellListener(null,null,null,((e,t,n,s,o,r)=>{if(I){c(v,(()=>{O.push(v),A(),q(T),v=void 0,S=1}));const e=C(E,t,p()),l=C(e,n,p()),d=C(l,s,[void 0,void 0],(e=>e[0]=r));d[1]=o,d[0]===d[1]&&i(u(l,s))&&i(u(e,n))&&i(u(E,t))&&(v=O.pop(),S=1),K()}})),G=(e="")=>(r(v)&&(v=""+w++,u(B,v,E),P(v,e),E=p(),S=1),v),H=()=>{s(O)||(T.unshift(G()),W(0,v),v=O.pop(),S=1)},J=()=>{s(T)||(O.push(v),v=T.shift(),W(1,v),S=1)},K=()=>{S&&(x(M),S=0)},N=e=>{const t=G(e);return K(),t},P=(e,t)=>(Q(e)&&h(F,e)!==t&&(u(F,e,t),x(b,[e])),R),Q=e=>l(B,e),R={setSize:e=>(z=e,A(),R),addCheckpoint:N,setCheckpoint:P,getStore:()=>o,getCheckpointIds:()=>[[...O],v,[...T]],forEachCheckpoint:e=>{return t=e,d(F,((e,n)=>t(n,e)));var t},hasCheckpoint:Q,getCheckpoint:e=>h(F,e),goBackward:()=>(H(),K(),R),goForward:()=>(J(),K(),R),goTo:t=>{const n=e(O,t)?H:e(T,t)?J:null;for(;!r(n)&&t!=v;)n();return K(),R},addCheckpointIdsListener:e=>j(e,M),addCheckpointListener:(e,t)=>j(t,b,[e]),delListener:e=>(y(e),R),clear:()=>(q(O),q(T),r(v)||m(v),v=void 0,w=0,N(),R),destroy:()=>{o.delListener(D)},getListenerStats:()=>({})};return L(R.clear())}));export{v as createCheckpoints};
1
+ const e=(e,t)=>e.includes(t),t=(e,t)=>e.forEach(t),n=e=>e.length,s=e=>0==n(e),o=e=>e.slice(1),r=(e,t)=>e.push(t),c=e=>e.pop(),l=e=>null==e,i=(e,t,n)=>l(e)?n?.():t(e),d=(e,t)=>e?.has(t)??!1,a=e=>l(e)||0==(e=>e.size)(e),h=(e,t)=>e?.forEach(t),u=(e,t)=>e?.delete(t),p=e=>new Map(e),C=(e,t)=>e?.get(t),g=(e,t,n)=>l(n)?(u(e,t),e):e?.set(t,n),k=(e,t,n,s)=>(d(e,t)||(s?.(n),e.set(t,n)),C(e,t)),f=e=>new Set(e),L=(e,t,r)=>n(r)<2?((e,t)=>e?.add(t))(s(r)?e:k(e,r[0],f()),t):L(k(e,r[0],p()),t,o(r)),v=e=>{const n=(r,c,...l)=>i(r,(r=>s(l)?e(r,c):t([l[0],null],(e=>n(C(r,e),c,...o(l))))));return n},w=Object.freeze,S=(e=>{const t=new WeakMap;return n=>(t.has(n)||t.set(n,e(n)),t.get(n))})((o=>{let S,z,E,I=100,M=p(),b=1;const j=f(),x=p(),[y,B,F]=(e=>{let s,o=0;const d=[],a=p();return[(t,n,r=[])=>{s??=e();const l=c(d)??""+o++;return g(a,l,[t,n,r]),L(n,l,r),l},(e,t=[],...n)=>v(h)(e,(e=>i(C(a,e),(([e])=>e(s,...t,...n)))),...t),e=>i(C(a,e),(([,t,s])=>(v(u)(t,e,...s),g(a,e),n(d)<1e3&&r(d,e),s)),(()=>[])),(e,o,r)=>i(C(a,e),(([e,,c])=>{const i=(...d)=>{const a=n(d);a==n(c)?e(s,...d,...r(d)):l(c[a])?t(o[a](...d),(e=>i(...d,e))):i(...d,c[a])};i()}))]})((()=>V)),O=p(),T=p(),W=[],m=[],q=(e,t)=>{b=0,o.transaction((()=>h(C(O,t),((t,n)=>h(t,((t,s)=>h(t,((t,r)=>l(t[e])?o.delCell(n,s,r,!0):o.setCell(n,s,r,t[e]))))))))),b=1},A=e=>{g(O,e),g(T,e),B(x,[e])},D=(e,s)=>t(((e,t)=>e.splice(0,t))(e,s??n(e)),A),G=()=>D(W,n(W)-I),H=o.addCellListener(null,null,null,((e,t,n,s,o,l)=>{if(b){i(S,(()=>{r(W,S),G(),D(m),S=void 0,E=1}));const e=k(M,t,p()),d=k(e,n,p()),h=k(d,s,[void 0,void 0],(e=>e[0]=l));h[1]=o,h[0]===h[1]&&a(g(d,s))&&a(g(e,n))&&a(g(M,t))&&(S=c(W),E=1),P()}})),J=(e="")=>(l(S)&&(S=""+z++,g(O,S,M),R(S,e),M=p(),E=1),S),K=()=>{s(W)||(m.unshift(J()),q(0,S),S=c(W),E=1)},N=()=>{s(m)||(r(W,S),S=m.shift(),q(1,S),E=1)},P=()=>{E&&(B(j),E=0)},Q=e=>{const t=J(e);return P(),t},R=(e,t)=>(U(e)&&C(T,e)!==t&&(g(T,e,t),B(x,[e])),V),U=e=>d(O,e),V={setSize:e=>(I=e,G(),V),addCheckpoint:Q,setCheckpoint:R,getStore:()=>o,getCheckpointIds:()=>[[...W],S,[...m]],forEachCheckpoint:e=>{return t=e,h(T,((e,n)=>t(n,e)));var t},hasCheckpoint:U,getCheckpoint:e=>C(T,e),goBackward:()=>(K(),P(),V),goForward:()=>(N(),P(),V),goTo:t=>{const n=e(W,t)?K:e(m,t)?N:null;for(;!l(n)&&t!=S;)n();return P(),V},addCheckpointIdsListener:e=>y(e,j),addCheckpointListener:(e,t)=>y(t,x,[e]),delListener:e=>(F(e),V),clear:()=>(D(W),D(m),l(S)||A(S),S=void 0,z=0,Q(),V),destroy:()=>{o.delListener(H)},getListenerStats:()=>({})};return w(V.clear())}));export{S as createCheckpoints};
Binary file
@@ -5,6 +5,8 @@ const arrayIsEmpty = (array) => arrayLength(array) == 0;
5
5
  const arrayReduce = (array, cb, initial) => array.reduce(cb, initial);
6
6
  const arrayFromSecond = (ids) => ids.slice(1);
7
7
  const arrayClear = (array, to) => array.splice(0, to);
8
+ const arrayPush = (array, value) => array.push(value);
9
+ const arrayPop = (array) => array.pop();
8
10
 
9
11
  const isUndefined = (thing) => thing == void 0;
10
12
  const ifNotUndefined = (value, then, otherwise) =>
@@ -76,7 +78,7 @@ const getListenerFunctions = (getThing) => {
76
78
  const allListeners = mapNew();
77
79
  const addListener = (listener, deepSet, idOrNulls = []) => {
78
80
  thing ??= getThing();
79
- const id = listenerPool.pop() ?? '' + nextId++;
81
+ const id = arrayPop(listenerPool) ?? '' + nextId++;
80
82
  mapSet(allListeners, id, [listener, deepSet, idOrNulls]);
81
83
  addDeepSet(deepSet, id, idOrNulls);
82
84
  return id;
@@ -97,7 +99,7 @@ const getListenerFunctions = (getThing) => {
97
99
  forDeepSet(collDel)(deepSet, id, ...idOrNulls);
98
100
  mapSet(allListeners, id);
99
101
  if (arrayLength(listenerPool) < 1e3) {
100
- listenerPool.push(id);
102
+ arrayPush(listenerPool, id);
101
103
  }
102
104
  return idOrNulls;
103
105
  },
@@ -173,7 +175,7 @@ const createCheckpoints = getCreateFunction((store) => {
173
175
  (_store, tableId, rowId, cellId, newCell, oldCell) => {
174
176
  if (listening) {
175
177
  ifNotUndefined(currentId, () => {
176
- backwardIds.push(currentId);
178
+ arrayPush(backwardIds, currentId);
177
179
  trimBackwardsIds();
178
180
  clearCheckpointIds(forwardIds);
179
181
  currentId = void 0;
@@ -192,7 +194,7 @@ const createCheckpoints = getCreateFunction((store) => {
192
194
  if (collIsEmpty(mapSet(row, cellId))) {
193
195
  if (collIsEmpty(mapSet(table, rowId))) {
194
196
  if (collIsEmpty(mapSet(delta, tableId))) {
195
- currentId = backwardIds.pop();
197
+ currentId = arrayPop(backwardIds);
196
198
  checkpointsChanged = 1;
197
199
  }
198
200
  }
@@ -216,13 +218,13 @@ const createCheckpoints = getCreateFunction((store) => {
216
218
  if (!arrayIsEmpty(backwardIds)) {
217
219
  forwardIds.unshift(addCheckpointImpl());
218
220
  updateStore(0, currentId);
219
- currentId = backwardIds.pop();
221
+ currentId = arrayPop(backwardIds);
220
222
  checkpointsChanged = 1;
221
223
  }
222
224
  };
223
225
  const goForwardImpl = () => {
224
226
  if (!arrayIsEmpty(forwardIds)) {
225
- backwardIds.push(currentId);
227
+ arrayPush(backwardIds, currentId);
226
228
  currentId = forwardIds.shift();
227
229
  updateStore(1, currentId);
228
230
  checkpointsChanged = 1;
@@ -12,6 +12,8 @@ const arrayLength = (array) => array.length;
12
12
  const arrayIsEmpty = (array) => arrayLength(array) == 0;
13
13
  const arrayReduce = (array, cb, initial) => array.reduce(cb, initial);
14
14
  const arrayFromSecond = (ids) => ids.slice(1);
15
+ const arrayPush = (array, value) => array.push(value);
16
+ const arrayPop = (array) => array.pop();
15
17
 
16
18
  const isUndefined = (thing) => thing == void 0;
17
19
  const ifNotUndefined = (value, then, otherwise) =>
@@ -203,7 +205,7 @@ const getListenerFunctions = (getThing) => {
203
205
  const allListeners = mapNew();
204
206
  const addListener = (listener, deepSet, idOrNulls = []) => {
205
207
  thing ??= getThing();
206
- const id = listenerPool.pop() ?? '' + nextId++;
208
+ const id = arrayPop(listenerPool) ?? '' + nextId++;
207
209
  mapSet(allListeners, id, [listener, deepSet, idOrNulls]);
208
210
  addDeepSet(deepSet, id, idOrNulls);
209
211
  return id;
@@ -224,7 +226,7 @@ const getListenerFunctions = (getThing) => {
224
226
  forDeepSet(collDel)(deepSet, id, ...idOrNulls);
225
227
  mapSet(allListeners, id);
226
228
  if (arrayLength(listenerPool) < 1e3) {
227
- listenerPool.push(id);
229
+ arrayPush(listenerPool, id);
228
230
  }
229
231
  return idOrNulls;
230
232
  },
@@ -13,6 +13,8 @@ const arrayLength = (array) => array.length;
13
13
  const arrayIsEmpty = (array) => arrayLength(array) == 0;
14
14
  const arrayReduce = (array, cb, initial) => array.reduce(cb, initial);
15
15
  const arrayFromSecond = (ids) => ids.slice(1);
16
+ const arrayPush = (array, value) => array.push(value);
17
+ const arrayPop = (array) => array.pop();
16
18
 
17
19
  const mathMax = Math.max;
18
20
  const mathMin = Math.min;
@@ -206,7 +208,7 @@ const getListenerFunctions = (getThing) => {
206
208
  const allListeners = mapNew();
207
209
  const addListener = (listener, deepSet, idOrNulls = []) => {
208
210
  thing ??= getThing();
209
- const id = listenerPool.pop() ?? '' + nextId++;
211
+ const id = arrayPop(listenerPool) ?? '' + nextId++;
210
212
  mapSet(allListeners, id, [listener, deepSet, idOrNulls]);
211
213
  addDeepSet(deepSet, id, idOrNulls);
212
214
  return id;
@@ -227,7 +229,7 @@ const getListenerFunctions = (getThing) => {
227
229
  forDeepSet(collDel)(deepSet, id, ...idOrNulls);
228
230
  mapSet(allListeners, id);
229
231
  if (arrayLength(listenerPool) < 1e3) {
230
- listenerPool.push(id);
232
+ arrayPush(listenerPool, id);
231
233
  }
232
234
  return idOrNulls;
233
235
  },
@@ -7,6 +7,8 @@ const arrayLength = (array) => array.length;
7
7
  const arrayIsEmpty = (array) => arrayLength(array) == 0;
8
8
  const arrayReduce = (array, cb, initial) => array.reduce(cb, initial);
9
9
  const arrayFromSecond = (ids) => ids.slice(1);
10
+ const arrayPush = (array, value) => array.push(value);
11
+ const arrayPop = (array) => array.pop();
10
12
 
11
13
  const isUndefined = (thing) => thing == void 0;
12
14
  const ifNotUndefined = (value, then, otherwise) =>
@@ -196,7 +198,7 @@ const getListenerFunctions = (getThing) => {
196
198
  const allListeners = mapNew();
197
199
  const addListener = (listener, deepSet, idOrNulls = []) => {
198
200
  thing ??= getThing();
199
- const id = listenerPool.pop() ?? '' + nextId++;
201
+ const id = arrayPop(listenerPool) ?? '' + nextId++;
200
202
  mapSet(allListeners, id, [listener, deepSet, idOrNulls]);
201
203
  addDeepSet(deepSet, id, idOrNulls);
202
204
  return id;
@@ -217,7 +219,7 @@ const getListenerFunctions = (getThing) => {
217
219
  forDeepSet(collDel)(deepSet, id, ...idOrNulls);
218
220
  mapSet(allListeners, id);
219
221
  if (arrayLength(listenerPool) < 1e3) {
220
- listenerPool.push(id);
222
+ arrayPush(listenerPool, id);
221
223
  }
222
224
  return idOrNulls;
223
225
  },
@@ -332,6 +332,35 @@ export type CellListener = (
332
332
  getCellChange: GetCellChange | undefined,
333
333
  ) => void;
334
334
 
335
+ /**
336
+ * The InvalidCellListener type describes a function that is used to listen to
337
+ * attempts to set invalid data to a Cell.
338
+ *
339
+ * A InvalidCellListener is provided when using the addInvalidCellListener
340
+ * method. See that method for specific examples.
341
+ *
342
+ * When called, a InvalidCellListener is given a reference to the Store, the Id
343
+ * of the Table, the Id of the Row, and the Id of Cell that were being attempted
344
+ * to be changed. It is also given the invalid value of the Cell, which could
345
+ * have been of absolutely any type. Since there could have been multiple failed
346
+ * attempts to set the Cell within a single transaction, this is an array
347
+ * containing each attempt, chronologically.
348
+ *
349
+ * @param store A reference to the Store that was being changed.
350
+ * @param tableId The Id of the Table that was being changed.
351
+ * @param rowId The Id of the Row that was being changed.
352
+ * @param cellId The Id of the Cell that was being changed.
353
+ * @param invalidCells An array of the values of the Cell that were invalid.
354
+ * @category Listener
355
+ */
356
+ export type InvalidCellListener = (
357
+ store: Store,
358
+ tableId: Id,
359
+ rowId: Id,
360
+ cellId: Id,
361
+ invalidCells: any[],
362
+ ) => void;
363
+
335
364
  /**
336
365
  * The GetCellChange type describes a function that returns information about
337
366
  * any Cell's changes during a transaction.
@@ -545,6 +574,9 @@ export type StoreListenerStats = {
545
574
  * unique Id. And the setPartialRow method lets you update multiple Cell values
546
575
  * in a Row without affecting the others.
547
576
  *
577
+ * You can listen to attempts to write invalid data to a Cell with the
578
+ * addInvalidCellListener method.
579
+ *
548
580
  * The transaction method is used to wrap multiple changes to the Store so that
549
581
  * the relevant listeners only fire once.
550
582
  *
@@ -2371,6 +2403,133 @@ export interface Store {
2371
2403
  mutator?: boolean,
2372
2404
  ): Id;
2373
2405
 
2406
+ /**
2407
+ * The addInvalidCellListener method registers a listener function with the
2408
+ * Store that will be called whenever invalid data was attempted to be written
2409
+ * to a Cell.
2410
+ *
2411
+ * You can either listen to a single Cell (by specifying the Table Id, Row Id,
2412
+ * and Cell Id as the method's first three parameters) or invalid attempts to
2413
+ * change any Cell (by providing `null` wildcards).
2414
+ *
2415
+ * All, some, or none of the `tableId`, `rowId`, and `cellId` parameters can
2416
+ * be wildcarded with `null`. You can listen to a specific Cell in a specific
2417
+ * Row in a specific Table, any Cell in any Row in any Table, for example - or
2418
+ * every other combination of wildcards.
2419
+ *
2420
+ * The provided listener is an InvalidCellListener function, and will be
2421
+ * called with a reference to the Store, the Id of the Table, the Id of the
2422
+ * Row, and the Id of Cell that were being attempted to be changed. It is also
2423
+ * given the invalid value of the Cell, which could have been of absolutely
2424
+ * any type. Since there could have been multiple failed attempts to set the
2425
+ * Cell within a single transaction, this is an array containing each attempt,
2426
+ * chronologically.
2427
+ *
2428
+ * Use the optional mutator parameter to indicate that there is code in the
2429
+ * listener that will mutate Store data. If set to `false` (or omitted), such
2430
+ * mutations will be silently ignored. All relevant mutator listeners (with
2431
+ * this flag set to `true`) are called _before_ any non-mutator listeners
2432
+ * (since the latter may become relevant due to changes made in the former).
2433
+ * The changes made by mutator listeners do not fire other mutating listeners,
2434
+ * though they will fire non-mutator listeners.
2435
+ *
2436
+ * @param tableId The Id of the Table to listen to, or `null` as a wildcard.
2437
+ * @param rowId The Id of the Row to listen to, or `null` as a wildcard.
2438
+ * @param cellId The Id of the Cell to listen to, or `null` as a wildcard.
2439
+ * @param listener The function that will be called whenever an attempt to
2440
+ * write invalid data to the matching Cell was made.
2441
+ * @param mutator An optional boolean that indicates that the listener mutates
2442
+ * Store data.
2443
+ * @returns A unique Id for the listener that can later be used to call it
2444
+ * explicitly, or to remove it.
2445
+ * @example
2446
+ * This example registers a listener that responds to any invalid changes to a
2447
+ * specific Cell.
2448
+ *
2449
+ * ```js
2450
+ * const store = createStore().setTables({
2451
+ * pets: {fido: {species: 'dog', color: 'brown'}},
2452
+ * });
2453
+ * const listenerId = store.addInvalidCellListener(
2454
+ * 'pets',
2455
+ * 'fido',
2456
+ * 'color',
2457
+ * (store, tableId, rowId, cellId, invalidCells) => {
2458
+ * console.log('Invalid color cell in fido row in pets table');
2459
+ * console.log(invalidCells);
2460
+ * },
2461
+ * );
2462
+ *
2463
+ * store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
2464
+ * // -> 'Invalid color cell in fido row in pets table'
2465
+ * // -> [{r: '96', g: '4B', b: '00'}]
2466
+ *
2467
+ * store.delListener(listenerId);
2468
+ * ```
2469
+ * @example
2470
+ * This example registers a listener that responds to any invalid changes to
2471
+ * any Cell.
2472
+ *
2473
+ * ```js
2474
+ * const store = createStore().setTables({
2475
+ * pets: {fido: {species: 'dog', color: 'brown'}},
2476
+ * });
2477
+ * const listenerId = store.addInvalidCellListener(
2478
+ * null,
2479
+ * null,
2480
+ * null,
2481
+ * (store, tableId, rowId, cellId) => {
2482
+ * console.log(
2483
+ * `Invalid ${cellId} cell in ${rowId} row in ${tableId} table`,
2484
+ * );
2485
+ * },
2486
+ * );
2487
+ *
2488
+ * store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
2489
+ * // -> 'Invalid color cell in fido row in pets table'
2490
+ * store.setTable('sales', {fido: {date: new Date()}});
2491
+ * // -> 'Invalid date cell in fido row in sales table'
2492
+ *
2493
+ * store.delListener(listenerId);
2494
+ * ```
2495
+ * @example
2496
+ * This example registers a listener that responds to any changes to a
2497
+ * specific Cell, and which also mutates the Store itself.
2498
+ *
2499
+ * ```js
2500
+ * const store = createStore().setTables({
2501
+ * pets: {fido: {species: 'dog', color: 'brown'}},
2502
+ * });
2503
+ * const listenerId = store.addInvalidCellListener(
2504
+ * 'pets',
2505
+ * 'fido',
2506
+ * 'color',
2507
+ * (store, tableId, rowId, cellId, invalidCells) =>
2508
+ * store.setCell(
2509
+ * 'meta',
2510
+ * 'invalid_updates',
2511
+ * `${tableId}_${rowId}_${cellId}`,
2512
+ * JSON.stringify(invalidCells[0]),
2513
+ * ),
2514
+ * true,
2515
+ * );
2516
+ *
2517
+ * store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
2518
+ * console.log(store.getRow('meta', 'invalid_updates'));
2519
+ * // -> {'pets_fido_color': '{"r":"96","g":"4B","b":"00"}'}
2520
+ *
2521
+ * store.delListener(listenerId);
2522
+ * ```
2523
+ * @category Listener
2524
+ */
2525
+ addInvalidCellListener(
2526
+ tableId: IdOrNull,
2527
+ rowId: IdOrNull,
2528
+ cellId: IdOrNull,
2529
+ listener: InvalidCellListener,
2530
+ mutator?: boolean,
2531
+ ): Id;
2532
+
2374
2533
  /**
2375
2534
  * The callListener method provides a way for you to manually provoke a
2376
2535
  * listener to be called, even if the underlying data hasn't changed.
@@ -15,6 +15,8 @@ const arrayIsEmpty = (array) => arrayLength(array) == 0;
15
15
  const arrayReduce = (array, cb, initial) => array.reduce(cb, initial);
16
16
  const arrayFilter = (array, cb) => array.filter(cb);
17
17
  const arrayFromSecond = (ids) => ids.slice(1);
18
+ const arrayPush = (array, value) => array.push(value);
19
+ const arrayPop = (array) => array.pop();
18
20
 
19
21
  const jsonString = (obj) =>
20
22
  JSON.stringify(obj, (_key, value) =>
@@ -125,7 +127,7 @@ const getListenerFunctions = (getThing) => {
125
127
  const allListeners = mapNew();
126
128
  const addListener = (listener, deepSet, idOrNulls = []) => {
127
129
  thing ??= getThing();
128
- const id = listenerPool.pop() ?? '' + nextId++;
130
+ const id = arrayPop(listenerPool) ?? '' + nextId++;
129
131
  mapSet(allListeners, id, [listener, deepSet, idOrNulls]);
130
132
  addDeepSet(deepSet, id, idOrNulls);
131
133
  return id;
@@ -146,7 +148,7 @@ const getListenerFunctions = (getThing) => {
146
148
  forDeepSet(collDel)(deepSet, id, ...idOrNulls);
147
149
  mapSet(allListeners, id);
148
150
  if (arrayLength(listenerPool) < 1e3) {
149
- listenerPool.push(id);
151
+ arrayPush(listenerPool, id);
150
152
  }
151
153
  return idOrNulls;
152
154
  },
@@ -207,6 +209,7 @@ const createStore = () => {
207
209
  const changedRowIds = mapNew();
208
210
  const changedCellIds = mapNew();
209
211
  const changedCells = mapNew();
212
+ const invalidCells = mapNew();
210
213
  const schemaMap = mapNew();
211
214
  const schemaDefaultRows = mapNew();
212
215
  const tablesMap = mapNew();
@@ -217,6 +220,7 @@ const createStore = () => {
217
220
  const rowListeners = mapNewPair();
218
221
  const cellIdsListeners = mapNewPair();
219
222
  const cellListeners = mapNewPair();
223
+ const invalidCellListeners = mapNewPair();
220
224
  const [addListener, callListeners, delListenerImpl, callListenerImpl] =
221
225
  getListenerFunctions(() => store);
222
226
  const validateSchema = (schema) =>
@@ -240,13 +244,13 @@ const createStore = () => {
240
244
  const validateTables = (tables) => validate(tables, validateTable);
241
245
  const validateTable = (table, tableId) =>
242
246
  (!hasSchema || collHas(schemaMap, tableId)) &&
243
- validate(table, (row) => validateRow(tableId, row));
244
- const validateRow = (tableId, row, skipDefaults) =>
247
+ validate(table, (row, rowId) => validateRow(tableId, rowId, row));
248
+ const validateRow = (tableId, rowId, row, skipDefaults) =>
245
249
  validate(
246
250
  skipDefaults ? row : addDefaultsToRow(row, tableId),
247
251
  (cell, cellId) =>
248
252
  ifNotUndefined(
249
- getValidatedCell(tableId, cellId, cell),
253
+ getValidatedCell(tableId, rowId, cellId, cell),
250
254
  (validCell) => {
251
255
  row[cellId] = validCell;
252
256
  return true;
@@ -254,15 +258,17 @@ const createStore = () => {
254
258
  () => false,
255
259
  ),
256
260
  );
257
- const getValidatedCell = (tableId, cellId, cell) =>
261
+ const getValidatedCell = (tableId, rowId, cellId, cell) =>
258
262
  hasSchema
259
263
  ? ifNotUndefined(
260
264
  mapGet(mapGet(schemaMap, tableId), cellId),
261
265
  (cellSchema) =>
262
- getCellType(cell) != cellSchema[TYPE] ? cellSchema[DEFAULT] : cell,
266
+ getCellType(cell) != cellSchema[TYPE]
267
+ ? cellInvalid(tableId, rowId, cellId, cell, cellSchema[DEFAULT])
268
+ : cell,
263
269
  )
264
270
  : isUndefined(getCellType(cell))
265
- ? void 0
271
+ ? cellInvalid(tableId, rowId, cellId, cell)
266
272
  : cell;
267
273
  const addDefaultsToRow = (row, tableId) => {
268
274
  ifNotUndefined(mapGet(schemaDefaultRows, tableId), (defaultRow) =>
@@ -398,6 +404,17 @@ const createStore = () => {
398
404
  cellId,
399
405
  oldCell,
400
406
  );
407
+ const cellInvalid = (tableId, rowId, cellId, invalidCell, defaultedCell) => {
408
+ arrayPush(
409
+ mapEnsure(
410
+ mapEnsure(mapEnsure(invalidCells, tableId, mapNew()), rowId, mapNew()),
411
+ cellId,
412
+ [],
413
+ ),
414
+ invalidCell,
415
+ );
416
+ return defaultedCell;
417
+ };
401
418
  const getCellChange = (tableId, rowId, cellId) => {
402
419
  const changedRow = mapGet(mapGet(changedCells, tableId), rowId);
403
420
  const newCell = getCell(tableId, rowId, cellId);
@@ -405,6 +422,25 @@ const createStore = () => {
405
422
  ? [true, mapGet(changedRow, cellId), newCell]
406
423
  : [false, newCell, newCell];
407
424
  };
425
+ const callInvalidCellListeners = (mutator) => {
426
+ if (!collIsEmpty(invalidCellListeners[mutator])) {
427
+ collForEach(
428
+ mutator
429
+ ? mapClone(invalidCells, (table) => mapClone(table, mapClone))
430
+ : invalidCells,
431
+ (rows, tableId) =>
432
+ collForEach(rows, (cells, rowId) =>
433
+ collForEach(cells, (invalidCell, cellId) =>
434
+ callListeners(
435
+ invalidCellListeners[mutator],
436
+ [tableId, rowId, cellId],
437
+ invalidCell,
438
+ ),
439
+ ),
440
+ ),
441
+ );
442
+ }
443
+ };
408
444
  const callListenersForChanges = (mutator) => {
409
445
  const emptyIdListeners =
410
446
  collIsEmpty(cellIdsListeners[mutator]) &&
@@ -498,46 +534,50 @@ const createStore = () => {
498
534
  const getJson = () => jsonString(tablesMap);
499
535
  const getSchemaJson = () => jsonString(schemaMap);
500
536
  const setTables = (tables) => {
501
- if (validateTables(tables)) {
502
- transaction(() => setValidTables(tables));
503
- }
537
+ validateTables(tables)
538
+ ? transaction(() => setValidTables(tables))
539
+ : transaction();
504
540
  return store;
505
541
  };
506
542
  const setTable = (tableId, table) => {
507
- if (validateTable(table, tableId)) {
508
- transaction(() => setValidTable(tableId, table));
509
- }
543
+ validateTable(table, tableId)
544
+ ? transaction(() => setValidTable(tableId, table))
545
+ : transaction();
510
546
  return store;
511
547
  };
512
548
  const setRow = (tableId, rowId, row) => {
513
- if (validateRow(tableId, row)) {
514
- setValidRowTransaction(tableId, rowId, row);
515
- }
549
+ validateRow(tableId, rowId, row)
550
+ ? setValidRowTransaction(tableId, rowId, row)
551
+ : transaction();
516
552
  return store;
517
553
  };
518
554
  const addRow = (tableId, row) => {
519
555
  let rowId = void 0;
520
- if (validateRow(tableId, row)) {
521
- rowId = getNewRowId(mapGet(tablesMap, tableId));
522
- setValidRowTransaction(tableId, rowId, row);
523
- }
556
+ validateRow(tableId, rowId, row)
557
+ ? setValidRowTransaction(
558
+ tableId,
559
+ (rowId = getNewRowId(mapGet(tablesMap, tableId))),
560
+ row,
561
+ )
562
+ : transaction();
524
563
  return rowId;
525
564
  };
526
565
  const setPartialRow = (tableId, rowId, partialRow) => {
527
- if (validateRow(tableId, partialRow, 1)) {
528
- transaction(() => {
529
- const table = getOrCreateTable(tableId);
530
- objForEach(partialRow, (cell, cellId) =>
531
- setCellIntoDefaultRow(tableId, table, rowId, cellId, cell),
532
- );
533
- });
534
- }
566
+ validateRow(tableId, rowId, partialRow, 1)
567
+ ? transaction(() => {
568
+ const table = getOrCreateTable(tableId);
569
+ objForEach(partialRow, (cell, cellId) =>
570
+ setCellIntoDefaultRow(tableId, table, rowId, cellId, cell),
571
+ );
572
+ })
573
+ : transaction();
535
574
  return store;
536
575
  };
537
576
  const setCell = (tableId, rowId, cellId, cell) => {
538
577
  ifNotUndefined(
539
578
  getValidatedCell(
540
579
  tableId,
580
+ rowId,
541
581
  cellId,
542
582
  isFunction(cell) ? cell(getCell(tableId, rowId, cellId)) : cell,
543
583
  ),
@@ -551,6 +591,7 @@ const createStore = () => {
551
591
  validCell,
552
592
  ),
553
593
  ),
594
+ transaction,
554
595
  );
555
596
  return store;
556
597
  };
@@ -611,16 +652,24 @@ const createStore = () => {
611
652
  return;
612
653
  }
613
654
  transactions++;
614
- const result = actions();
655
+ const result = actions?.();
615
656
  transactions--;
616
657
  if (transactions == 0) {
617
658
  transactions = 1;
659
+ callInvalidCellListeners(1);
618
660
  callListenersForChanges(1);
619
661
  transactions = -1;
662
+ callInvalidCellListeners(0);
620
663
  callListenersForChanges(0);
621
664
  transactions = 0;
622
665
  arrayForEach(
623
- [changedCells, changedTableIds, changedRowIds, changedCellIds],
666
+ [
667
+ changedCells,
668
+ invalidCells,
669
+ changedTableIds,
670
+ changedRowIds,
671
+ changedCellIds,
672
+ ],
624
673
  collClear,
625
674
  );
626
675
  }
@@ -660,6 +709,12 @@ const createStore = () => {
660
709
  rowId,
661
710
  cellId,
662
711
  ]);
712
+ const addInvalidCellListener = (tableId, rowId, cellId, listener, mutator) =>
713
+ addListener(listener, invalidCellListeners[mutator ? 1 : 0], [
714
+ tableId,
715
+ rowId,
716
+ cellId,
717
+ ]);
663
718
  const callListener = (listenerId) => {
664
719
  callListenerImpl(listenerId, [getTableIds, getRowIds, getCellIds], (ids) =>
665
720
  isUndefined(ids[2]) ? [] : Array(2).fill(getCell(...ids)),
@@ -717,6 +772,7 @@ const createStore = () => {
717
772
  addRowListener,
718
773
  addCellIdsListener,
719
774
  addCellListener,
775
+ addInvalidCellListener,
720
776
  callListener,
721
777
  delListener,
722
778
  getListenerStats,