@isograph/react-disposable-state 0.0.4 → 0.1.1

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.
package/src/CacheItem.ts CHANGED
@@ -2,15 +2,15 @@ import {
2
2
  CleanupFn,
3
3
  Factory,
4
4
  ItemCleanupPair,
5
- } from "@isograph/disposable-types";
5
+ } from '@isograph/disposable-types';
6
6
 
7
7
  const DEFAULT_TEMPORARY_RETAIN_TIME = 5000;
8
8
 
9
9
  export type NotInParentCacheAndDisposed = {
10
- kind: "NotInParentCacheAndDisposed";
10
+ kind: 'NotInParentCacheAndDisposed';
11
11
  };
12
12
  export type NotInParentCacheAndNotDisposed<T> = {
13
- kind: "NotInParentCacheAndNotDisposed";
13
+ kind: 'NotInParentCacheAndNotDisposed';
14
14
  value: T;
15
15
  disposeValue: () => void;
16
16
 
@@ -18,7 +18,7 @@ export type NotInParentCacheAndNotDisposed<T> = {
18
18
  permanentRetainCount: number;
19
19
  };
20
20
  export type InParentCacheAndNotDisposed<T> = {
21
- kind: "InParentCacheAndNotDisposed";
21
+ kind: 'InParentCacheAndNotDisposed';
22
22
  value: T;
23
23
  disposeValue: () => void;
24
24
  removeFromParentCache: () => void;
@@ -43,21 +43,25 @@ export type CacheItemOptions = {
43
43
  // TODO convert cacheitem impl to a getter and setter and free functions
44
44
 
45
45
  /**
46
+ * CacheItem:
47
+ *
48
+ * Terminology:
46
49
  * - TRC = Temporary Retain Count
47
50
  * - PRC = Permanent Retain Count
48
51
  *
49
- * Rules:
50
- * - In parent cache <=> TRC > 0
51
- * - Removed from parent cache <=> TRC === 0
52
- * - In parent cache => not disposed
53
- * - Disposed => removed from parent cache + PRC === 0
54
- *
55
52
  * A CacheItem<T> can be in three states:
56
- * - Removed from the parent cache, item disposed, TRC === 0, PRC === 0
57
- * - Removed from the parent cache, item not disposed, PRC > 0, TRC === 0
58
- * - In parent cache, item not disposed, TRC > 0, PRC >= 0
53
+ * In parent cache? | Item disposed? | TRC | PRC | Name
54
+ * -----------------+----------------+-----+-----+-------------------------------
55
+ * In parent cache | Not disposed | >0 | >=0 | InParentCacheAndNotDisposed
56
+ * Removed | Not disposed | 0 | >0 | NotInParentCacheAndNotDisposed
57
+ * Removed | Disposed | 0 | 0 | NotInParentCacheAndNotDisposed
58
+ *
59
+ * A cache item can only move down rows. As in, if its in the parent cache,
60
+ * it can be removed. It can never be replaced in the parent cache. (If a
61
+ * parent cache becomes full again, it will contain a new CacheItem.) The
62
+ * contained item can be disposed, but never un-disposed.
59
63
  *
60
- * Valid transitions are:
64
+ * So, the valid transitions are:
61
65
  * - InParentCacheAndNotDisposed => NotInParentCacheAndNotDisposed
62
66
  * - InParentCacheAndNotDisposed => NotInParentCacheAndDisposed
63
67
  * - NotInParentCacheAndNotDisposed => NotInParentCacheAndDisposed
@@ -74,12 +78,12 @@ export class CacheItem<T> {
74
78
  constructor(
75
79
  factory: Factory<T>,
76
80
  removeFromParentCache: CleanupFn,
77
- options: CacheItemOptions | void
81
+ options: CacheItemOptions | void,
78
82
  ) {
79
83
  this.__options = options ?? null;
80
84
  const [value, disposeValue] = factory();
81
85
  this.__state = {
82
- kind: "InParentCacheAndNotDisposed",
86
+ kind: 'InParentCacheAndNotDisposed',
83
87
  value,
84
88
  disposeValue,
85
89
  removeFromParentCache,
@@ -92,26 +96,26 @@ export class CacheItem<T> {
92
96
 
93
97
  getValue(): T {
94
98
  switch (this.__state.kind) {
95
- case "InParentCacheAndNotDisposed": {
99
+ case 'InParentCacheAndNotDisposed': {
96
100
  return this.__state.value;
97
101
  }
98
- case "NotInParentCacheAndNotDisposed": {
102
+ case 'NotInParentCacheAndNotDisposed': {
99
103
  return this.__state.value;
100
104
  }
101
105
  default: {
102
106
  throw new Error(
103
- "Attempted to access disposed value from CacheItem. " +
104
- "This indicates a bug in react-disposable-state."
107
+ 'Attempted to access disposed value from CacheItem. ' +
108
+ 'This indicates a bug in react-disposable-state.',
105
109
  );
106
110
  }
107
111
  }
108
112
  }
109
113
 
110
114
  permanentRetainIfNotDisposed(
111
- disposeOfTemporaryRetain: CleanupFn
115
+ disposeOfTemporaryRetain: CleanupFn,
112
116
  ): ItemCleanupPair<T> | null {
113
117
  switch (this.__state.kind) {
114
- case "InParentCacheAndNotDisposed": {
118
+ case 'InParentCacheAndNotDisposed': {
115
119
  let cleared = false;
116
120
  this.__state.permanentRetainCount++;
117
121
  disposeOfTemporaryRetain();
@@ -120,35 +124,35 @@ export class CacheItem<T> {
120
124
  () => {
121
125
  if (cleared) {
122
126
  throw new Error(
123
- "A permanent retain should only be cleared once. " +
124
- "This indicates a bug in react-disposable-state."
127
+ 'A permanent retain should only be cleared once. ' +
128
+ 'This indicates a bug in react-disposable-state.',
125
129
  );
126
130
  }
127
131
  cleared = true;
128
132
  switch (this.__state.kind) {
129
- case "InParentCacheAndNotDisposed": {
133
+ case 'InParentCacheAndNotDisposed': {
130
134
  this.__state.permanentRetainCount--;
131
135
  this.__maybeExitInParentCacheAndNotDisposedState(this.__state);
132
136
  return;
133
137
  }
134
- case "NotInParentCacheAndNotDisposed": {
138
+ case 'NotInParentCacheAndNotDisposed': {
135
139
  this.__state.permanentRetainCount--;
136
140
  this.__maybeExitNotInParentCacheAndNotDisposedState(
137
- this.__state
141
+ this.__state,
138
142
  );
139
143
  return;
140
144
  }
141
145
  default: {
142
146
  throw new Error(
143
- "CacheItem was in a disposed state, but there existed a permanent retain. " +
144
- "This indicates a bug in react-disposable-state."
147
+ 'CacheItem was in a disposed state, but there existed a permanent retain. ' +
148
+ 'This indicates a bug in react-disposable-state.',
145
149
  );
146
150
  }
147
151
  }
148
152
  },
149
153
  ];
150
154
  }
151
- case "NotInParentCacheAndNotDisposed": {
155
+ case 'NotInParentCacheAndNotDisposed': {
152
156
  let cleared = false;
153
157
  this.__state.permanentRetainCount++;
154
158
  disposeOfTemporaryRetain();
@@ -157,23 +161,23 @@ export class CacheItem<T> {
157
161
  () => {
158
162
  if (cleared) {
159
163
  throw new Error(
160
- "A permanent retain should only be cleared once. " +
161
- "This indicates a bug in react-disposable-state."
164
+ 'A permanent retain should only be cleared once. ' +
165
+ 'This indicates a bug in react-disposable-state.',
162
166
  );
163
167
  }
164
168
  cleared = true;
165
169
  switch (this.__state.kind) {
166
- case "NotInParentCacheAndNotDisposed": {
170
+ case 'NotInParentCacheAndNotDisposed': {
167
171
  this.__state.permanentRetainCount--;
168
172
  this.__maybeExitNotInParentCacheAndNotDisposedState(
169
- this.__state
173
+ this.__state,
170
174
  );
171
175
  return;
172
176
  }
173
177
  default: {
174
178
  throw new Error(
175
- "CacheItem was in an unexpected state. " +
176
- "This indicates a bug in react-disposable-state."
179
+ 'CacheItem was in an unexpected state. ' +
180
+ 'This indicates a bug in react-disposable-state.',
177
181
  );
178
182
  }
179
183
  }
@@ -189,23 +193,23 @@ export class CacheItem<T> {
189
193
 
190
194
  temporaryRetain(): CleanupFn {
191
195
  type TemporaryRetainStatus =
192
- | "Uncleared"
193
- | "ClearedByCallback"
194
- | "ClearedByTimeout";
196
+ | 'Uncleared'
197
+ | 'ClearedByCallback'
198
+ | 'ClearedByTimeout';
195
199
 
196
200
  switch (this.__state.kind) {
197
- case "InParentCacheAndNotDisposed": {
198
- let status: TemporaryRetainStatus = "Uncleared";
201
+ case 'InParentCacheAndNotDisposed': {
202
+ let status: TemporaryRetainStatus = 'Uncleared';
199
203
  this.__state.temporaryRetainCount++;
200
204
  const clearTemporaryRetainByCallack: CleanupFn = () => {
201
- if (status === "ClearedByCallback") {
205
+ if (status === 'ClearedByCallback') {
202
206
  throw new Error(
203
- "A temporary retain should only be cleared once. " +
204
- "This indicates a bug in react-disposable-state."
207
+ 'A temporary retain should only be cleared once. ' +
208
+ 'This indicates a bug in react-disposable-state.',
205
209
  );
206
- } else if (status === "Uncleared") {
210
+ } else if (status === 'Uncleared') {
207
211
  switch (this.__state.kind) {
208
- case "InParentCacheAndNotDisposed": {
212
+ case 'InParentCacheAndNotDisposed': {
209
213
  this.__state.temporaryRetainCount--;
210
214
  this.__maybeExitInParentCacheAndNotDisposedState(this.__state);
211
215
  clearTimeout(timeoutId);
@@ -213,8 +217,8 @@ export class CacheItem<T> {
213
217
  }
214
218
  default: {
215
219
  throw new Error(
216
- "A temporary retain was cleared, for which the CacheItem is in an invalid state. " +
217
- "This indicates a bug in react-disposable-state."
220
+ 'A temporary retain was cleared, for which the CacheItem is in an invalid state. ' +
221
+ 'This indicates a bug in react-disposable-state.',
218
222
  );
219
223
  }
220
224
  }
@@ -222,17 +226,17 @@ export class CacheItem<T> {
222
226
  };
223
227
 
224
228
  const clearTemporaryRetainByTimeout = () => {
225
- status = "ClearedByTimeout";
229
+ status = 'ClearedByTimeout';
226
230
  switch (this.__state.kind) {
227
- case "InParentCacheAndNotDisposed": {
231
+ case 'InParentCacheAndNotDisposed': {
228
232
  this.__state.temporaryRetainCount--;
229
233
  this.__maybeExitInParentCacheAndNotDisposedState(this.__state);
230
234
  return;
231
235
  }
232
236
  default: {
233
237
  throw new Error(
234
- "A temporary retain was cleared, for which the CacheItem is in an invalid state. " +
235
- "This indicates a bug in react-disposable-state."
238
+ 'A temporary retain was cleared, for which the CacheItem is in an invalid state. ' +
239
+ 'This indicates a bug in react-disposable-state.',
236
240
  );
237
241
  }
238
242
  }
@@ -240,14 +244,14 @@ export class CacheItem<T> {
240
244
 
241
245
  const timeoutId = setTimeout(
242
246
  clearTemporaryRetainByTimeout,
243
- this.__options?.temporaryRetainTime ?? DEFAULT_TEMPORARY_RETAIN_TIME
247
+ this.__options?.temporaryRetainTime ?? DEFAULT_TEMPORARY_RETAIN_TIME,
244
248
  );
245
249
  return clearTemporaryRetainByCallack;
246
250
  }
247
251
  default: {
248
252
  throw new Error(
249
- "temporaryRetain was called, for which the CacheItem is in an invalid state. " +
250
- "This indicates a bug in react-disposable-state."
253
+ 'temporaryRetain was called, for which the CacheItem is in an invalid state. ' +
254
+ 'This indicates a bug in react-disposable-state.',
251
255
  );
252
256
  }
253
257
  }
@@ -255,58 +259,58 @@ export class CacheItem<T> {
255
259
 
256
260
  permanentRetain(): CleanupFn {
257
261
  switch (this.__state.kind) {
258
- case "InParentCacheAndNotDisposed": {
262
+ case 'InParentCacheAndNotDisposed': {
259
263
  let cleared = false;
260
264
  this.__state.permanentRetainCount++;
261
265
  return () => {
262
266
  if (cleared) {
263
267
  throw new Error(
264
- "A permanent retain should only be cleared once. " +
265
- "This indicates a bug in react-disposable-state."
268
+ 'A permanent retain should only be cleared once. ' +
269
+ 'This indicates a bug in react-disposable-state.',
266
270
  );
267
271
  }
268
272
  cleared = true;
269
273
  switch (this.__state.kind) {
270
- case "InParentCacheAndNotDisposed": {
274
+ case 'InParentCacheAndNotDisposed': {
271
275
  this.__state.permanentRetainCount--;
272
276
  this.__maybeExitInParentCacheAndNotDisposedState(this.__state);
273
277
  return;
274
278
  }
275
- case "NotInParentCacheAndNotDisposed": {
279
+ case 'NotInParentCacheAndNotDisposed': {
276
280
  this.__state.permanentRetainCount--;
277
281
  this.__maybeExitNotInParentCacheAndNotDisposedState(this.__state);
278
282
  return;
279
283
  }
280
284
  default: {
281
285
  throw new Error(
282
- "CacheItem was in a disposed state, but there existed a permanent retain. " +
283
- "This indicates a bug in react-disposable-state."
286
+ 'CacheItem was in a disposed state, but there existed a permanent retain. ' +
287
+ 'This indicates a bug in react-disposable-state.',
284
288
  );
285
289
  }
286
290
  }
287
291
  };
288
292
  }
289
- case "NotInParentCacheAndNotDisposed": {
293
+ case 'NotInParentCacheAndNotDisposed': {
290
294
  let cleared = false;
291
295
  this.__state.permanentRetainCount++;
292
296
  return () => {
293
297
  if (cleared) {
294
298
  throw new Error(
295
- "A permanent retain should only be cleared once. " +
296
- "This indicates a bug in react-disposable-state."
299
+ 'A permanent retain should only be cleared once. ' +
300
+ 'This indicates a bug in react-disposable-state.',
297
301
  );
298
302
  }
299
303
  cleared = true;
300
304
  switch (this.__state.kind) {
301
- case "NotInParentCacheAndNotDisposed": {
305
+ case 'NotInParentCacheAndNotDisposed': {
302
306
  this.__state.permanentRetainCount--;
303
307
  this.__maybeExitNotInParentCacheAndNotDisposedState(this.__state);
304
308
  return;
305
309
  }
306
310
  default: {
307
311
  throw new Error(
308
- "CacheItem was in an unexpected state. " +
309
- "This indicates a bug in react-disposable-state."
312
+ 'CacheItem was in an unexpected state. ' +
313
+ 'This indicates a bug in react-disposable-state.',
310
314
  );
311
315
  }
312
316
  }
@@ -314,26 +318,26 @@ export class CacheItem<T> {
314
318
  }
315
319
  default: {
316
320
  throw new Error(
317
- "permanentRetain was called, but the CacheItem is in an invalid state. " +
318
- "This indicates a bug in react-disposable-state."
321
+ 'permanentRetain was called, but the CacheItem is in an invalid state. ' +
322
+ 'This indicates a bug in react-disposable-state.',
319
323
  );
320
324
  }
321
325
  }
322
326
  }
323
327
 
324
328
  private __maybeExitInParentCacheAndNotDisposedState(
325
- state: InParentCacheAndNotDisposed<T>
329
+ state: InParentCacheAndNotDisposed<T>,
326
330
  ) {
327
331
  if (state.temporaryRetainCount === 0 && state.permanentRetainCount === 0) {
328
332
  state.removeFromParentCache();
329
333
  state.disposeValue();
330
334
  this.__state = {
331
- kind: "NotInParentCacheAndDisposed",
335
+ kind: 'NotInParentCacheAndDisposed',
332
336
  };
333
337
  } else if (state.temporaryRetainCount === 0) {
334
338
  state.removeFromParentCache();
335
339
  this.__state = {
336
- kind: "NotInParentCacheAndNotDisposed",
340
+ kind: 'NotInParentCacheAndNotDisposed',
337
341
  value: state.value,
338
342
  disposeValue: state.disposeValue,
339
343
  permanentRetainCount: state.permanentRetainCount,
@@ -342,12 +346,12 @@ export class CacheItem<T> {
342
346
  }
343
347
 
344
348
  private __maybeExitNotInParentCacheAndNotDisposedState(
345
- state: NotInParentCacheAndNotDisposed<T>
349
+ state: NotInParentCacheAndNotDisposed<T>,
346
350
  ) {
347
351
  if (state.permanentRetainCount === 0) {
348
352
  state.disposeValue();
349
353
  this.__state = {
350
- kind: "NotInParentCacheAndDisposed",
354
+ kind: 'NotInParentCacheAndDisposed',
351
355
  };
352
356
  }
353
357
  }
@@ -356,7 +360,7 @@ export class CacheItem<T> {
356
360
  export function createTemporarilyRetainedCacheItem<T>(
357
361
  factory: Factory<T>,
358
362
  removeFromParentCache: CleanupFn,
359
- options: CacheItemOptions | void
363
+ options: CacheItemOptions | void,
360
364
  ): [CacheItem<T>, CleanupFn] {
361
365
  const cacheItem = new CacheItem(factory, removeFromParentCache, options);
362
366
  const disposeTemporaryRetain = cacheItem.temporaryRetain();
@@ -1,41 +1,45 @@
1
- import { describe, assert, test, vi, expect } from "vitest";
2
- import { ParentCache } from "./ParentCache";
3
- import { ItemCleanupPair } from "@isograph/disposable-types";
4
- import { CacheItem } from "./CacheItem";
1
+ import { describe, assert, test, vi, expect } from 'vitest';
2
+ import { ParentCache } from './ParentCache';
3
+ import { ItemCleanupPair } from '@isograph/disposable-types';
4
+ import { CacheItem } from './CacheItem';
5
5
 
6
6
  function getValue<T>(cache: ParentCache<T>): CacheItem<T> | null {
7
- return (cache as any).__item as CacheItem<T> | null;
7
+ return (cache as any).__cacheItem as CacheItem<T> | null;
8
8
  }
9
9
 
10
- describe("ParentCache", () => {
11
- test("Populated, emptied, repopulated cache is not re-emptied by original temporary retain being disposed", () => {
12
- const factory = vi.fn(() => {
13
- const pair: ItemCleanupPair<number> = [1, vi.fn()];
14
- return pair;
15
- });
16
- const parentCache = new ParentCache<number>(factory);
10
+ describe('ParentCache', () => {
11
+ test(
12
+ 'Populated, emptied, repopulated cache is not ' +
13
+ 're-emptied by original temporary retain being disposed',
14
+ () => {
15
+ const factory = vi.fn(() => {
16
+ const pair: ItemCleanupPair<number> = [1, vi.fn()];
17
+ return pair;
18
+ });
19
+ const parentCache = new ParentCache<number>(factory);
17
20
 
18
- const [_cacheItem, value, clearTemporaryRetain] =
19
- parentCache.getOrPopulateAndTemporaryRetain();
21
+ const [_cacheItem, value, clearTemporaryRetain] =
22
+ parentCache.getOrPopulateAndTemporaryRetain();
20
23
 
21
- expect(factory.mock.calls.length).toBe(1);
22
- assert(value === 1);
23
- assert(getValue(parentCache) != null);
24
+ expect(factory.mock.calls.length).toBe(1);
25
+ assert(value === 1);
26
+ assert(getValue(parentCache) != null, 'Parent cache should not be empty');
24
27
 
25
- parentCache.empty();
26
- assert(getValue(parentCache) === null);
28
+ parentCache.empty();
29
+ assert(getValue(parentCache) === null);
27
30
 
28
- parentCache.getOrPopulateAndTemporaryRetain();
29
- expect(factory.mock.calls.length).toBe(2);
31
+ parentCache.getOrPopulateAndTemporaryRetain();
32
+ expect(factory.mock.calls.length).toBe(2);
30
33
 
31
- assert(getValue(parentCache) != null);
34
+ assert(getValue(parentCache) != null);
32
35
 
33
- clearTemporaryRetain();
36
+ clearTemporaryRetain();
34
37
 
35
- assert(getValue(parentCache) != null);
36
- });
38
+ assert(getValue(parentCache) != null);
39
+ },
40
+ );
37
41
 
38
- test("Clearing the only temporary retain removes the item from the parent cache", () => {
42
+ test('Clearing the only temporary retain removes the item from the parent cache', () => {
39
43
  const factory = vi.fn(() => {
40
44
  const pair: ItemCleanupPair<number> = [1, vi.fn()];
41
45
  return pair;
@@ -49,7 +53,7 @@ describe("ParentCache", () => {
49
53
  assert(getValue(parentCache) === null);
50
54
  });
51
55
 
52
- test("Clearing one of two temporary retains does not remove the item from the parent cache", () => {
56
+ test('Clearing one of two temporary retains does not remove the item from the parent cache', () => {
53
57
  const factory = vi.fn(() => {
54
58
  const pair: ItemCleanupPair<number> = [1, vi.fn()];
55
59
  return pair;
@@ -1,9 +1,9 @@
1
- import { CacheItem, createTemporarilyRetainedCacheItem } from "./CacheItem";
1
+ import { CacheItem, createTemporarilyRetainedCacheItem } from './CacheItem';
2
2
  import {
3
3
  CleanupFn,
4
4
  Factory,
5
5
  ItemCleanupPair,
6
- } from "@isograph/disposable-types";
6
+ } from '@isograph/disposable-types';
7
7
 
8
8
  // TODO convert cache impl to a getter and setter and free functions
9
9
  // TODO accept options that get passed to CacheItem
@@ -79,7 +79,7 @@ export class ParentCache<T> {
79
79
  // typescript thinks that cacheItem is any, because it's referenced in the closure.
80
80
  const [cacheItem, disposeTemporaryRetain] = pair;
81
81
  this.__cacheItem = cacheItem;
82
- return [cacheItem, this.__cacheItem.getValue(), disposeTemporaryRetain];
82
+ return [cacheItem, cacheItem.getValue(), disposeTemporaryRetain];
83
83
  }
84
84
 
85
85
  empty() {
package/src/index.ts CHANGED
@@ -1,9 +1,9 @@
1
- export * from "@isograph/disposable-types";
1
+ export * from '@isograph/disposable-types';
2
2
 
3
- export * from "./CacheItem";
4
- export * from "./ParentCache";
5
- export * from "./useCachedPrecommitValue";
6
- export * from "./useDisposableState";
7
- export * from "./useHasCommittedRef";
8
- export * from "./useLazyDisposableState";
9
- export * from "./useUpdatableDisposableState";
3
+ export * from './CacheItem';
4
+ export * from './ParentCache';
5
+ export * from './useCachedPrecommitValue';
6
+ export * from './useDisposableState';
7
+ export * from './useHasCommittedRef';
8
+ export * from './useLazyDisposableState';
9
+ export * from './useUpdatableDisposableState';