File size: 7,155 Bytes
bc20498
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# gc-hook

[![build status](https://github.com/WebReflection/gc-hook/actions/workflows/node.js.yml/badge.svg)](https://github.com/WebReflection/gc-hook/actions) [![Coverage Status](https://coveralls.io/repos/github/WebReflection/gc-hook/badge.svg?branch=main)](https://coveralls.io/github/WebReflection/gc-hook?branch=main)

<sup>**Social Media Photo by [Steve Johnson](https://unsplash.com/@steve_j) on [Unsplash](https://unsplash.com/)**</sup>

A simplified [FinalizationRegistry](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry) utility that works:

  * it does the right thing by never leaking the reference meant to be notified
  * it allows overriding the returned proxy with any other more complex wrapper or indirection
  * it allows references owners to *drop* from the registry explicitly, either via the *held* reference or an explicit token, if passed as extra option
  * it avoids understanding how the FinalizationRegistry works, helping you to focus on more complex issues instead of re-implementing the same dance over and over

### Example

```js
// available as commonjs too
import { create, drop } from 'gc-hook';

// keep a count of all passed references created here
let references = 0;

// notify how many references are still around
const onGarbageCollected = myUtility => {
  console.log(--references, 'references still used');
};

export default options => {
  const myUtility = { ...options, do: 'something' };
  console.log(++references, 'references provided');
  // return a proxy to avoid holding directly myUtility
  // while keeping the utility in memory until such proxy
  // is not needed, used, or referenced anymore
  return create(myUtility, onGarbageCollected);
};

// as module consumer
import createUtility from './module.js';

let util = createUtility({some: 'thing'});
// do something  amazing with the util ... then
setTimeout(() => {
  // clear the utility or don't reference it anymore anywhere
  util = null;
  // once the GC kicks in, the module.js will log how many
  // utilities are still around and never collected
});
```

## Use Cases

<details>
  <summary><strong>Internal Objects</strong></summary>
  <div markdown=1>

In case you'd like to be notified when an object not meant to leak has been collected,
you can use the `create` function in its most simple way:

```js
import { create } from 'gc-hook';

const privateObject = {};
const onGC = privateObject => {
  console.log(privateObject, 'not used anymore');
};

export create(privateObject, onGC);
```

  </div>
</details>

<details>
  <summary><strong>FFI Objects</strong></summary>
  <div markdown=1>

If you are handling *FFI* related references, you can hold on internal values and yet return whatever artifact you like in the wild.

```js
import { create } from 'gc-hook';

export const createWrap = reference => {

  const onGC = reference => {
    ffi.gc.decreaseRefCounting(reference);
  };

  const wrap = function (...args) {
    return ffi.apply(reference, args);
  };

  wrap.destroy = onGC;

  // will return the wrap as it is without holding
  // the reference in the wild
  return create(reference, onGC, { return: wrap });
};
```

This use case was designed after *pyodide* Proxy and GC dance around passed references to the *JS* world.

  </div>
</details>

<details>
  <summary><strong>Primitives</strong></summary>
  <div markdown=1>

In case you need to relate a specific object to a unique id (*[coincident](https://github.com/WebReflection/coincident)* use case) and you don't need to ever unregister the held reference / id internally:

```js
import { create } from 'gc-hook';

const onGC = id => {
  console.log(id.valueOf(), 'not needed anymore');
};

// id can be any primitive in here and ref must be used as return
export const relate = (id, ref) => {
  return create(
    typeof id === 'string' ? new String(id) : new Number(id),
    onGC,
    { token: false, return: ref }
  );
};
```

  </div>
</details>

<details>
  <summary><strong>Primitives + Drop</strong></summary>
  <div markdown=1>

In case you need to relate a specific object to a unique id but you still would like to drop the reference from the *FinalizationRegistry* later on:

```js
import { create, drop } from 'gc-hook';

const onGC = ({ id, time }) => {
  console.log(id, 'created at', time, 'not needed anymore');
};

// id can be any primitive in here
export const relate = (id, wrap) => {
  const token = { id, time: Date.now() };
  const hold = typeof id === 'string' ? new String(id) : new Number(id);
  return {
    value: create(hold, onGC, { token, return: wrap }),
    drop: () => drop(token)
  };
};
```

  </div>
</details>

<details>
  <summary><strong>Complex held values</strong></summary>
  <div markdown=1>

One does not need to pass to the *GC* callback just a specific kind of value so that it's possible to combine various operations at once:

```js
import { create, drop } from 'gc-hook';

export const createComplexHeld = ref => {
  const onGC = ({ ref, destroy, time }) => {
    destroy();
    console.log(ref, 'created at', time, 'not needed');
  };

  const wrap = function (...args) {
    return ffi.apply(ref, args);
  };

  wrap.destroy = () => {
    drop(held);
    ffi.gc.decreaseRefCounting(ref);
  };

  const held = {
    ref,
    destroy: wrap.destroy,
    time: Date.now(),
  };

  return create(held, onGC, { return: wrap });
}:
```

The only and most important thing is to never return something part of the `held` logic otherwise that returned value cannot possibly ever be Garbage Collected.

  </div>
</details>

## API

```js
// returns a ProxyHandler<hold> or whatever
// the `return` option wants to return.
// The returned reference is the one that
// notifies the GC handler once destroyed
// or not referenced anymore in the consumer code.
create(
  // the reference or primitive to keep in memory
  // until the returned value is used. It can be
  // a primitive, but it requires `token = false`,
  // or any reference to hold in memory.
  hold,
  // a callback that will receive the held value
  // whenever its Proxy or wrapper is not referenced
  // anymore in the program using it.
  onGarbageCollected,
  // optional properties:
  {
    // if passed along, it will be used automatically
    // to create the ProxyHandler<hold>.
    handler = Object.create(null),
    // override the otherwise automatically created Proxy
    // for the `held` reference.
    return = new Proxy(hold, handler),
    // allow dropping from the registry via something
    // different from the returned value itself.
    // If this is explicitly `false`, no token is used
    // to register the retained value.
    token = hold,
    // if explicitly set as `true` it will `console.debug`
    // the fact the held value is not retained anymore out there.
    debug = false,
  } = {}
);

// Returns `true` if the `token` successfully
// unregistered the proxy reference from the registry.
drop(
  // it's either the held value waiting to be passed
  // to the GC callback, or the explicit `token` passed
  // while creating the reference around it.
  token
);
```