A TypeScript library for Deno š¦ designed to provide a high-level API to manage low-level resources.
Recyclable allows you to free and/or close your resources automatically, preventing memory leaks and unnecessary memory usage. It uses V8ās Garbage Collection system to know when to clean up, and, if the object is never collected, then it will be freeād right before the program exits.
Advantages
- š Cross-Platform. Works on Windows, Linux and MacOS (Should work on those two, not tested yet).
- š Portable. Zero dependencies
- š Types. Includes types declarations
Table of Contents
Changelog
You can read the changelogs on Recyclableās Releases page
Quick Start
To use Recyclable, simply import it into your project like this:
import Recyclable from "https://deno.land/x/recyclable/mod.ts";Then, extend the Recyclable class and implement the create() and delete() methods.
class SomeClass extends Recyclable {
create() {
// Initialize and allocate the object here
}
delete() {
// Clean up and free the resources here
}
}IMPORTANT: Recyclable must be the first module you import in your proyect, as it relies on Monkey-patching the Deno.exit(), Deno.addSignalListener() and Deno.removeSignalListener() methods. Also, to handle SIGHUP on Windows, it needs to use SetConsoleCtrlHandler which may cause some stability issues if itās not called right away. Until Deno adds proper way of handling exit events, doing this is the only option.
For the flags, on Linux/MacOS you donāt need any. On Windows, you need --unstable and --allow-ffi. This is to handle the SIGHUP event which insāt accesible through Denoās APIs.
Documentation
Recyclable exports a single, default, export, which is an abstract class named Recyclable with the following implementation:
abstract class Recyclable<Parameters extends unknown[] = []> {
constructor(...params: Parameters);
abstract create(...params: Parameters): void;
abstract delete(): void;
}You must extend the Recyclable class to use it, and then, when an instance of your class is constructed, the underlying Recyclable ās constructor willā¦
- Create a āSymbolic Referenceā to the actual instance, which will be returned by the class
- Call
create()with the same arguments that were passed tosuper()and with the āSymbolic Referenceā asthisparameter - Define non-configurable wrappers for
delete()andcreate()in the instance to prevent calling them more than once
Since a āSymbolic Referenceā (Made via Proxying the real instance) is returned, itās possible to detect when the object is out of reach while mainting the original instance available so it can be used as this parameter for the delete() function, which will be called whenā¦
- The āSymbolic Referenceā instance is garbage collected
Deno.exit()gets called- A process-terminating signal (Like
SIGINTsent byCTRL+C) is received - No code to execute is left
And in the case that a delete() method throws while being called right before exiting, it will print the uncaught error to stderr but wonāt prevent the rest of delete() methods in queue from being called, ensuring that every object is freeād before exiting.
However, this also introduces some limitations:
create()anddelete()can only be called once. In the case ofcreate(), it is called when constructing the instance, so in practice, it canāt never be called. In the other hand,delete()is called automatically, unless you have called it before manually (However the purpose of this library is that you donāt have to⦠so why would you? š¤).A
Referenceinstance can not create any circular references of the instance during any moment at runtime (circulars of other objects inside of it are allowed, just not of the instance itself), otherwise, an error will be thrown by theProxytraps.create()can not use any asynchronous methods or be asynchronous itself, as that would make it return aPromisewhich is unsupported.delete()can not use any asynchronous methods or be asynchronous itself, as that would require the program to continue executing in circumstances where it shouldnāt like when callingDeno.exit().In TypeScript, the
declarekeyword must be used when defining class properties. If you do not, the properties will be set to their default value orundefinedafter thesuper()andcreate()calls return.In JavaScript, class properties that are initialized by
create()cannot be specified in the class body nor have default values for the same reason listed above.
Usage
Letās take a look at some examples to understand better how to use Recyclable. Our first example shows us how to use Deno.dlopen and Recyclable together.
Using Deno.dlopen with Recyclable:
import Recyclable from "https://deno.land/x/recyclable/mod.ts";
class User32 extends Recyclable {
// Don't forget to use the declare keyword
declare library: Deno.DynamicLibrary<{
MessageBoxA: {
parameters: [ 'i32', 'buffer', 'buffer', 'i32' ],
result: 'i32'
}
}>;
create() {
console.log('Opening the user32.dll library');
this.library = Deno.dlopen('user32', {
MessageBoxA: {
parameters: [ 'i32', 'buffer', 'buffer', 'i32' ],
result: 'i32'
}
})
}
delete() {
console.log('Closing the user32.dll library');
this.library.close();
}
}
let encoder = new TextEncoder();
let user32 = new User32();
function CString(str: string) {
return encoder.encode(`${str}\0`).buffer;
}
user32.library.symbols.MessageBoxA(0, CString('Hello world!'), CString('My Message Box'), 0);
// No need to manually call user32.library.close() Yey! š„³Output:
Opening the user32.dll library
Closing the user32.dll library(This example requires the --allow-ffi and --unstable flags).
In this example User32::delete() was called and closed the Deno.DynamicLibrary even thought we never explicitly called that function. That is thanks to the Recyclable class calling the method right before exiting.
Using Deno.FsFile with Recyclable:
import Recyclable from "https://deno.land/x/recyclable/mod.ts";
class WritableFile extends Recyclable<[filepath: string]> {
// Don't forget to use the declare keyword
declare filepath: string;
declare encoder: TextEncoder;
declare fsFile: Deno.FsFile;
create(filepath: string) {
console.log(`Opening a handle to ${filepath}`);
this.filepath = filepath;
this.encoder = new TextEncoder();
this.fsFile = Deno.openSync(filepath, { write: true, create: true });
}
delete() {
console.log(`Closing the handle to ${this.filepath}`);
this.fsFile.close();
}
write(contents: string) {
let buffer = this.encoder.encode(contents);
this.fsFile.writeSync(buffer);
return buffer.length;
}
}
let myFile = new WritableFile('./greeting.txt');
let written = myFile.write('Hello, World!');
console.log(`${written} bytes written to ${myFile.filepath}. Time to exit`);
Deno.exit(0);
// No need to manually call myFile.fsFile.close() Yey! š„³Output
Opening a handle to ./greeting.txt
13 bytes written to ./greeting.txt. Time to exit
Closing the handle to ./greeting.txt(This example requires the --allow-write flag, and, if running Windows, the --allow-ffi and --unstable flags).
This example is very interesting, as we can see that Deno.exit() is called without WritableFile::delete() being called before, yet it is called anyways right after Deno.exit() gets called without preventing the program from exiting.
Known Issues
Certain signals cannot be handled as the OS does not allows them to be handled, that means that, despite
Recyclabletrying itās best to make sure thatdelete()is called before exiting, it is not guaranteed under the circumstance that an unhandleable signal is sent. However, if the user is sending these signals, it should know that not a single program will be able to gracefully exit, so⦠š¤·āāļøRecycable canāt run cleanup tasks if your app is frozen (Like when itās stuck on an infinite loop). So make sure to not get the Event Loop blocked otherwise no JavaScript code is will be able to run at all.
Currently, it is not possible to detect when the console attached to the Deno process is destroyed/detached on Windows, such as when the user closes the command prompt. This limitation is due to the Deno runtimeās lack of support for adding listeners to theThis has been fixed! This known issue will be removed from the README.md on future releases. However, solving this made theSIGHUPsignal. Any attempts to useSetConsoleCtrlHandlerwould require using therefmethod on theUnsafeCallbackwhich prevents the process from exiting (Looking onto this, searching a way to callunrefonce theEvent Loopās Queue and Stack are empty). One possible workaround is to hide the Terminal window completely using the Windows API to prevent the user from closing the command prompt, but this approach has its own drawbacks and is beyond the scope of this library.--unstableand--allow-ffiflags mandatory on Windows.
Suggestions and Bugs š
Aaaand just like that youāve reached the end! But remember, despite there being only 2 examples, there are many more uses you can give to this library! If you have any suggestions, or want to report a bug, please do so by creating an issue at Recyclableās Repository.
License
Recyclable is licensed under the MIT License. By using it, you accept the conditions and limitations specified in the LICENSE file.
Copyright Ā© 2023 @jabonkun