Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
This page is a work in progress and may not have the polish of a usual Evie-Written document!
Some quick docs:
Possible Operators (accepts all variations listed below, as strings):
+
, add
, addition
: Increments the value in the enmap by the provided value.
-
, sub
, subtract
: Decrements the value in the enmap by the provided value.
*
, mult
, multiply
: Multiply the value in the enmap by the provided value.
/
, div
, divide
: Divide the value in the enmap by the provided value.
%
, mod
, modulo
: Gets the modulo of the value in the enmap by the provided value.
^
, exp
, exponential
: Raises the value in the enmap by the power of the provided value.
As described in the home page, one disadvantage of Enmap is that it loads all your data in memory, so you're sacrificing RAM in order to gain speed. In larger projects, this might become a concern fairly quickly - or when using larger data sets that take more memory.
For this purpose, there are features in Enmap that enable less caching, by sacrificing some speed and ease of use. That is to say, with data not being fully loaded, there are some things that can't be done easily - see below for details.
The options are as follow:
fetchAll
: Defaults to true
, which means fetching all keys on load. Setting it to false
means that no keys are fetched, so it loads faster and uses less memory.
autoFetch
: Defaults to true
. When enabled, will automatically fetch any key that's requested using get, getProp, etc. This is a "syncroneous" operation, which means it doesn't need any of this promise or callback use.
If fetchAll is set to false, no data (by default) will be loaded from the database - the Enmap will be completely empty. This means that doing a .size
check returns 0, looping and filtering doesn't return anything, and get() requests all return null.
Ok but... how's that useful? It's useful because if you don't need the data, it's not loaded. To load data, there are 2 different methods available.
enmap.fetchEverything()
will, of course, fetch all the data from the database - this is the method that's called on init() if fetchAll is true. This means you can stagger your loading time, or that you can decide, later, under certain conditions, you do want to load everything.
enmap.fetch(key)
can fetch a single key from the database, saves it to enmap, and returns a promise with the fetched value.
enmap.fetch([array, of, keys])
will fetch each key in the requested array, and return an array of [key, value]
pairs for each fetched value.
enmap.count
will give you the number of keys in the database itself, including uncached ones (.size is only cached values).
enmap.indexes
will give you a list of keys in the database, including uncached ones.
Yup. Those are the only things you really need to know for the current version of Enmap's fetchAll feature.
I'm working on the following features in future versions of enmap, related to fetch methods:
Add the ability to check if the DB has a key without fetching (a sort of "uncached has()")
Add an auto-uncache feature so that "stale" keys are cleaned. This would combine well with autoFetch in that it would ultimately keep memory usage low.
While Enmap is a Map enhanced with Array methods, Enmap also offers some enhanced array methods for the data stored inside of it. Talk about ArrayCeption!
So what do I mean by methods for your stored data? I mean that you can store arrays inside Enmap, and directly push, pull, add and remove from those arrays. There are methods to work both on direct arrays, as well as arrays stored inside of an object.
Let's take a look at two example entries in enmap that we can use. The first is a direct array, the second is an array inside an object.
There are two methods to push to an array, one for simple arrays and one for arrays inside objects. Pushing in an enmap array is the same as a regular array push: it adds the element to the end of the array.
The second parameter in pushIn is the "path" to the array in an object. It works the same as the properties path used in Working With Objects.
Similarly, you can remove from an array. Note, however, that this will only work if you're removing a string or number. Removing an object from an array is not supported.
When using persistent enmaps, it's very important to understand that it takes time to load the data from the database. Attempting to use the enmap before it's fully loaded can lead to errors, so we need to make sure it's ready before using it.
To make sure that all your data is loaded before you start working, Enmap provides a handy property called defer
, which is a promise that is resolved once the provider is ready and all the data has been loaded into memory. There are a few ways to use defer
, since it's a promise.
For more information on async/await and promises, see My JavaScript Guide.
Enmap also provides a isReady
option that tells you if the database is loaded. You can use that however you want, though the preferred method is using defer
.
This page will describe how to use Enmap from multiple files within your same project. Note that I mean the same app, process, or shard, but different files within this one running process.
When Enmap is used with its default options, it loads everything in its cache and generally provides your data from this cache, not directly from the database. In the case where you want to use the data from one Enmap from multiple locations, you might encounter the following issue:
Hi! When I update data in Enmap from one file, it doesn't update in the other file, I have to restart the bot to update. Is this a bug?
However, this also means that when you do new Enmap({ name: "something" })
from more than one file, that's also a different instance, that doesn't share the same memory space. So not only will it not update the data in memory for the other file, it also uses double the memory. And of course, that's bad. So how do we fix this?
Admitedly, the vast majority of you Enmap users are doing Discord.js Bots, and even though Enmap works fine with any nodejs project that need simple data storage, bots are my main clients. Considering this fact, we have an extremely simple way to share an Enmap between multiple files: We attach it to the bot client. Usually your client is defined in your main file (index.js, app.js, bot.js, whatever you named it), and every part of your bot has access to this client. We can attach Enmap directly to it, like so:
This will work even if you're using a command handler, framework, or whatever - as long as you have access to a client variable, you have access to your enmaps.
In other frameworks and libraries, you might have something similar. For example with Express or Koa for http servers, you can sometimes attach the enmap to your request from the very top, in a middleware. If that's not possible, or if you find that to be complicated, you can use the next method.
This means you can simply require that file elsewhere. Let's say we called that file db.js
, here's how you'd use it:
And as I mentioned, as a bonus you now have the ability to create functions which you can export and use, to simplify your code and remove duplication. So, let's say I need to get all the tags for a specific guild, and my tags are built using an object as shown above. To get all those tags for a guild, you'd need filters, right? Like so:
now let's say you use this code a lot in your app, and you'd like to not have to type this whole thing every time. You could add a simple function in your module that only takes an ID and returns the tags:
To answer my own obvious question: it's not a bug, it's a feature that I cannot implement. The way Enmap's cache works is that the data is loaded in memory in that of Enmap, and only for that instance. This is what enables you to have many different Enmaps in your project - one Enmap doesn't share data with another.
Important Note: Do NOT override Discord.js' existing collections! That means, client.users, client.guilds, etc. - none of these should be overriden.
All things considered, are probably the recommended way to use your Enmap in multiple files within your project. Not only does it give you a single file to import, lets you define multiple Enmaps you can individually import, it also gives you the ability to add specific functions to do common actions you use throughout your project.
As covered in , modules are fairly straightforward. This is how I have done an Enmap shared module before:
And there you have it! There are other ways to build the exports, you can also split it differently, take a look at for more information.
Now that we have a functional Enmap structure (which we'll always refer to as myEnmap
), we're ready to start writing data to it, and getting data from it.
The code samples on this page assume that you have correctly initialized
myEnmap
, and awaited its initialization if it's persistent.
In terms of Enmap, "writing", "adding" and "editing" data is essentially the same thing. When using the basic set()
method, if the key does not exist it's created, and if it does, it's modified.
Enmap supports most native JavaScript data types, with a few small exceptions.
null
and undefined
values are not supported.
Complex objects like Set()
, Map()
, etc, are not supported.
Class instances and Functions are not supported.
As a general rule, except for null and undefined, anything that can be handled by JSON.stringify()
will work as expected in Enmap. This includes:
String
Number
Integer
Boolean
Object
Array
Objects and Arrays are a little more complex to deal with, so they have their own page. See Working with Objects for more information.
The usage for the set()
method is simple:
key
must be a string or integer. A key should be unique, otherwise it will be overwritten by new values using the same key.
value
must be a supported native data type as mentioned above.
Here are a few examples of writing simple data values:
Getting data back from an Enmap is just as simple as writing to it. All you need is the key
of what you want to retrieve, and you get its value back!
That's pretty much it for only retrieiving a single data value. There are more complex operations that are available, take a look at Array Methods for the more advanced things you can do on Enmap's data!
Enmap is a great way to store structured data, and offers a few helper features that directly affect both objects and arrays.
Let's assume for a moment that we want to store the following data structure in Enmap:
This structure has 5 "properties": first
, second
, changeme
, isCool
, sub
. The sub
property has 2 properties of its own, yay
and thing
.
To store this structure in Enmap, you can use a variable, or just straight-up write the object:
Note: All further methods require the value to be an object. If you attempt to get, set, modify or remove using the below methods and your value isn't an object, Enmap will throw an error.
Retrieving a specific property from an object is done through the get()
method, by specifying both the key and the "path" to the property you want.
The exact method is <Enmap>.get(key, path)
.
You can also check if a specific property exists or not. This is done through the has
method, with a key, and path to the property:
There are a few various ways to modify properties of both Objects and Arrays. The very basic way to set a property on an object or array is through .set(key, value, path)
like the following examples:
As you can see, setProp() and getProp() work on the same concept that the path can be as complex as you want.
Arrays have additional helper methods, you can see them here.
Mostly, this documentation will be concentrating on the "persistent" version of enmap - the one where data is saved automatically.
If you don't want persistence, the only difference is how you initialize the enmap:
Using persistent enmaps require the additional install of the better-sqlite-pool
module.
If using a persistent enmap, you need to add options:
The following is a list of all options that are available in Enmap, when initializing it:
name
: A name for the enmap. Defines the table name in SQLite (the name is "cleansed" before use).
If an enmap has a name, it is considered persistent and will require better-sqlite-pool
to run.
If an enmap does not have a name, it is not persistent and any option related to database interaction is ignored (fetchAll, autoFetch, polling and pollingInterval).
fetchAll
: Defaults to true
, which means fetching all keys on load. Setting it to false
means that no keys are fetched, so it loads faster and uses less memory.
autoFetch
: Defaults to true
. When enabled, will automatically fetch any key that's requested using get, getProp, etc. This is a "syncroneous" operation, which means it doesn't need any of this promise or callback use.
dataDir
: Defaults to ./data
. Determines where the sqlite files will be stored. Can be relative (to your project root) or absolute on the disk. Windows users , remember to escape your backslashes!
cloneLevel
: Defaults to deep
. Determines how objects and arrays are treated when inserting and retrieving from the database.
none
: Data is inserted by reference, meaning if you change it in the Enmap it changes outside, and vice versa. This should only be used in non-persistent enmaps if you know what you're doing!.
shallow
: Any object or array will be inserted as a shallow copy, meaning the first level is copied but sub-elements are inserted as references. This emulates Enmap 3's behaviour, but is not recommended unless you know what you're doing.
deep
: Any object or array will be inserted and retrieved as a deep copy, meaning it is a completely different object. Since there is no chance of ever creating side-effects from modifying object, This is the recommended, and default, setting.
NEW IN ENMAP 4.2.0 AND HIGHER
polling
: defaults to false
. Determines whether Enmap will attempt to retrieve changes from the database on a regular interval. This means that if another Enmap in another process modifies a value, this change will be reflected in ALL enmaps using the polling feature.
pollingInterval
: defaults to 1000
, polling every second. Delay in milliseconds to poll new data from the database. The shorter the interval, the more CPU is used, so it's best not to lower this. Polling takes about 350-500ms if no data is found, and time will grow with more changes fetched. In my tests, 15 rows took a little more than 1 second, every second.