├── .eslintrc.json ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── docs ├── README.md ├── SUMMARY.md ├── api.md ├── assets │ ├── enmap-logo.svg │ ├── favicon.ico │ └── favicon.png ├── blog-posts │ ├── README.md │ ├── enmaps-history.md │ ├── josh.md │ ├── why-remove-cache.md │ └── why-sqlite-only.md ├── help │ └── CJStoESM.md ├── install │ ├── README.md │ ├── troubleshooting-guide.md │ ├── upgrade.md │ └── upgradev6.md ├── retype.manifest ├── retype.yml ├── sponsors.md └── usage │ ├── README.md │ ├── arrays.md │ ├── basic.md │ ├── math.md │ ├── objects.md │ ├── paths.md │ ├── serialize.md │ ├── using-enmap.multi.md │ └── using-from-multiple-files.md ├── package-lock.json ├── package.json ├── scripts └── build-docs.js ├── src ├── error.js └── index.js ├── test ├── error.spec.js └── index.spec.js ├── typings └── index.d.ts └── vitest.config.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:node/recommended", 5 | "prettier" 6 | ], 7 | "env": { 8 | "node": true, 9 | "es6": true 10 | }, 11 | "parserOptions": { 12 | "ecmaVersion": 2023, 13 | "sourceType": "module" 14 | }, 15 | "rules": { 16 | "no-unused-vars": 1, 17 | "valid-jsdoc": ["warn", { 18 | "requireReturn": false, 19 | "requireReturnDescription": false, 20 | "preferType": { 21 | "String": "string", 22 | "Number": "number", 23 | "Boolean": "boolean", 24 | "Symbol": "symbol", 25 | "function": "Function", 26 | "object": "Object", 27 | "date": "Date", 28 | "error": "Error" 29 | } 30 | }], 31 | "semi": ["error", "always"], 32 | "indent": ["error", 2, {"SwitchCase": 1}], 33 | "comma-dangle": ["error", "always-multiline"], 34 | "keyword-spacing": ["error", { 35 | "overrides": { 36 | "if": { "after": true }, 37 | "for": { "after": true }, 38 | "while": { "after": true }, 39 | "catch": { "after": true }, 40 | "switch": { "after": true } 41 | } 42 | }], 43 | "quotes": ["error", "single", { "avoidEscape": true, "allowTemplateLiterals": true }], 44 | "template-curly-spacing": "error" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | ci: 11 | name: Tests 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout Project 15 | uses: actions/checkout@v4 16 | - name: Use Node.js v20 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: '20.x' 20 | cache: 'npm' 21 | - name: Install Dependencies 22 | run: npm install 23 | - name: Run Tests 24 | run: npm run test -- --coverage 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .hgignore 2 | node_modules 3 | .c9 4 | .DS_Store 5 | Thumbs.db 6 | data/ 7 | config.json 8 | .vscode 9 | data/ 10 | dist/ 11 | out/ 12 | .pnp.* 13 | .yarn/* 14 | !.yarn/patches 15 | !.yarn/plugins 16 | !.yarn/releases 17 | !.yarn/sdks 18 | !.yarn/versions 19 | .retype/ 20 | coverage/ 21 | tmp/ 22 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | yarn.lock 4 | docs -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "semi": true, 4 | "singleQuote": true, 5 | "trailingComma": "all", 6 | "endOfLine": "lf" 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | APPENDIX: Code from other projects 190 | This project contains partial code from Discord.js version 10. 191 | Project Link: https://github.com/discordjs/discord.js 192 | Author: Amish Shaw (hydrabolt) 193 | License: https://github.com/discordjs/discord.js/blob/master/LICENSE 194 | 195 | 196 | Copyright 2018 Évelyne Lachance 197 | 198 | Licensed under the Apache License, Version 2.0 (the "License"); 199 | you may not use this file except in compliance with the License. 200 | You may obtain a copy of the License at 201 | 202 | http://www.apache.org/licenses/LICENSE-2.0 203 | 204 | Unless required by applicable law or agreed to in writing, software 205 | distributed under the License is distributed on an "AS IS" BASIS, 206 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 207 | See the License for the specific language governing permissions and 208 | limitations under the License. 209 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Enmap - Enhanced Maps 2 | 3 |
4 |

5 | Discord server 6 | NPM version 7 | NPM downloads 8 | Patreon 9 |

10 |
11 | 12 |
13 |

Enmap Logo 14 |

15 | 16 | Enhanced Maps are a data structure that can be used to store data in memory that can also be saved in a database behind the scenes. 17 | These operations are fast, safe, and painless. 18 | 19 | The data is synchronized to the database automatically, seamlessly, and asynchronously for maximum effectiveness. 20 | The storage system used is an `sqlite` database which is fast, performant, can be easily backed up, 21 | and supports multiple simultaneous connections. 22 | 23 | ## Documentation 24 | 25 | * [Installation](https://enmap.alterion.dev/install) 26 | * [Basic Setup](https://enmap.alterion.dev/usage) 27 | * [API Reference](https://enmap.alterion.dev/api) 28 | * [Examples](https://enmap.alterion.dev/complete-examples) 29 | 30 | ## Support 31 | 32 | Support is offered on my official [Alterion.dev Discord](https://discord.gg/N7ZKH3P). 33 | 34 | ## FAQs 35 | 36 | ### Q: So what's Enmap? 37 | 38 | **A**: Enmaps are the Javascript Map() data structure with additional utility methods. This started 39 | as a pretty straight clone of the [Discord.js Collections](https://discord.js.org/#/docs/collection/main/class/Collection) 40 | but since its creation has grown far beyond those methods alone. 41 | 42 | ### Q: What is "Persistent"? 43 | 44 | **A**: By using a database layer with `better-sqlite3`, any data added to the Enmap 45 | is stored not only in temporary memory but also backed up in a local database. This means that 46 | when you restart your project, your data is not lost and is loaded on startup. 47 | 48 | ### Q: How big can the Enmap be? 49 | 50 | **A**: The size of the memory used is directly proportional to the size of all the keys loaded in memory. 51 | The more data you have, the more complex it is, the more memory it may use. You can use the 52 | [fetchAll](https://enmap.alterion.dev/usage/fetchall) option to reduce memory usage. 53 | 54 | ### Q: Who did you make this for? 55 | 56 | **A**: Enmap was made specifically for beginners in mind. It's for you, the budding javascript developer that wants to save data 57 | in a database but doesn't want to learn SQL - yet. It's also for people that want to rapidly prototype some app that depends on 58 | a database but doesn't want to have to deal with queries, even if it's not the most efficient way to do things. 59 | 60 | ### Q: What can it be used for? 61 | 62 | **A**: Enmap is useful for storing very simple key/value data for easy retrieval, and also for more complex objects with many properties. 63 | Mainly, because of who I originally made this for, it's used in Discord.js Bots to save currencies, content blocks, server settings, and 64 | user information for bans, blacklists, timers, warning systems, etc. 65 | 66 | ## Testimonials 67 | 68 | Some user comments! 69 | 70 | > I have legit tried several databases, from popular complicated ones to pretty basic ones. The only database I had absolutely no issue with was and still is enmap. 71 | 72 | > I know how to use a real db, but enmap is so sweet and easy to use 73 | 74 | > Thanks to Enmap, I am able to do tons of things that I never thought I would accomplish. 75 | From custom settings to even just saving the little things, it is amazing to use. 76 | 77 | > Enmap helped me, and it stills helps me, because it is very simple and useful. Thank you for creating Enmap. 78 | 79 | > Without your tutorials I didn't have an internship and some work.. :)) 80 | 81 | > Enmap was introduced to me fairly early, and has been essential to the growth and development of my bot. Without it, I'd have to use and learn complicated and unsafe systems. Enmap has helped me do exactly what I want with my bot. Thank you. 82 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Enmap, the super simple database wrapper with over a million downloads to 4 | date. Wrapping around better-sqlite3 with its warm embrace, it's the easiest 5 | way to save data in node for your first project! 6 | --- 7 | 8 | # What is Enmap? 9 | 10 | ![](assets/enmap-logo.svg) 11 | 12 | Enmap stands for "Enhanced Map", and is a data structure based on the native JavaScript Map() structure with additional helper methods from the native Array() structure. Enmap also offers _persistence_, which means it will automatically save everything to save to it in a database, in the background, without any additional code or delays. 13 | 14 | {% hint style="danger" %} 15 | Enmap requires filesystem access. It **DOES NOT WORK** on Heroku, or other such systems that do not allow you to save data directly to disk. 16 | 17 | It should also not be used on **Repl.it** where the data cannot be hidden (and will be public) or on **Glitch* *which has been known to break Enmap's data persistence and lose data. 18 | {% endhint %} 19 | 20 | ## Why Enmap? 21 | 22 | While there are other better-known systems that offer some features of Enmap, especially caching in memory, Enmap is targeted specifically to newer users of JavaScript that might not want to deal with complicated systems or database queries. 23 | 24 | ## Advantage/Disadvantage 25 | 26 | Here are some advantages of using Enmap: 27 | 28 | * **Simple to Install**: Enmap itself only requires a simple `npm install` command to install and use, and a single line to initialize. [See Installation for details](install/). 29 | * **Simple to Use**: Basic Enmap usage can be completely done with 1-2 lines of initialization, and 3 commands, set(), get() and delete(). 30 | * **Very Fast**: Since Enmap resides in memory, accessing its data is blazing fast (as fast as Map() is). Even with persistence, Enmap still accesses data from memory so you get it almost instantly. 31 | 32 | Some disadvantages, compared to using a database connection directly: 33 | 34 | * **More memory use**: Since Enmap resides in memory and (by default) all its data is loaded when it starts, your entire data resides in RAM. When using a large amount of data on a low-end computer or VPS, this might be an issue for some users. 35 | * **Limited power**: You can have multiple Enmap "tables" loaded in your app, but they do not and cannot have relationships between them. Basically, one Enmap value can't refer to another value in another Enmap. This is something databases can be very good at, but due to the simplistic nature of Enmap, it's not possible here. 36 | * **Lack of scalability**: Enmap is great for small apps that require a simple key/value storage. However, a scalable app spread over multiple processes, shards, or clusters, will be severely limited by Enmap as it cannot update itself from the database on change - one process would not be aware of another process' changes. 37 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [What is Enmap?](README.md) 4 | * [Enmap Installation](install/README.md) 5 | * [Troubleshooting Guide](install/troubleshooting-guide.md) 6 | * [Migrating data from Enmap 3](install/upgrade.md) 7 | * [Upgrading to Enmap 6](install/upgradev6.md) 8 | * [Usage Documentation](usage/README.md) 9 | * [Basic Data Use](usage/basic.md) 10 | * [Understanding Paths](usage/paths.md) 11 | * [Working with Objects](usage/objects.md) 12 | * [Array Methods](usage/arrays.md) 13 | * [Mathematical Methods](usage/math.md) 14 | * [Using from multiple files](usage/using-from-multiple-files.md) 15 | * [Serializing and Deserializing](usage/serialize.md) 16 | * [Full Documentation](api.md) 17 | * [Blog Posts](blog-posts/README.md) 18 | * [Enmap's History](blog-posts/enmaps-history.md) 19 | * [V4: Why SQLITE only?](blog-posts/why-sqlite-only.md) 20 | * [V6: Why remove cache?](blog-posts/why-remove-cache.md) 21 | * [Enmap and Josh](blog-posts/josh.md) 22 | 23 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | The complete and unadultered API documentation for every single method and 4 | property accessible in Enmap. 5 | --- 6 | 7 | # Full Documentation 8 | 9 | The following is the complete list of methods available in Enmap. As it is auto-generated from the source code and its comments, it's a little more "raw" than the Usage docs. However, it has the benefit of being more complete and usually more up to date than the manually written docs. 10 | 11 | {% hint style="warning" %} 12 | If you're doing a PR on the docs github, please do not manually edit the below contents, as it will be overwritten. Check the src/index.js source code and change the comments there instead! 13 | {% endhint %} 14 | 15 | 16 | 17 | ### new Enmap([options]) 18 | Initializes a new Enmap, with options. 19 | 20 | 21 | | Param | Type | Description | 22 | | --- | --- | --- | 23 | | [options] | Object | Options for the enmap. See https://enmap.alterion.dev/usage#enmap-options for details. | 24 | | [options.name] | string | The name of the enmap. Represents its table name in sqlite. Unless inMemory is set to true, the enmap will be persisted to disk. | 25 | | [options.dataDir] | string | 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! *Note*: Enmap will not automatically create the folder if it is set manually, so make sure it exists before starting your code! | 26 | | [options.ensureProps] | boolean | defaults to `true`. If enabled and the value in the enmap is an object, using ensure() will also ensure that every property present in the default object will be added to the value, if it's absent. See ensure API reference for more information. | 27 | | [options.autoEnsure] | \* | default is disabled. When provided a value, essentially runs ensure(key, autoEnsure) automatically so you don't have to. This is especially useful on get(), but will also apply on set(), and any array and object methods that interact with the database. | 28 | | [options.serializer] | function | Optional. If a function is provided, it will execute on the data when it is written to the database. This is generally used to convert the value into a format that can be saved in the database, such as converting a complete class instance to just its ID. This function may return the value to be saved, or a promise that resolves to that value (in other words, can be an async function). | 29 | | [options.deserializer] | function | Optional. If a function is provided, it will execute on the data when it is read from the database. This is generally used to convert the value from a stored ID into a more complex object. This function may return a value, or a promise that resolves to that value (in other words, can be an async function). | 30 | | [options.inMemory] | boolean | Optional. If set to true, the enmap will be in-memory only, and will not write to disk. Useful for temporary stores. | 31 | | [options.sqliteOptions] | Object | Optional. An object of options to pass to the better-sqlite3 Database constructor. | 32 | 33 | **Example** 34 | ```js 35 | const Enmap = require("enmap"); 36 | // Named, Persistent enmap 37 | const myEnmap = new Enmap({ name: "testing" }); 38 | 39 | // Memory-only enmap 40 | const memoryEnmap = new Enmap({ inMemory: true }); 41 | 42 | // Enmap that automatically assigns a default object when getting or setting anything. 43 | const autoEnmap = new Enmap({name: "settings", autoEnsure: { setting1: false, message: "default message"}}) 44 | ``` 45 | 46 | 47 | ### enmap.size ⇒ number 48 | Get the number of key/value pairs saved in the enmap. 49 | 50 | **Kind**: instance property of Enmap 51 | **Returns**: number - The number of elements in the enmap. 52 | **Read only**: true 53 | 54 | 55 | ### enmap.db ⇒ Database 56 | Get the better-sqlite3 database object. Useful if you want to directly query or interact with the 57 | underlying SQLite database. Use at your own risk, as errors here might cause loss of data or corruption! 58 | 59 | **Kind**: instance property of Enmap 60 | 61 | 62 | ### enmap.autonum ⇒ number 63 | Generates an automatic numerical key for inserting a new value. 64 | This is a "weak" method, it ensures the value isn't duplicated, but does not 65 | guarantee it's sequential (if a value is deleted, another can take its place). 66 | Useful for logging, actions, items, etc - anything that doesn't already have a unique ID. 67 | 68 | **Kind**: instance property of Enmap 69 | **Returns**: number - The generated key number. 70 | **Read only**: true 71 | **Example** 72 | ```js 73 | enmap.set(enmap.autonum, "This is a new value"); 74 | ``` 75 | 76 | 77 | ### enmap.set(key, value, path) 78 | Sets a value in Enmap. If the key already has a value, overwrites the data (or the value in a path, if provided). 79 | 80 | **Kind**: instance method of Enmap 81 | 82 | | Param | Type | Description | 83 | | --- | --- | --- | 84 | | key | string | Required. The location in which the data should be saved. | 85 | | value | \* | Required. The value to write. Values must be serializable, which is done through (better-serialize)[https://github.com/RealShadowNova/better-serialize] If the value is not directly serializable, please use a custom serializer/deserializer. | 86 | | path | string | Optional. The path to the property to modify inside the value object or array. Should be a path with dot notation, such as "prop1.subprop2.subprop3" | 87 | 88 | **Example** 89 | ```js 90 | // Direct Value Examples 91 | enmap.set('simplevalue', 'this is a string'); 92 | enmap.set('isEnmapGreat', true); 93 | enmap.set('TheAnswer', 42); 94 | enmap.set('IhazObjects', { color: 'black', action: 'paint', desire: true }); 95 | enmap.set('ArraysToo', [1, "two", "tree", "foor"]) 96 | 97 | // Settings Properties 98 | enmap.set('IhazObjects', 'blue', 'color'); //modified previous object 99 | enmap.set('ArraysToo', 'three', 2); // changes "tree" to "three" in array. 100 | ``` 101 | 102 | 103 | ### enmap.update(key, valueOrFunction) ⇒ \* 104 | Update an existing object value in Enmap by merging new keys. **This only works on objects**, any other value will throw an error. 105 | Heavily inspired by setState from React's class components. 106 | This is very useful if you have many different values to update and don't want to have more than one .set(key, value, prop) lines. 107 | 108 | **Kind**: instance method of Enmap 109 | **Returns**: \* - The modified (merged) value. 110 | 111 | | Param | Type | Description | 112 | | --- | --- | --- | 113 | | key | string | The key of the object to update. | 114 | | valueOrFunction | \* | Either an object to merge with the existing value, or a function that provides the existing object and expects a new object as a return value. In the case of a straight value, the merge is recursive and will add any missing level. If using a function, it is your responsibility to merge the objects together correctly. | 115 | 116 | **Example** 117 | ```js 118 | // Define an object we're going to update 119 | enmap.set("obj", { a: 1, b: 2, c: 3 }); 120 | 121 | // Direct merge 122 | enmap.update("obj", { d: 4, e: 5 }); 123 | // obj is now { a: 1, b: 2, c: 3, d: 4, e: 5 } 124 | 125 | // Functional update 126 | enmap.update("obj", (previous) => ({ 127 | ...obj, 128 | f: 6, 129 | g: 7 130 | })); 131 | // this example takes heavy advantage of the spread operators. 132 | // More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax 133 | ``` 134 | 135 | 136 | ### enmap.get(key, path) ⇒ \* 137 | Retrieves a value from the enmap, using its key. 138 | 139 | **Kind**: instance method of Enmap 140 | **Returns**: \* - The parsed value for this key. 141 | 142 | | Param | Type | Description | 143 | | --- | --- | --- | 144 | | key | string | The key to retrieve from the enmap. | 145 | | path | string | Optional. The property to retrieve from the object or array. Should be a path with dot notation, such as "prop1.subprop2.subprop3" | 146 | 147 | **Example** 148 | ```js 149 | const myKeyValue = enmap.get("myKey"); 150 | console.log(myKeyValue); 151 | 152 | const someSubValue = enmap.get("anObjectKey", "someprop.someOtherSubProp"); 153 | ``` 154 | 155 | 156 | ### enmap.observe(key, path) ⇒ \* 157 | Returns an observable object. Modifying this object or any of its properties/indexes/children 158 | will automatically save those changes into enmap. This only works on 159 | objects and arrays, not "basic" values like strings or integers. 160 | 161 | **Kind**: instance method of Enmap 162 | **Returns**: \* - The value for this key. 163 | 164 | | Param | Type | Description | 165 | | --- | --- | --- | 166 | | key | \* | The key to retrieve from the enmap. | 167 | | path | string | Optional. The property to retrieve from the object or array. | 168 | 169 | 170 | 171 | ### enmap.keys() ⇒ Array.<string> 172 | Get all the keys of the enmap as an array. 173 | 174 | **Kind**: instance method of Enmap 175 | **Returns**: Array.<string> - An array of all the keys in the enmap. 176 | 177 | 178 | ### enmap.values() ⇒ Array.<\*> 179 | Get all the values of the enmap as an array. 180 | 181 | **Kind**: instance method of Enmap 182 | **Returns**: Array.<\*> - An array of all the values in the enmap. 183 | 184 | 185 | ### enmap.entries() ⇒ Array.<Array.<\*, \*>> 186 | Get all entries of the enmap as an array, with each item containing the key and value. 187 | 188 | **Kind**: instance method of Enmap 189 | **Returns**: Array.<Array.<\*, \*>> - An array of arrays, with each sub-array containing two items, the key and the value. 190 | 191 | 192 | ### enmap.push(key, value, path, allowDupes) 193 | Push to an array value in Enmap. 194 | 195 | **Kind**: instance method of Enmap 196 | 197 | | Param | Type | Default | Description | 198 | | --- | --- | --- | --- | 199 | | key | string | | Required. The key of the array element to push to in Enmap. | 200 | | value | \* | | Required. The value to push to the array. | 201 | | path | string | | Optional. The path to the property to modify inside the value object or array. Should be a path with dot notation, such as "prop1.subprop2.subprop3" | 202 | | allowDupes | boolean | false | Optional. Allow duplicate values in the array (default: false). | 203 | 204 | **Example** 205 | ```js 206 | // Assuming 207 | enmap.set("simpleArray", [1, 2, 3, 4]); 208 | enmap.set("arrayInObject", {sub: [1, 2, 3, 4]}); 209 | 210 | enmap.push("simpleArray", 5); // adds 5 at the end of the array 211 | enmap.push("arrayInObject", "five", "sub"); // adds "five" at the end of the sub array 212 | ``` 213 | 214 | 215 | ### enmap.math(key, operation, operand, path) ⇒ number 216 | Executes a mathematical operation on a value and saves it in the enmap. 217 | 218 | **Kind**: instance method of Enmap 219 | **Returns**: number - The updated value after the operation 220 | 221 | | Param | Type | Description | 222 | | --- | --- | --- | 223 | | key | string | The enmap key on which to execute the math operation. | 224 | | operation | string | Which mathematical operation to execute. Supports most math ops: =, -, *, /, %, ^, and english spelling of those operations. | 225 | | operand | number | The right operand of the operation. | 226 | | path | string | Optional. The property path to execute the operation on, if the value is an object or array. | 227 | 228 | **Example** 229 | ```js 230 | // Assuming 231 | points.set("number", 42); 232 | points.set("numberInObject", {sub: { anInt: 5 }}); 233 | 234 | points.math("number", "/", 2); // 21 235 | points.math("number", "add", 5); // 26 236 | points.math("number", "modulo", 3); // 2 237 | points.math("numberInObject", "+", 10, "sub.anInt"); 238 | ``` 239 | 240 | 241 | ### enmap.inc(key, path) ⇒ number 242 | Increments a key's value or property by 1. Value must be a number, or a path to a number. 243 | 244 | **Kind**: instance method of Enmap 245 | **Returns**: number - The udpated value after incrementing. 246 | 247 | | Param | Type | Description | 248 | | --- | --- | --- | 249 | | key | string | The enmap key where the value to increment is stored. | 250 | | path | string | Optional. The property path to increment, if the value is an object or array. | 251 | 252 | **Example** 253 | ```js 254 | // Assuming 255 | points.set("number", 42); 256 | points.set("numberInObject", {sub: { anInt: 5 }}); 257 | 258 | points.inc("number"); // 43 259 | points.inc("numberInObject", "sub.anInt"); // {sub: { anInt: 6 }} 260 | ``` 261 | 262 | 263 | ### enmap.dec(key, path) ⇒ Enmap 264 | Decrements a key's value or property by 1. Value must be a number, or a path to a number. 265 | 266 | **Kind**: instance method of Enmap 267 | **Returns**: Enmap - The enmap. 268 | 269 | | Param | Type | Description | 270 | | --- | --- | --- | 271 | | key | string | The enmap key where the value to decrement is stored. | 272 | | path | string | Optional. The property path to decrement, if the value is an object or array. | 273 | 274 | **Example** 275 | ```js 276 | // Assuming 277 | points.set("number", 42); 278 | points.set("numberInObject", {sub: { anInt: 5 }}); 279 | 280 | points.dec("number"); // 41 281 | points.dec("numberInObject", "sub.anInt"); // {sub: { anInt: 4 }} 282 | ``` 283 | 284 | 285 | ### enmap.ensure(key, defaultValue, path) ⇒ \* 286 | Returns the key's value, or the default given, ensuring that the data is there. 287 | This is a shortcut to "if enmap doesn't have key, set it, then get it" which is a very common pattern. 288 | 289 | **Kind**: instance method of Enmap 290 | **Returns**: \* - The value from the database for the key, or the default value provided for a new key. 291 | 292 | | Param | Type | Description | 293 | | --- | --- | --- | 294 | | key | string | Required. The key you want to make sure exists. | 295 | | defaultValue | \* | Required. The value you want to save in the database and return as default. | 296 | | path | string | Optional. If presents, ensures both the key exists as an object, and the full path exists. Should be a path with dot notation, such as "prop1.subprop2.subprop3" | 297 | 298 | **Example** 299 | ```js 300 | // Simply ensure the data exists (for using property methods): 301 | enmap.ensure("mykey", {some: "value", here: "as an example"}); 302 | enmap.has("mykey"); // always returns true 303 | enmap.get("mykey", "here") // returns "as an example"; 304 | 305 | // Get the default value back in a variable: 306 | const settings = mySettings.ensure("1234567890", defaultSettings); 307 | console.log(settings) // enmap's value for "1234567890" if it exists, otherwise the defaultSettings value. 308 | ``` 309 | 310 | 311 | ### enmap.has(key, path) ⇒ boolean 312 | Returns whether or not the key exists in the Enmap. 313 | 314 | **Kind**: instance method of Enmap 315 | 316 | | Param | Type | Description | 317 | | --- | --- | --- | 318 | | key | string | Required. The key of the element to add to The Enmap or array. | 319 | | path | string | Optional. The property to verify inside the value object or array. Should be a path with dot notation, such as "prop1.subprop2.subprop3" | 320 | 321 | **Example** 322 | ```js 323 | if(enmap.has("myKey")) { 324 | // key is there 325 | } 326 | 327 | if(!enmap.has("myOtherKey", "oneProp.otherProp.SubProp")) return false; 328 | ``` 329 | 330 | 331 | ### enmap.includes(key, value, path) ⇒ boolean 332 | Performs Array.includes() on a certain enmap value. Works similar to 333 | [Array.includes()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes). 334 | 335 | **Kind**: instance method of Enmap 336 | **Returns**: boolean - Whether the array contains the value. 337 | 338 | | Param | Type | Description | 339 | | --- | --- | --- | 340 | | key | string | Required. The key of the array to check the value of. | 341 | | value | string \| number | Required. The value to check whether it's in the array. | 342 | | path | string | Optional. The property to access the array inside the value object or array. Should be a path with dot notation, such as "prop1.subprop2.subprop3" | 343 | 344 | 345 | 346 | ### enmap.delete(key, path) 347 | Deletes a key in the Enmap. 348 | 349 | **Kind**: instance method of Enmap 350 | 351 | | Param | Type | Description | 352 | | --- | --- | --- | 353 | | key | string | Required. The key of the element to delete from The Enmap. | 354 | | path | string | Optional. The name of the property to remove from the object. Should be a path with dot notation, such as "prop1.subprop2.subprop3" | 355 | 356 | 357 | 358 | ### enmap.clear() ⇒ void 359 | Deletes everything from the enmap. 360 | 361 | **Kind**: instance method of Enmap 362 | 363 | 364 | ### enmap.remove(key, val, path) 365 | Remove a value in an Array or Object element in Enmap. Note that this only works for 366 | values, not keys. Note that only one value is removed, no more. Arrays of objects must use a function to remove, 367 | as full object matching is not supported. 368 | 369 | **Kind**: instance method of Enmap 370 | 371 | | Param | Type | Description | 372 | | --- | --- | --- | 373 | | key | string | Required. The key of the element to remove from in Enmap. | 374 | | val | \* \| function | Required. The value to remove from the array or object. OR a function to match an object. If using a function, the function provides the object value and must return a boolean that's true for the object you want to remove. | 375 | | path | string | Optional. The name of the array property to remove from. Should be a path with dot notation, such as "prop1.subprop2.subprop3". If not presents, removes directly from the value. | 376 | 377 | **Example** 378 | ```js 379 | // Assuming 380 | enmap.set('array', [1, 2, 3]) 381 | enmap.set('objectarray', [{ a: 1, b: 2, c: 3 }, { d: 4, e: 5, f: 6 }]) 382 | 383 | enmap.remove('array', 1); // value is now [2, 3] 384 | enmap.remove('objectarray', (value) => value.e === 5); // value is now [{ a: 1, b: 2, c: 3 }] 385 | ``` 386 | 387 | 388 | ### enmap.export() ⇒ string 389 | Exports the enmap data to stringified JSON format. 390 | **__WARNING__**: Does not work on memory enmaps containing complex data! 391 | 392 | **Kind**: instance method of Enmap 393 | **Returns**: string - The enmap data in a stringified JSON format. 394 | 395 | 396 | ### enmap.import(data, overwrite, clear) 397 | Import an existing json export from enmap. This data must have been exported from enmap, 398 | and must be from a version that's equivalent or lower than where you're importing it. 399 | (This means Enmap 5 data is compatible in Enmap 6). 400 | 401 | **Kind**: instance method of Enmap 402 | 403 | | Param | Type | Default | Description | 404 | | --- | --- | --- | --- | 405 | | data | string | | The data to import to Enmap. Must contain all the required fields provided by an enmap export(). | 406 | | overwrite | boolean | true | Defaults to `true`. Whether to overwrite existing key/value data with incoming imported data | 407 | | clear | boolean | false | Defaults to `false`. Whether to clear the enmap of all data before importing (**__WARNING__**: Any existing data will be lost! This cannot be undone.) | 408 | 409 | 410 | 411 | ### enmap.random([count]) ⇒ \* \| Array.<\*> 412 | Obtains random value(s) from this Enmap. This relies on [Enmap#array](Enmap#array). 413 | 414 | **Kind**: instance method of Enmap 415 | **Returns**: \* \| Array.<\*> - The single value if `count` is undefined, 416 | or an array of values of `count` length 417 | 418 | | Param | Type | Default | Description | 419 | | --- | --- | --- | --- | 420 | | [count] | number | 1 | Number of values to obtain randomly | 421 | 422 | 423 | 424 | ### enmap.randomKey([count]) ⇒ \* \| Array.<\*> 425 | Obtains random key(s) from this Enmap. This relies on [Enmap#keyArray](Enmap#keyArray) 426 | 427 | **Kind**: instance method of Enmap 428 | **Returns**: \* \| Array.<\*> - The single key if `count` is undefined, 429 | or an array of keys of `count` length 430 | 431 | | Param | Type | Default | Description | 432 | | --- | --- | --- | --- | 433 | | [count] | number | 1 | Number of keys to obtain randomly | 434 | 435 | 436 | 437 | ### enmap.every(valueOrFunction, [path]) ⇒ boolean 438 | Similar to 439 | [Array.every()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every). 440 | Supports either a predicate function or a value to compare. 441 | Returns true only if the predicate function returns true for all elements in the array (or the value is strictly equal in all elements). 442 | 443 | **Kind**: instance method of Enmap 444 | 445 | | Param | Type | Description | 446 | | --- | --- | --- | 447 | | valueOrFunction | function \| string | Function used to test (should return a boolean), or a value to compare. | 448 | | [path] | string | Required if the value is an object. The path to the property to compare with. | 449 | 450 | 451 | 452 | ### enmap.some(valueOrFunction, [path]) ⇒ Array 453 | Similar to 454 | [Array.some()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some). 455 | Supports either a predicate function or a value to compare. 456 | Returns true if the predicate function returns true for at least one element in the array (or the value is equal in at least one element). 457 | 458 | **Kind**: instance method of Enmap 459 | 460 | | Param | Type | Description | 461 | | --- | --- | --- | 462 | | valueOrFunction | function \| string | Function used to test (should return a boolean), or a value to compare. | 463 | | [path] | string | Required if the value is an object. The path to the property to compare with. | 464 | 465 | 466 | 467 | ### enmap.map(pathOrFn) ⇒ Array 468 | Similar to 469 | [Array.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map). 470 | Returns an array of the results of applying the callback to all elements. 471 | 472 | **Kind**: instance method of Enmap 473 | 474 | | Param | Type | Description | 475 | | --- | --- | --- | 476 | | pathOrFn | function \| string | A function that produces an element of the new Array, or a path to the property to map. | 477 | 478 | 479 | 480 | ### enmap.find(pathOrFn, [value]) ⇒ \* 481 | Searches for a single item where its specified property's value is identical to the given value 482 | (`item[prop] === value`), or the given function returns a truthy value. In the latter case, this is similar to 483 | [Array.find()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find). 484 | 485 | **Kind**: instance method of Enmap 486 | 487 | | Param | Type | Description | 488 | | --- | --- | --- | 489 | | pathOrFn | string \| function | The path to the value to test against, or the function to test with | 490 | | [value] | \* | The expected value - only applicable and required if using a property for the first argument | 491 | 492 | **Example** 493 | ```js 494 | enmap.find('username', 'Bob'); 495 | ``` 496 | **Example** 497 | ```js 498 | enmap.find(val => val.username === 'Bob'); 499 | ``` 500 | 501 | 502 | ### enmap.findIndex(pathOrFn, [value]) ⇒ string \| number 503 | Searches for the key of a single item where its specified property's value is identical to the given value 504 | (`item[prop] === value`), or the given function returns a truthy value. In the latter case, this is similar to 505 | [Array.findIndex()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex). 506 | 507 | **Kind**: instance method of Enmap 508 | 509 | | Param | Type | Description | 510 | | --- | --- | --- | 511 | | pathOrFn | string \| function | The path to the value to test against, or the function to test with | 512 | | [value] | \* | The expected value - only applicable and required if using a property for the first argument | 513 | 514 | **Example** 515 | ```js 516 | enmap.findIndex('username', 'Bob'); 517 | ``` 518 | **Example** 519 | ```js 520 | enmap.findIndex(val => val.username === 'Bob'); 521 | ``` 522 | 523 | 524 | ### enmap.reduce(predicate, [initialValue]) ⇒ \* 525 | Similar to 526 | [Array.reduce()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce). 527 | 528 | **Kind**: instance method of Enmap 529 | 530 | | Param | Type | Description | 531 | | --- | --- | --- | 532 | | predicate | function | Function used to reduce, taking three arguments; `accumulator`, `currentValue`, `currentKey`. | 533 | | [initialValue] | \* | Starting value for the accumulator | 534 | 535 | 536 | 537 | ### enmap.filter(pathOrFn, [value]) ⇒ Enmap 538 | Similar to 539 | [Array.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter). 540 | Returns an array of values where the given function returns true for that value. 541 | Alternatively you can provide a value and path to filter by using exact value matching. 542 | 543 | **Kind**: instance method of Enmap 544 | 545 | | Param | Type | Description | 546 | | --- | --- | --- | 547 | | pathOrFn | function | The path to the value to test against, or the function to test with. If using a function, this function should return a boolean. | 548 | | [value] | string | Value to use as `this` when executing function | 549 | 550 | 551 | 552 | ### enmap.sweep(pathOrFn, [value]) ⇒ number 553 | Deletes entries that satisfy the provided filter function or value matching. 554 | 555 | **Kind**: instance method of Enmap 556 | **Returns**: number - The number of removed entries. 557 | 558 | | Param | Type | Description | 559 | | --- | --- | --- | 560 | | pathOrFn | function \| string | The path to the value to test against, or the function to test with. | 561 | | [value] | \* | The expected value - only applicable and required if using a property for the first argument. | 562 | 563 | 564 | 565 | ### enmap.changed(cb) 566 | Function called whenever data changes within Enmap after the initial load. 567 | Can be used to detect if another part of your code changed a value in enmap and react on it. 568 | 569 | **Kind**: instance method of Enmap 570 | 571 | | Param | Type | Description | 572 | | --- | --- | --- | 573 | | cb | function | A callback function that will be called whenever data changes in the enmap. | 574 | 575 | **Example** 576 | ```js 577 | enmap.changed((keyName, oldValue, newValue) => { 578 | console.log(`Value of ${keyName} has changed from: \n${oldValue}\nto\n${newValue}`); 579 | }); 580 | ``` 581 | 582 | 583 | ### enmap.partition(pathOrFn, value) ⇒ Array.<Array.<\*>> 584 | Separates the Enmap into multiple arrays given a function that separates them. 585 | 586 | **Kind**: instance method of Enmap 587 | **Returns**: Array.<Array.<\*>> - An array of arrays with the partitioned data. 588 | 589 | | Param | Type | Description | 590 | | --- | --- | --- | 591 | | pathOrFn | \* | the path to the value to test against, or the function to test with. | 592 | | value | \* | the value to use as a condition for partitioning. | 593 | 594 | 595 | 596 | ### Enmap.multi(names, options) ⇒ Object 597 | Initialize multiple Enmaps easily. 598 | 599 | **Kind**: static method of Enmap 600 | **Returns**: Object - An array of initialized Enmaps. 601 | 602 | | Param | Type | Description | 603 | | --- | --- | --- | 604 | | names | Array.<string> | Array of strings. Each array entry will create a separate enmap with that name. | 605 | | options | Object | Options object to pass to each enmap, excluding the name.. | 606 | 607 | **Example** 608 | ```js 609 | // Using local variables. 610 | const Enmap = require('enmap'); 611 | const { settings, tags, blacklist } = Enmap.multi(['settings', 'tags', 'blacklist']); 612 | 613 | // Attaching to an existing object (for instance some API's client) 614 | const Enmap = require("enmap"); 615 | Object.assign(client, Enmap.multi(["settings", "tags", "blacklist"])); 616 | ``` 617 | -------------------------------------------------------------------------------- /docs/assets/enmap-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 22 | 25 | 26 | 33 | 36 | 37 | 38 | 40 | 41 | 43 | image/svg+xml 44 | 46 | 47 | 48 | 49 | 50 | 66 | 84 | 87 | 92 | 96 | 100 | 101 | 105 | 106 | 109 | 113 | 117 | 121 | 122 | 125 | 129 | 133 | 137 | 141 | 142 | 145 | ^ 156 | ^ 167 | 168 | 169 | -------------------------------------------------------------------------------- /docs/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eslachance/enmap/44b8276a87caea571afb405a4e9c353614eea7be/docs/assets/favicon.ico -------------------------------------------------------------------------------- /docs/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eslachance/enmap/44b8276a87caea571afb405a4e9c353614eea7be/docs/assets/favicon.png -------------------------------------------------------------------------------- /docs/blog-posts/README.md: -------------------------------------------------------------------------------- 1 | # Blog Posts 2 | 3 | -------------------------------------------------------------------------------- /docs/blog-posts/enmaps-history.md: -------------------------------------------------------------------------------- 1 | # Enmap's History 2 | 3 | From the first moment where I started using the Discord.js library, one thing in it fascinated me: "Collections". Discord.js Collections are a Map structure from JavaScript on top of which a bunch of useful methods are added, most notably those from JavaScript's Array structure. 4 | 5 | Things like map, filter, reduce, find, sort... they made Maps so useful, so much more powerful, that I admired their design. It struck me at one point, that if such a structure were to be separated from Discord.js and perhaps made to be saved in a database, it would make interacting with data so easy that even a child could do it. 6 | 7 | So when I started getting seriously into bot that required their own data to be saved, I turned to Amish Shah \(Hydrabolt\) and I said to him, I said "Listen, buddy, can I extract Collections and publish them as a separate module? That'd be awesome!" and Amish replied, like the great guy he is, "uhhhh sure, why not?" 8 | 9 | And so, in May 2017, the `djs-collection` module was born. It was a simple thing, just straight-up lifted from Discord.js' code \(not illegally, mind you, I retained all proper licenses and credits to Hydrabolt!\). The following month, I added persistence \(saving to a database\) to it and published `djs-collection-persistent` , which then became my own defacto way to save data to a database. 10 | 11 | But... let's be honest, `npm install --save djs-collection-persistent` is a mouthful to type out. Plus, I was realizing that having those two as separate modules meant I had to update them separately and ensure they still worked individually... So at one point, I decided it was time to merge them. 12 | 13 | Releasing a single repository meant that I could now change the name, because of the aformentioned "omg mile-long name" problem, and just the overall annoyance of writing it. So I settled on "well, they're enhanced maps, let's call it Enmap!". A quick search revealed Enmap's only other apparent meaning was that it was the name of a satellite, and I was guessing no one would confuse the two. 14 | 15 | But I didn't want to _force_ enmap users to have persistence, so at the same time I actually created a _separate_ module called `enmap-level`, which controlled the database layer and was completely optional. These modules I called _Providers_ since, obviously, they provided data persistence and and API. 16 | 17 | Enmap 0.4.0 was released at the beginning of October 2017, and since then has grown into a fairly solid module used by tens of thousands of people across the world, not only in discord.js bots but also in other projects built with Node. Its solidity and simplicity makes it the ideal storage for simple key/value pairs, and I'm extremely proud to have made it. 18 | 19 | At the moment of writing this \(2018-09-02\) Enmap has been downloaded over 32,000 times and is growing by the minute with almost 10,000 downloads in August alone! 20 | 21 | ## Update, August 2019 22 | 23 | It's been a year now since I last wrote this post. And in case you were wondering, growth hasn't stopped! In fact, it's quite accelerated. One big change that I made was to go back to a single locked-in database provider, which I describe in Why SQLite Only? 24 | 25 | Other than that, and adding some new features due to the switch to better-sqlite3, the main event is that just las month I reached a whopping 500,000 downloads for Enmap. Yes, that's a half million downloads for my little useful module that I started making for myself and ended up being useful for so many. 26 | 27 | -------------------------------------------------------------------------------- /docs/blog-posts/josh.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Let's take a quick peek at what Josh is, and what it means for the future of 4 | Enmap 5 | --- 6 | 7 | # Enmap and Josh 8 | 9 | As I've noted in my[ previous blog post](why-sqlite-only.md), when Enmap moved to SQLite only, there were a few feathers and features lost in transition. Most notably, the loss of Providers was a big one, even though in my opinion it was a good trade-off to get the new features I wanted to include in Enmap 4 and onward. 10 | 11 | But since that moment where Providers were removed, I had a plan in mind to give those that needed them an escape route. And not only that, Enmap itself does have some pretty solid limitations when it comes to growth, because of its lack of ability to support multiple processes and sharded applications. 12 | 13 | ### Introducing Josh 14 | 15 | Then plan was [Josh](https://josh.evie.dev/), all along. Josh is the Javascript Object Storage Helper, and if that sounds a lot like what Enmap does it's because it is. In fact, [Josh ](https://josh.evie.dev/)could best be described to you, my reader, as "A version of Enmap that doesn't have caching, is promised-based, and supports providers again". 16 | 17 | So I've been working on it for a few years now - not full time, mind you, as it would have been ready a long time ago, but as a side project. It's finally picked up steam, and you can Get Josh right now to try out the early access version. It's limited \(not as powerful as Enmap is currently\) but that's rapidly evolving. 18 | 19 | ### So what does that mean for Enmap? 20 | 21 | You might immediately wonder, "But Evie, if you're working on Josh, what's going to happen with Enmap?" and I'm telling you right now, you don't need to worry about this. Enmap is still growing in popularity, I still have things to do with it, and I fully intend on maintaining and enhancing it in the future. 22 | 23 | Josh might be similar to Enmap but it's not made to replace it! It has a different purpose, which is to support larger applications, potentially web-based ones, provide live updates, and all the things that were lost with Enmap's great provider purge. And since Josh is promise-based, it's not as simple to pick up as Enmap was, so I do expect people to start off with Enmap either way. 24 | 25 | Josh and Enmap should, and will, be fully compatible with one another, in that you will be able to easily migrate between them \(with [export](../api.md#enmap-export-string)\(\) and [import](../api.md#enmap-import-data-overwrite-clear-enmap)\(\) \), and moving from one to another would require a minimal amount of code changes. It's not zero, but I'm trying as much as possible to keep those differences as small as possible. 26 | 27 | ### What does the future hold? 28 | 29 | I've already back-ported a few things that I originally intended for Josh as part of Enmap's new updates. The [observe](../api.md#enmap-observe-key-path)\(\) method, as well as the[ serializer/deserializer](../usage/serialize.md) feature, were originally intended for Josh but ended up being implementable in Enmap also. This means, if I add a feature to Josh, I will add it to Enmap if I can, if it's compatible. So you won't be left behind! 30 | 31 | It is my sincere hope that Enmap and Josh will both continue to grow, to help more people, and to help us all create better code, together! 32 | 33 | -------------------------------------------------------------------------------- /docs/blog-posts/why-remove-cache.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Why would Enmap V6 remove caching? Doesn't that make it slower? What gives? 4 | --- 5 | 6 | # Why update to v6??? 7 | 8 | So, you might be wondering what the main driver of Enmap Version 6 is. Let me give you a little bit of historical context here. 9 | 10 | Before Enmap, there was [djs-collection-persistent](https://www.npmjs.com/package/djs-collection-persistent). This module was born 11 | from using discord.js' Collection, and the idea was, "What if I could use that, but save it in a database?" and thus, this whole 12 | adventure started. It saved data on `leveldb`, and it was alright. But who the hell wants to remember that name? 13 | 14 | And thus, Enmap 1.0 was born. The *Enhanced* Map, which detached the name from djs itself. Enmap 1.0 already established Providers, 15 | including leveldb, postgresql, sqlite, rethinkdb, and mongo. 16 | 17 | Enmap 2 and 3 added more features, moved things around, but generally stayed the same. Lot of bug fixes, added math, that sort of thing. 18 | 19 | In Enmap 4, the main major change was that I removed all providers. I discovered (better-sqlite3)[https://www.npmjs.com/package/better-sqlite3], 20 | the first (and only?) synchronous database module. This started changing everything, but I didn't fully trust its performance yet. So Enmap 4 21 | is sqlite only, persistent, but it still has its cache... that is to say, it's still an enhanced Map structure with persistence. Enmap 5 is 22 | more of the same, updates, small breaking changes, new features, etc. 23 | 24 | But there's a problem : Enmap... is stil cached. It still uses a lot of memory, and that makes it slower than it should be. better-sqlite3 is *fast* 25 | and now I'm updating both the cache (Map) and the database! But I left this sleeping for the last few years as I was doing other things with life. 26 | 27 | And here we are. Enmap 6.0 just removes caching, and updates all the map/array methods to directly interact with the database, no cache needed. This 28 | not only simplifies the code, and reduces RAM usage by a *wide* margin, it also makes Enmap much faster in a number of situations. 29 | 30 | ## The SPEED 31 | 32 | ### Loading 33 | 34 | Loading of data remains approximately the same when empty, but can be much faster in Enmap 6 the larger your database is, if `autoFetch` is `true`. 35 | With the 1 million rows, Enmap 6 loads in 6ms (milliseconds) but Enmap 5 loads in 20s (seconds). That's a massive difference, because of caching. 36 | 37 | ### Adding Data 38 | 39 | This test inserts 1 million rows in a simple for loop. Each entry is an object with multiple randomly generated numbers. 40 | 41 | Here's the actual test! 42 | ```js 43 | const rnum = size => Math.floor(Math.random() * (size + 1)); 44 | 45 | for (let i = 1; i <= 1_000_000; i++) { 46 | enmap.set(`obj${i}`, { 47 | key: `obj${i}`, 48 | a: rnum(100), 49 | b: rnum(100), 50 | c: rnum(100), 51 | d: rnum(100), 52 | e: rnum(100), 53 | }); 54 | } 55 | ``` 56 | 57 | ```diff 58 | -1 million enmap5: 2:57.741 (m:ss.mmm) 59 | +1 million enmap6: 2:44.252 (m:ss.mmm) 60 | ``` 61 | 62 | As you can see, the insert time is almost the same. I tried a few times, the time are around 2 minutes 50 seconds, +- 10 seconds. 63 | The speed does not change if the data already exists since it's all new data anyway (this means "key creation" doesn't cost anything). 64 | 65 | ### Looping over data 66 | 67 | Enmap, when looping over data, is generally faster. 68 | 69 | Here's the tests and results. I tried more than once, and it's all the same ballpark. 70 | 71 | #### Partition: Faster 72 | 73 | ```js 74 | console.time('partition enmap'); 75 | const [one, two] = enmap.partition((value) => value.a % 2 === 0); 76 | console.timeEnd('partition enmap'); 77 | 78 | console.time('partition enmap6'); 79 | const [one6, two6] = enmap6.partition((value) => value.a % 2 === 0); 80 | console.timeEnd('partition enmap6'); 81 | ``` 82 | 83 | ```diff 84 | -partition enmap5: 51.221s 85 | +partition enmap6: 6.048s 86 | ``` 87 | 88 | As you can see Enmap 6 is 8.5x faster with partitioning (again, this is on 1 million rows). 89 | 90 | This is partially due to partition() returning an array of 2 Enmap structure. It would potentially 91 | be faster if Enmap 5's partition() returned arrays. 92 | 93 | #### Filtering: Faster, sort of 94 | 95 | ```js 96 | console.time('filter enmap'); 97 | const filtered = enmap.filter((value) => value.a % 2 === 0); 98 | console.timeEnd('filter enmap'); 99 | 100 | console.time('filter enmap6'); 101 | const filtered6 = enmap6.filter((value) => value.a % 2 === 0); 102 | console.timeEnd('filter enmap6'); 103 | ``` 104 | 105 | ```diff 106 | - filter enmap5: 28.315s 107 | + filter enmap6: 5.560s 108 | ``` 109 | 110 | Filtering is also faster with Enmap 6 partially because Enmap 5 uses an Enmap as a return value, 111 | rather than an array. filterArray is definitely faster if the data is cached: 112 | ``` 113 | filterArray enmap: 56.564ms 114 | ``` 115 | 116 | #### Mapping: Slower 117 | 118 | ```js 119 | console.time('map enmap'); 120 | const mapped = enmap.map((value) => value.a * 2); 121 | console.timeEnd('map enmap'); 122 | 123 | console.time('map enmap6'); 124 | const mapped6 = enmap6.map((value) => value.a * 2); 125 | console.timeEnd('map enmap6'); 126 | ``` 127 | 128 | ```diff 129 | -map enmap5: 47.295ms 130 | +map enmap6: 6.271s 131 | ``` 132 | 133 | I **almost** missed the difference in magnitude here: enmap.map() is slower by a lot. 134 | I'm not sure why and won't expend more time on this, and I don't feel guilty, because 135 | loading the 1M values took 17s for enmap5 versus the 6ms uncached enmap6. Still a clear 136 | value winner either way. 137 | 138 | ## Conclusion 139 | 140 | I initially was very excited by the better Enmap 6 performance, but that was before I realized that 141 | some of this better performance is due to using memory-only Enmaps as return values. This means 142 | that some Enmap 5 methods are faster, such as filterArray and map. 143 | 144 | As I really do want Enmap 6 to come out, however, I'm satisfied with the current removal of the cache. 145 | it still gives the advantage of having a lot less RAM usage since a cache isn't filled. It also means 146 | more consistency in query times, in memory usage, and availability - some cached methods like partition 147 | *only* worked with cached values and did not fetch keys before running. 148 | 149 | I will, however, re-add caching to Enmap 6.1, as an optional addition and potentially more control over 150 | the caching, time-to-live, etc. 151 | -------------------------------------------------------------------------------- /docs/blog-posts/why-sqlite-only.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | This page explains the reason behind the removal of the "Provider" system, and 4 | the selection of sqlite as the only database available for Enmap starting 5 | version 4 6 | --- 7 | 8 | # Why SQLITE only? 9 | 10 | ## Why providers in the first place? 11 | 12 | So one of the major changes from Enmap 3 to 4 is the removal of Providers. Providers were something I've had since Enmap 1.0 \(when I converted from djs-collections-persistent\), and had 2 advantages \(2 reasons to have them in the first place\). 13 | 14 | 1. It enabled supporting more databases, not only one. This gave more power to users, and, I thought, more capabilities. 15 | 2. It separated the memory enmap \(non-persistent\) from the database layer, so installing enmap didn't require installing sqlite. 16 | 17 | ## And why remove them? 18 | 19 | But, after a year of updating Enmap, I realized that I'd painted myself in a corner with Providers. There came to light that there were multiple limitations to providers: 20 | 21 | 1. Features were limited to the "lowest common denominator", whatever was available to _all_ providers. For instance, better-sqlite3 is a synchronous module that's nonblocking \(which is a magical thing, really\). But since all other providers required promises, then I had to use sqlite as a promise module. 22 | 2. Maintaining multiple providers is hard work. Every new feature would require updating all the providers \(5 at this time\), and there were many requests to create new providers which is an annoying, sometimes complicated task that adds even more work in the future. 23 | 3. There were features I wanted that simply weren't possible, physically, with the providers \([like the fetchAll/autoFetch options](../usage/fetchall.md)\). 24 | 25 | In addition, the advantages became lesser with time. I realized most people were using leveldb at first, then most switch to sqlite when I updated guides to use that provider. Essentially, most people use whatever they're told to use. So, just forcing one database wasn't that much of an issue and didn't affect the majority of users. 26 | 27 | Also, most people did use enmap with persistence, and those that didn't... well, most users have enmap to use with discord.js bots in the first place which gives them Collection - almost the same as a non-persistent enmap. 28 | 29 | ## What are the advantages of sqlite? 30 | 31 | The reasoning behind removing all other providers and keeping sqlite was for specific features and capabilities inherent to the module I'm using, better-sqlite3. 32 | 33 | * better-sqlite3 is, as I mention, _synchronous_ , which means, no callbacks, no promises. Just straight-up "make a request and it does it before the next line". No more need for "waiting" for things, resolving promises, etc. 34 | * The sync nature of better-sqlite3 means I can add an autoFetch feature. I can simply say "If the key isn't there, try to get the data", without requiring the user to resolve a promise. This is awesome. 35 | * By the same token, I can also add simple things like "get all keys in the database" using a _getter_. This means you can do `enmap.indexes` and this is actually querying the database seamlessly without the user really knowing it does that. Same for `enmap.count` and other features I'm planning. 36 | 37 | So overall, I'm happy with my decision. It gives me more power, it gives users more features, and the people affected by the removal of the other providers are few and far between. Hell, enmap-pgsql has less than 1000 downloads on npm which is mostly mirrors and caches. It showed me that provider was pretty useless in the first place. 38 | 39 | ## But what about people that NEED a provider? 40 | 41 | ~~I recognize that some people might want to use enmap and can't use sqlite. This is for many valid reasons, for example using it on heroku which doesn't support sqlite and leveldb. For those users, I'm keeping the providers open for maintenance. If someone wants to maintain and update the V3 branch, or even fork the entire system and maintain it under a new name, I have no issue with that \(assuming licenses are properly kept\). I'll accept PRs on all enmap repositories, including backporting some features and adding new ones.~~ 42 | 43 | ~~I'm also keeping the V3 docs in this gitbook so it can be maintained through gitbook and PRed on github.~~ 44 | 45 | ~~You can still install any provider as you would before, and install enmap using `npm i eslachance/enmap#v3` for the version 3 branch that will remain.~~ 46 | 47 | Update: Enmap's no longer hosted on gitbook, and Enmap V3 is old enough to be dangerous to use due to potential security vulnerabilities, and providers most likely don't work on recent node versions anyways. All Enmap 3 providers are deprecated and archived. 48 | -------------------------------------------------------------------------------- /docs/help/CJStoESM.md: -------------------------------------------------------------------------------- 1 | I'm not going to describe all of the process here, but the gist of it goes something like this 2 | (credit to `qrpx` on The Programmer's Hangout for the reference): 3 | 4 | ```diff 5 | - const AModule = require("a-module") 6 | + import AModule from "a-module"; 7 | 8 | - const { AModule } = require("a-module"); 9 | + import { AModule } from "a-module"; 10 | 11 | - module.exports = AnExport; 12 | + export default AnExport; 13 | 14 | - module.exports = { Export1, Export2 }; 15 | + export default { Export1, Export2 }; 16 | 17 | - module.exports.Export1 = Export1; 18 | + export { Export1 }; 19 | 20 | - module.exports = require("a-module"); 21 | + export Export from "a-module"; 22 | + export * from "a-module"; 23 | 24 | - module.exports = require("a-module").Export; 25 | + export { Export } from "a-module"; 26 | 27 | - module.exports = { 28 | - myvar1: "String1", 29 | - mayVar2: "String2" 30 | - } 31 | + export const myVar1 = "String1"; 32 | + export const myVar2 = "String2"; 33 | 34 | - require("my-file")(myvar); 35 | + (await import(`my-file`)).default(myVar); 36 | ``` 37 | 38 | Renaming: 39 | ```diff 40 | - const renamed = require("name"); 41 | + import name as renamed from "name"; 42 | 43 | - const { name: renamed } = require("name"); 44 | + import { name as renamed } from "name"; 45 | 46 | - module.exports.Export1 = Export2; 47 | + export { Export2 as Export1 }; 48 | ``` 49 | 50 | 51 | Advantages over CommonJS 52 | ```diff 53 | - const EntireModule = require("a-module"); 54 | - const APartOfIt = require("a-module").part; 55 | 56 | + import EntireModule, { part as APartOfIt } from "a-module"; 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/install/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | To install Enmap, please read these instructions very carefully, every word is important! 4 | --- 5 | 6 | # Enmap Installation 7 | 8 | Enmap is a wrapper around better-sqlite3, which requires to be built directly on your system. As such, you need to install pre-requisites first. Please follow these instructions _to the letter_. If it's not written here, you probably shouldn't do it unless you know _why_ you're doing it. 9 | 10 | ## Pre-Requisites 11 | 12 | {% hint style="warning" %} 13 | SQLite modules usually only successfully work on LTS versions of node which are even-numbered. This means 14 | it will work on node 12, 14, 16 but will most likely _not_ work on 13, 15, 17. Make sure you have the right version, check this with `node -v`. 15 | {% endhint %} 16 | 17 | How to install the pre-requisites depends on your operating system, so see below for instructions: 18 | 19 | {% tabs %} 20 | {% tab title="Windows" %} 21 | On Windows, two things are required to install better-sqlite3. Python, and the Visual Studio C++ Build Tools. They are required for any module that is _built_ on the system, which includes sqlite. 22 | 23 | To install the necessary prerequisites on Windows, the easiest is to simply run the following commands separately, _under an **administrative** command prompt or powershell:_ 24 | 25 | ```javascript 26 | // First run: 27 | npm i -g --add-python-to-path --vs2015 --production windows-build-tools 28 | // If you get an error here READ THE TEXT ABOVE AND BELOW THIS CODE BLOCK, IT IS IMPORTANT. 29 | 30 | // Then run: 31 | npm i -g node-gyp@latest 32 | ``` 33 | 34 | > It's _very important_ that this be run in the **administrative** prompt, and not a regular one. 35 | 36 | Once the windows-build-tools are installed \(this might take quite some time, depending on your internet connection\), **close all open command prompts, powershell windows, VSCode, and editors with a built-in console/prompt**. Otherwise, the next command will not work. 37 | {% endtab %} 38 | 39 | {% tab title="Linux" %} 40 | On Linux, the pre-requisites are much simpler in a way. A lot of modern systems \(such as Ubuntu, since 16.04\) already come with python pre-installed. For some other systems, you might have to fiddle with it to install python (2 or 3, whichever is easiest). Google will be your friend as is customary with Linux. 41 | 42 | As for the C++ build tools, that's installed using the simple command: `sudo apt-get install build-essential` for most debian-based systems. For others, look towards your package manager and specificall "GCC build tools". 43 | {% endtab %} 44 | 45 | {% tab title="Mac OS" %} 46 | As of writing this page, MacOS versions seem to all come pre-built with Python on the system. You will, however, need the C++ build tools. 47 | 48 | * Install [XCode](https://developer.apple.com/xcode/download/) 49 | * Once XCode is installed, go to **Preferences**, **Downloads**, and install the **Command Line Tools**. 50 | 51 | Once installed, you're ready to continue. 52 | {% endtab %} 53 | {% endtabs %} 54 | 55 | ## Installing Enmap 56 | 57 | Once those pre-requisites are installed \(if they're not, scroll up, and _follow the instructions_\), and you've closed all open command prompts, open a new, _normal_ \(not-admin\) command prompt or terminal in your project, then install Enmap using the following command: 58 | 59 | ```text 60 | npm i enmap 61 | ``` 62 | 63 | This will take a few minutes, as it needs to build better-sqlite3 from source code, and then install enmap itself. Note that "a few minutes" can be 1 or 30 minutes, it really depends on your hardware and configuration. 64 | 65 | If you get any errors, please see the [Troubleshooting Guide](troubleshooting-guide.md). If the guide doesn't help, join the Discord \(link at the top of this page\). 66 | -------------------------------------------------------------------------------- /docs/install/troubleshooting-guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: '"Anything that can go wrong will go wrong" - Murphy''s Law' 3 | --- 4 | 5 | # Troubleshooting Guide 6 | 7 | {% hint style="warning" %} 8 | Please make sure to read the [install page](/install) **VERY CAREFULLY**! This should solve all the issues you face. If not carry on, but don't say we didn't tell you. 9 | {% endhint %} 10 | 11 | ## It's Taking Suuuuuuuuuuuuuuuuuper Long to Install! Whats up with that? 12 | 13 | Your computer has to install some programs and then build everything from source. This is going to take a while depending on your computer speed. Patience is key. We can't speed it up for you. It may look like its frozen but it is not. 14 | 15 | Some tips: Make sure you are doing this install on a fast hard drive (ideally SSD, but HDD will work fine) and **not on a USB Key** or removable media. 16 | 17 | ## command failed downloading python or build tools 18 | 19 | `Please restart this script in an administrative Powershel!` means you did not read the [install page](/install) correctly. 20 | 21 | ## MSB3428: Could not load the Visual C++ component "VCBuild.exe" 22 | 23 | Looks like someone hasn't follows the [installation instructions](/install) correctly... 24 | 25 | (are you sensing there's a theme yet in this troubleshooting guide?) 26 | ## My Question Isn't Answered Here! 27 | 28 | Please make sure you read the [install page](/install) first. If you can't find what your looking for you can join the discord [here](https://discord.gg/N7ZKH3P). 29 | -------------------------------------------------------------------------------- /docs/install/upgrade.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | This guide assists in migrating your data from Enmap 3 using Providers, to the 4 | latest version of enmap. 5 | --- 6 | 7 | # Migrating data from Enmap 3 8 | 9 | {% hint style="warning" %} 10 | You do not need this page if you're new to Enmap or if you're starting a new project! 11 | {% endhint %} 12 | 13 | Upgrading to enmap v4 requires a little bit of migration, as Enmap 4 changed the internal method by which data is stored, slightly. To use this migration: 14 | 15 | * Make a copy of your current app in a new folder. 16 | * Create a new folder "on the same level" as your bot. Name it something like "migrate" 17 | * You should now have 3 folders. Something like `mybots/coolbot` , `mybots/coolbot-copy` , `mybots/migrate/` 18 | * In the `migrate` folder, run `npm i enmap@3.1.4 enmap-sqlite@latest` , as well as whatever source provider you need if it's not sqlite \(in my example, `npm i enmap-mongo@latest` 19 | 20 | You should now have something like the following image. 21 | 22 | ![](../.gitbook/assets/image.png) 23 | 24 | In the `migrate` folder, create an `index.js` and use the following script for migration. Note that it's an example, change the provider option to fit what you're actually using. 25 | 26 | ```javascript 27 | const Enmap = require("enmap"); 28 | const Provider = require("enmap-mongo"); 29 | const SQLite = require("enmap-sqlite"); 30 | 31 | let options = { 32 | name: "test", 33 | user: "username", 34 | host: "yourhost", 35 | collection: "enmap", 36 | password: "password", 37 | port: 55258 38 | }; 39 | 40 | const source = new Provider(options); 41 | const target = new SQLite({"name": "test", dataDir: '../coolbot-copy/data'}); 42 | Enmap.migrate(source, target).then( () => process.exit(0) ); 43 | ``` 44 | 45 | Very important: the "target" **must** be enmap-sqlite. Enmap v4 only supports an sqlite-backend. 46 | 47 | From the `migrate` folder, run `node index.js`, which should correctly migrate your data. 48 | 49 | ### Simpler migration from enmap-sqlite 50 | 51 | If you're using enmap-sqlite already, you don't really need to do the entire thing above. Adding a single file called `migrate.js` to your project folder, then running it with `node migrate.js` will convert the format and then all you need is to modify the code for Enmap 4. Still, I recommend backing up your bot first. Just in case. 52 | 53 | ```javascript 54 | const Enmap = require("enmap"); 55 | const SQLite = require("enmap-sqlite"); 56 | 57 | const source = new SQLite({"name": "test"}); 58 | const target = new SQLite({"name": "test"}); 59 | Enmap.migrate(source, target).then( () => process.exit(0) ); 60 | ``` 61 | 62 | ## Code Changes 63 | 64 | There is _very little_ you need to change when moving to Enmap 4. The only changes that are required after migrating is the initialization of your Enmap which is now simpler. 65 | 66 | ```javascript 67 | // Change From: 68 | const Enmap = require("enmap"); 69 | const Provider = require("enmap-mongo"); 70 | 71 | client.points = new Enmap({provider: new Provider({name: "points", url: "blah"}); 72 | 73 | // Change To: 74 | const Enmap = require("enmap"); 75 | client.points = new Enmap({name: "points"}); 76 | ``` 77 | 78 | If using Enmap.multi\(\), the change is just as simple: 79 | 80 | ```javascript 81 | // Change from V3: 82 | const Enmap = require("enmap"); 83 | const Provider = require("enmap-mongo"); 84 | 85 | Object.assign(client, Enmap.multi(["settings", "tags"], Provider, { url: "blah" })); 86 | 87 | // Change to V4: 88 | const Enmap = require("enmap"); 89 | Object.assign(client, Enmap.multi(["settings", "tags"])); 90 | ``` 91 | 92 | The rest of your code \(all interactions with Enmap\) can remain the same - there should be no need to edit any of it. 93 | 94 | ## Installing V4 95 | 96 | Once your data is migrating and the code is changed, you can go ahead and install enmap version 4 through `npm i enmap@latest` in your "new" bot folder \(the target of the migration\). This will take a few minutes \(it needs to rebuild sqlite\) and output that 4.0.x is now installed. Start the bot, and it should be working! If it doesn't, join the support server and we'll help you out ^\_^. 97 | 98 | -------------------------------------------------------------------------------- /docs/install/upgradev6.md: -------------------------------------------------------------------------------- 1 | # Upgrading Enmap to Version 6 2 | 3 | Version 6 of Enmap is a complete re-write, even though most of the API remains identical, and the data can easily be transfered. 4 | 5 | Please pay attention to all the changes on this page :) 6 | 7 | ## Migration Method 8 | 9 | **BEFORE YOU UPGRADE VERSION**, you will need to use `enmap.export()` on Enmap 5, to have a functional backup. 10 | 11 | I *strongly* advise copying your `./data` directory... just in case this breaks ;) 12 | 13 | Here's a quick and dirty script: 14 | 15 | ```js 16 | const fs = require("fs"); 17 | const Enmap = require("enmap"); 18 | 19 | const enmap = new Enmap({ name: 'nameofenmap' }); 20 | 21 | fs.writeFile('./export.json', enmap.export(), () => { 22 | // I hope the data was in fact saved, because we're deleting it! Double-check your backup file size. 23 | enmap.clear(); 24 | }); 25 | ``` 26 | 27 | {% hint style="warning" %} 28 | You *will* need to do this for every separate enmap (every "name") you have, individually, with separate export files! 29 | {% endhint %} 30 | 31 | Once exporting is done, you can `npm i enmap@latest` to get version 6.X. After this, the *import* needs to be done, as such: 32 | 33 | ```js 34 | const fs = require("fs"); 35 | const Enmap = require("enmap"); 36 | 37 | const enmap = new Enmap({ name: 'nameofenmap' }); 38 | 39 | fs.readFile('./export.json', (err, data) => { 40 | enmap.import(data); 41 | }); 42 | ``` 43 | 44 | Marginally tested, but should work fine for any and all data. 45 | 46 | ## Move to ESM 47 | 48 | The first major change is the move to ECMAScript Modules, or *ESM*, which some erroneously call "ES6 modules". This change unfortunately not only 49 | affects the code related to Enmap but also means if you want to keep using it, you'll have to move to ESM too along with the rest of us. Gone is 50 | CJS, here comes ESM! 51 | 52 | ESM has been around for a long time, it matches the module format used in other javascript engines (such as browsers) and it used by the majority 53 | of builder tools. If you're using Typescript or doing web frameworks, chances are you're using ESM already. And if you're still on CJS, well, 54 | going to ESM is important in your JavaScript developer journey anyways. 55 | 56 | So what does this mean? It means modifying all your imports and exports, starting with Enmap: 57 | ```diff 58 | - const Enmap = require("enmap"); 59 | + import Enmap from 'enmap'; 60 | ``` 61 | 62 | Is that it? Yes, that's it... for my module. Now you have to do this for the rest of your code. [Here's a bit of a guide for you](../help/CJStoESM.md). 63 | 64 | ## Removal of the Caching 65 | 66 | Caching has been around since Enmap v1, for the simple reason that enmap used wrap around callback-based and promise-based database modules. 67 | In order to provide a synchronous interface to you, the user, the use of caching to update and read the database in the background. That 68 | hasn't been the case since Enmap 4 where I stripped out the providers and locked it to `better-sqlite3`, the only synchronous database module 69 | that exists for an actual database connection. That means I didn't need the cache anymore, but I kept it for performance reasons. 70 | 71 | For more details and justifications, see ["Why Remove Cache?"](../blog-posts/why-remove-cache.md). 72 | 73 | This means the following Enmap features are obsolete and have been stripped out from V6, hopefully never to be added again. 74 | 75 | - `enmap.fetch` and `enmap.fetchEverything` : were used to query the database and put the data in cache. 76 | - `enmap.evict` : used to remove from cache. 77 | - `enmap.array` and `enmap.keyArray` : used to list values and keys from cache only. 78 | - `enmap.indexes` : used to get a list of keys in the database (not the cache). 79 | - `options.fetchAll` : determines whether to cache the entire database on load. 80 | - `options.cloneLevel`: was a workaround to objects provided by the user affecting objects in the cache. 81 | - `options.polling` and `options.pollingInterval`: used to update the cache on an interval, useful to sync many processes. 82 | - `options.autoFetch` : used to fetch values automatically when using enmap.get() 83 | 84 | So all the above just don't exist anymore. However, they will return in Enmap 6.1 with *optional* controllable caching. 85 | 86 | ## Removal of duplicate concerns 87 | 88 | Enmap used to essentially extend two different structures: the Map(), and the Array(), javascript structures. With the removal of the cache, 89 | the Map()... well... I guess at this point Enmap's name is historical because I'm not extending a Map anymore! However, the extension of Map() 90 | for cache and Array for feature meant there was a lot of duplication in the methods. Enmap V6 clears out any method that could be achieved with 91 | another method. I have made every possible effort not to lose power in Enmap, so if you find that something I removed was stupid, please feel 92 | free to make the case for it on our Discord server. 93 | 94 | `enmap.keys()`, `enmap.values()` and `enmap.entries()` can be used to get only keys, only values, or *both*, in arrays. This will pull the *entire* 95 | database's worth of data, but that's what you were expecting, so it's fine, isn't it? As such, `enmap.array()` and `enmap.keyArray()` become 96 | obsolete and have been removed. 97 | 98 | `enmap.indexes` also isn't useful anymore and was the "uncached" version of `enmap.keys`, so it's removed. 99 | 100 | `enmap.count` and `enmap.size` have always been a bit confusing, especially since arrays have a `length`... so I've decided to just call it `enmap.length`. 101 | To maintain backwards compatibility, though, `enmap.size` will remain as an alias. 102 | 103 | `enmap.filter()` and `enmap.filterArray()` were always a bit confusing to me. The idea of "returning an Enmap" from Enmap itself was always weird and I 104 | will no longer be doing that - that means that `enmap.filter()` will not simply return an array, and that's it. Also, it returns an array of *values*, 105 | and does not concern itself with returning keys (same for any other method that only returns values in arrays). 106 | 107 | ## Obsolete things I've deleted 108 | 109 | These were already planned, and indicated as deprecated for a while now, but they've now been removed: 110 | 111 | - `enmap.equals()` (extremely expensive, not useful) 112 | - `enmap.exists()` (use `has(key, prop)`) 113 | - `enmap.setProp()` (use `set(key, prop)`) 114 | - `enmap.pushIn()` (use `push(key, prop)`) 115 | - `enmap.getProp()` (use `get(key, prop)`) 116 | - `enmap.deleteProp()` (use `delete(key, prop)`) 117 | - `enmap.removeProp()` (use `remove(key, prop)`) 118 | - `enmap.hasProp()` (use `has(key, prop)`) 119 | - `enmap.ready` or `enmap.defer` and all that jazz - completely useless in this sync world. 120 | 121 | ## Misc changes 122 | 123 | - The use of `'::memory::` as a name is removed, you can use `inMemory: true` instead. That means `new Enmap('::memory::')` is now `new Enmap({ inMemory: true })`. 124 | - In all loop methods like `every`, `some`, `map` and `filter`, the **value** now comes first, and **key** second. This matches array methods closer. 125 | - Methods will no longer return the enmap upon executing an action. It's always felt weird to me that some methods returned the enmap and others returned data. 126 | - The `destroy` method is removed, since it doesn't make much sense to delete all the db tables. You can still delete all your stuff with `clear()` though. 127 | - `wal` and `verbose` options have been removed from options, I honestly prefer the default to `journal_mode=WAL`. If you don't like it, run 128 | `enmap.db.pragma('journal_mode=DELETE')` (you can run direct DB calls to sqlite3 this way). For verbose, pass it as `options.sqliteOptions`, like, 129 | `new Enmap({ name: 'blah', sqliteOptions: { verbose: true }})`. 130 | -------------------------------------------------------------------------------- /docs/retype.manifest: -------------------------------------------------------------------------------- 1 | { 2 | "version": "3.5.0", 3 | "created": "2024-04-08T05:27:41Z", 4 | "files": [ 5 | { 6 | "path": ".nojekyll" 7 | }, 8 | { 9 | "path": "404.html" 10 | }, 11 | { 12 | "path": "api\\index.html" 13 | }, 14 | { 15 | "path": "assets\\enmap-logo.svg" 16 | }, 17 | { 18 | "path": "assets\\favicon.ico" 19 | }, 20 | { 21 | "path": "assets\\favicon.png" 22 | }, 23 | { 24 | "path": "blog-posts\\enmaps-history\\index.html" 25 | }, 26 | { 27 | "path": "blog-posts\\index.html" 28 | }, 29 | { 30 | "path": "blog-posts\\josh\\index.html" 31 | }, 32 | { 33 | "path": "blog-posts\\why-remove-cache\\index.html" 34 | }, 35 | { 36 | "path": "blog-posts\\why-sqlite-only\\index.html" 37 | }, 38 | { 39 | "path": "CNAME" 40 | }, 41 | { 42 | "path": "help\\cjstoesm\\index.html" 43 | }, 44 | { 45 | "path": "index.html" 46 | }, 47 | { 48 | "path": "install\\index.html" 49 | }, 50 | { 51 | "path": "install\\troubleshooting-guide\\index.html" 52 | }, 53 | { 54 | "path": "install\\upgrade\\index.html" 55 | }, 56 | { 57 | "path": "install\\upgradev6\\index.html" 58 | }, 59 | { 60 | "path": "resources\\css\\retype.css" 61 | }, 62 | { 63 | "path": "resources\\fonts\\Inter-italic-latin-var.woff2" 64 | }, 65 | { 66 | "path": "resources\\fonts\\Inter-roman-latin-var.woff2" 67 | }, 68 | { 69 | "path": "resources\\js\\config.js" 70 | }, 71 | { 72 | "path": "resources\\js\\lunr.js" 73 | }, 74 | { 75 | "path": "resources\\js\\prism.js" 76 | }, 77 | { 78 | "path": "resources\\js\\retype.js" 79 | }, 80 | { 81 | "path": "resources\\js\\search.json" 82 | }, 83 | { 84 | "path": "resources\\lunr.js.LICENSE.txt" 85 | }, 86 | { 87 | "path": "resources\\mermaid.js.LICENSE.txt" 88 | }, 89 | { 90 | "path": "resources\\prism.js.LICENSE.txt" 91 | }, 92 | { 93 | "path": "resources\\retype.js.LICENSE.txt" 94 | }, 95 | { 96 | "path": "resources\\retype.LICENSE.txt" 97 | }, 98 | { 99 | "path": "robots.txt" 100 | }, 101 | { 102 | "path": "sitemap.xml.gz" 103 | }, 104 | { 105 | "path": "sponsors\\index.html" 106 | }, 107 | { 108 | "path": "usage\\arrays\\index.html" 109 | }, 110 | { 111 | "path": "usage\\basic\\index.html" 112 | }, 113 | { 114 | "path": "usage\\index.html" 115 | }, 116 | { 117 | "path": "usage\\math\\index.html" 118 | }, 119 | { 120 | "path": "usage\\objects\\index.html" 121 | }, 122 | { 123 | "path": "usage\\paths\\index.html" 124 | }, 125 | { 126 | "path": "usage\\serialize\\index.html" 127 | }, 128 | { 129 | "path": "usage\\using-enmap.multi\\index.html" 130 | }, 131 | { 132 | "path": "usage\\using-from-multiple-files\\index.html" 133 | } 134 | ] 135 | } -------------------------------------------------------------------------------- /docs/retype.yml: -------------------------------------------------------------------------------- 1 | input: . 2 | output: .retype 3 | url: enmap.alterion.dev 4 | branding: 5 | logo: assets/enmap-logo.svg 6 | favicon: assets/favicon.png 7 | links: 8 | - text: Github 9 | link: https://github.com/eslachance/enmap 10 | - text: NPM 11 | link: https://npmjs.com/package/enmap 12 | - text: Discord 13 | link: https://discord.gg/N7ZKH3P 14 | edit: 15 | repo: "http://github.com/eslachance/enmap/edit/" 16 | footer: 17 | copyright: "© Copyright {{ year }}. All rights reserved." 18 | -------------------------------------------------------------------------------- /docs/sponsors.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'The people that help make Enmap, and my other projects, possible!' 3 | --- 4 | 5 | # Supporters and Partners 6 | 7 | ## Enmap Partners 8 | 9 | ### An Idiot's Guide 10 | 11 | My home for many years, way before Enmap was even a thought, AIG has been the place where I can be myself. That is, be sometimes of an asshole and sometimes helpful to beginners wanting to learn discord.js. Sometimes both at the same time! 12 | 13 | AIG helps users with discord.js support for version 11 and 12, has a channel for Enmap, and is a community of builders. Notably, we're the authors of the famous [An Idiot's Guide to Discord.js](https://anidiots.guide/) \(if that wasn't obvious\), where I'm the main author but there have been many contributions over the year. For discord.js support, head on over to [https://anidiots.guide/](https://anidiots.guide/) for the guide itself and [https://discord.gg/vXVxsAjSMF](https://discord.gg/vXVxsAjSMF) to join us on Discord. 14 | 15 | ## Enmap Supporters 16 | 17 | ### AndrewJWin 18 | 19 | The first supporter on GitHub and Patreon, working towards a degree in IT and an occasional idiot on An Idiots Guide. 20 | 21 | He's Andrew\(\)\#9999 on Discord, and always willing to tell you if your nose looks weird. 22 | -------------------------------------------------------------------------------- /docs/usage/README.md: -------------------------------------------------------------------------------- 1 | # Usage Documentation 2 | 3 | Mostly, this documentation will be concentrating on the "persistent" version of enmap - the one where data is saved automatically. 4 | 5 | If you don't want persistence, the only difference is how you initialize the enmap: 6 | 7 | ```javascript 8 | const Enmap = require("enmap"); 9 | const myEnmap = new Enmap(); 10 | 11 | // you can now use your enmap directly 12 | ``` 13 | 14 | ### Persistent Enmaps 15 | 16 | By default, Enmap saves only in memory and does not save anything to disk. To have persistent storage, you need to add some options. Enmaps with a "name" option will save, and there are additional options you can use to fine-tune the saving and loading features. 17 | 18 | ```javascript 19 | const Enmap = require("enmap"); 20 | 21 | // Normal enmap with default options 22 | const myEnmap = new Enmap({name: "points"}); 23 | 24 | // non-cached, auto-fetch enmap: 25 | const otherEnmap = new Enmap({ 26 | name: "settings", 27 | autoFetch: true, 28 | fetchAll: false 29 | }); 30 | ``` 31 | 32 | ### Enmap Options 33 | 34 | The following is a list of all options that are available in Enmap, when initializing it: 35 | 36 | * `name`: A name for the enmap. Defines the table name in SQLite \(the name is "cleansed" before use\). 37 | * If an enmap has a name, **it is considered persistent** and will require `better-sqlite-pool` to run. 38 | * 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\). 39 | * `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. 40 | * `autoFetch`: Defaults to `true`. When enabled, will automatically fetch any key that's requested using get, getProp, etc. This is a "synchronous" operation, which means it doesn't need any of this promise or callback use. 41 | * `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! 42 | * `cloneLevel`: Defaults to `deep`. Determines how objects and arrays are treated when inserting and retrieving from the database. 43 | * `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!**. 44 | * `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 behavior, but is not recommended unless you know what you're doing. 45 | * `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.** 46 | * `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. 47 | * `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. 48 | 49 | -------------------------------------------------------------------------------- /docs/usage/arrays.md: -------------------------------------------------------------------------------- 1 | # Array Methods 2 | 3 | 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! 4 | 5 | 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. 6 | 7 | Let's take a look at three example entries in Enmap that we can use. The first is a direct array, the second is an array inside an object, the last is an array of objects. 8 | 9 | ```javascript 10 | myEnmap.set("simpleArray", [1,2,3,4,5]); 11 | 12 | myEnmap.set("arrInObj", { 13 | name: "Bob", 14 | aliases: ["Bobby", "Robert"] 15 | }); 16 | 17 | 18 | myEnmap.set('objectarray', [{ a: 1, b: 2, c: 3 }, { d: 4, e: 5, f: 6 }]); 19 | ``` 20 | 21 | ## Adding to the array 22 | 23 | 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. 24 | 25 | ```javascript 26 | myEnmap.push("simpleArray", 6); 27 | // now [1,2,3,4,5,6] 28 | 29 | myEnmap.push("arrInObj", "Robby", "aliases"); 30 | // now ["Bobby", "Robert", "Robby"] 31 | ``` 32 | 33 | > The third parameter in push is the "path" to the array in an object. It works the same as the properties path used in [Working With Objects](objects.md). 34 | 35 | ## Removing from the array 36 | 37 | Similarly, you can remove from an array. With the normal _path_ system, you can either remove via the index in the array, or remove simple strings. To remove a complex object, you'll need to use a function in the remove method. 38 | 39 | ```javascript 40 | myEnmap.remove("simpleArray", 2); 41 | // now [1,3,4,5,6] 42 | 43 | myEnmap.remove("arrInObject", "Bobby", "aliases"); 44 | // now ["Robert", "Robby"] 45 | 46 | myEnmap.remove('objectarray', (value) => value.e === 5); 47 | // value is now [{ a: 1, b: 2, c: 3 }] 48 | ``` 49 | 50 | -------------------------------------------------------------------------------- /docs/usage/basic.md: -------------------------------------------------------------------------------- 1 | # Basic Data Use 2 | 3 | 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. 4 | 5 | > The code samples on this page assume that you have correctly initialized `myEnmap` 6 | 7 | ## Writing Data 8 | 9 | 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. 10 | 11 | Enmap supports pretty much all _native_ JavaScript data types. However, it **cannot** support Class Instances directly. That means if you have, say, a "User" object or a "House" class, they cannot be stored here. 12 | 13 | There is however a workaround, which is to use [Serializing and Deserializing](serialize.md). 14 | 15 | > Objects and Arrays are a little more complex to deal with, so they have their own page. See [Working with Objects](objects.md) for more information. 16 | 17 | The usage for the `set()` method is simple: 18 | 19 | ```text 20 | .set(key, value); 21 | ``` 22 | 23 | * `key` must be a string or integer. A key should be unique, otherwise it will be overwritten by new values using the same key. 24 | * `value` must be a supported native data type as mentioned above. 25 | 26 | Here are a few examples of writing simple data values: 27 | 28 | ```javascript 29 | myEnmap.set('boolean', true); 30 | myEnmap.set('integer', 42); 31 | myEnmap.set('someFloat', 73.2345871); 32 | myEnmap.set("Test2", "test2"); 33 | ``` 34 | 35 | ## Retrieving Data 36 | 37 | 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! 38 | 39 | ```javascript 40 | const floatValue = myEnmap.get('someFloat'); 41 | const test = myEnmap.get('Test2'); 42 | 43 | // you can even use booleans in conditions: 44 | if(myEnmap.get('boolean')) { 45 | // boolean is true! 46 | } 47 | ``` 48 | 49 | That's pretty much it for only retrieving a single data value. There are more complex operations that are available, take a look at [Array Methods](arrays.md) for the more advanced things you can do on Enmap's data! 50 | 51 | ### Deleting Data 52 | 53 | Removing data from Enmap is as simple as saving or retrieving. You can easily use the delete\(\) method as such: 54 | 55 | ```javascript 56 | myEnmap.delete("integer"); 57 | 58 | myEnmap.delete("boolean"); 59 | ``` 60 | 61 | -------------------------------------------------------------------------------- /docs/usage/math.md: -------------------------------------------------------------------------------- 1 | # Mathematical Methods 2 | 3 | {% hint style="warning" %} 4 | This page is a work in progress and may not have the polish of a usual Evie-Written document! 5 | {% endhint %} 6 | 7 | Some quick docs: 8 | 9 | ### enmap.math\(key, operation, operator, \[objectPath\]\) 10 | 11 | ```javascript 12 | // Assuming 13 | points.set("number", 42); 14 | points.set("numberInObject", {sub: { anInt: 5 }}); 15 | 16 | points.math("number", "/", 2); // 21 17 | points.math("number", "add", 5); // 26 18 | points.math("number", "modulo", 3); // 2 19 | points.math("numberInObject", "+", 10, "sub.anInt"); 20 | ``` 21 | 22 | Possible Operators \(accepts all variations listed below, as strings\): 23 | 24 | * `+`, `add`, `addition`: Increments the value in the enmap by the provided value. 25 | * `-`, `sub`, `subtract`: Decrements the value in the enmap by the provided value. 26 | * `*`, `mult`, `multiply`: Multiply the value in the enmap by the provided value. 27 | * `/`, `div`, `divide`: Divide the value in the enmap by the provided value. 28 | * `%`, `mod`, `modulo`: Gets the modulo of the value in the enmap by the provided value. 29 | * `^`, `exp`, `exponential`: Raises the value in the enmap by the power of the provided value. 30 | 31 | ### enmap.inc\(key, \[objectPath\]\) 32 | 33 | ```javascript 34 | // Assuming 35 | points.set("number", 42); 36 | points.set("numberInObject", {sub: { anInt: 5 }}); 37 | 38 | points.inc("number"); // 43 39 | points.inc("numberInObject", "sub.anInt"); // {sub: { anInt: 6 }} 40 | ``` 41 | 42 | ### enmap.dec\(key. \[objectPath\]\) 43 | 44 | ```javascript 45 | // Assuming 46 | points.set("number", 42); 47 | points.set("numberInObject", {sub: { anInt: 5 }}); 48 | 49 | points.dec("number"); // 41 50 | points.dec("numberInObject", "sub.anInt"); // {sub: { anInt: 4 }} 51 | ``` 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /docs/usage/objects.md: -------------------------------------------------------------------------------- 1 | # Working with Objects 2 | 3 | Enmap is a great way to store structured data, and offers a few helper features that directly affect both objects and arrays. 4 | 5 | Let's assume for a moment that we want to store the following data structure in Enmap: 6 | 7 | ```javascript 8 | const myStructure = { 9 | first: "blah", 10 | second: "foo", 11 | changeme: "initial", 12 | isCool: false 13 | sub: { 14 | yay: true, 15 | thing: "amagig" 16 | } 17 | } 18 | ``` 19 | 20 | This structure has 5 "properties": `first`, `second`, `changeme`, `isCool`, `sub`. The `sub` property has 2 properties of its own, `yay` and `thing`. 21 | 22 | To store this structure in Enmap, you can use a variable, or just straight-up write the object: 23 | 24 | ```javascript 25 | myEnmap.set("someObject", myStructure); 26 | 27 | // Or directly the object 28 | myEnmap.set("someObject", {first: "blah", ...}); 29 | 30 | // Works with arrays, too! 31 | myEnmap.set("someArray", ["one", "two", "three"]); 32 | ``` 33 | 34 | > **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. 35 | 36 | ## Getting properties 37 | 38 | 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. 39 | 40 | The exact method is `.get(key, path)`. 41 | 42 | ```javascript 43 | const second = myEnmap.get("someObject", "second"); 44 | // returns "foo" 45 | 46 | const thing = myEnmap.get("someObject", "sub.yay"); 47 | // returns true 48 | 49 | // The path can be dynamic, too: 50 | const propToGet = "thing"; 51 | const blah = myEnmap.get("someObject", `sub.${propToGet}`); 52 | ``` 53 | 54 | ## Checking if a property exists 55 | 56 | 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: 57 | 58 | ```javascript 59 | myEnmap.has("someObject", "sub.thing"); // returns true 60 | 61 | myEnmap.has("someObject", "heck"); // returns false. 62 | ``` 63 | 64 | ## Modifying Properties 65 | 66 | 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: 67 | 68 | ```javascript 69 | // Set an object property 70 | myEnmap.set("someObject", "newThing", "sub.blah"); 71 | 72 | // Set an array property 73 | myEnmap.set("someArray", "four", 3); 74 | ``` 75 | 76 | As you can see, setProp\(\) and getProp\(\) work on the same concept that the path can be as complex as you want. 77 | 78 | Arrays have additional helper methods, [you can see them here](arrays.md). 79 | 80 | -------------------------------------------------------------------------------- /docs/usage/paths.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'What is Paths in Enmap, how to use them, what is their syntax?' 3 | --- 4 | 5 | # Understanding Paths 6 | 7 | In a whole lot of methods for Enmap, one of the properties is the "path". Paths are used in _Object_ data saved in Enmap, that is to say, setting or ensuring a value that is an object at the top level. 8 | 9 | To understand what a path really means, we can start by having an object as a value. Here I'm not even using Enmap, as the idea is related to basic JavaScript, not my module. 10 | 11 | ```javascript 12 | const myObject = { 13 | a: "foo", 14 | b: true, 15 | c: { 16 | but: "who", 17 | are: "you?", 18 | and: ["are you", "you?"], 19 | }, 20 | sub: { values: { are: { "cool" } } }, 21 | }; 22 | ``` 23 | 24 | So here we have an object that actually has multiple levels, that is to say, the `c` and `sub` properties have, as a value, another object with its own keys. `sub` takes this further with 4 different levels, just to fully demonstrate my point. 25 | 26 | So how would we reach the values in this object? Well, in core JavaScript, let's say we wanted to get the word "cool", we'd use `myObject.sub.values.are.cool`. This is one way to access object properties, the other one being `myObject["sub"]["values"]["are"]["cool"]` \(where those strings can be variables, btw, for dynamic property access\). 27 | 28 | Alright so what about the array, there? Well, arrays are accessed through their index, meaning their position in the array, starting at 0. That means to access the `c.and` values, you'd do something like `myObject.c.and[0]` . That looks like a strange syntax I'll admit, but considering you can use the same for objects, `myObject["c"]["and"][1]` perhaps looks a bit more coherent. 29 | 30 | ### Doing it in Enmap 31 | 32 | Now that you've seen how to access those properties in regular JavaScript, what about doing it in Enmap? Well, it's actually quite simple: the `path` parameter in the methods simply take exactly what you've seen above, with 2 exceptions: 33 | 34 | * The path doesn't include the object name \(which is your `key`\) 35 | * You don't need to use variables for dynamic paths since it's a string 36 | 37 | What does that mean in reality? Well let's rewrite the example above as Enmap code: 38 | 39 | ```javascript 40 | myEnmap.set("myObject", { 41 | a: "foo", 42 | b: true, 43 | c: { 44 | but: "who", 45 | are: "you?", 46 | and: ["are you", "you?"], 47 | }, 48 | sub: { values: { are: { "cool" } } }, 49 | }); 50 | ``` 51 | 52 | To access the "cool" string, the code then becomes `myEnmap.get("myObject", "sub.values.are")` . Accessing the array values looks the same: `myEnmap.get("myObject", "c.and[0]")` . In this case indexes can be used either way, so you can also do `myEnmap.get("myObject", "c.and.0")` and that'll work equally well. 53 | 54 | -------------------------------------------------------------------------------- /docs/usage/serialize.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Learn how to manipulate the data you save and retrieve from the database, to 4 | more easily store complex data without having to convert it to simple data 5 | everywhere you use it. 6 | --- 7 | 8 | # Serializing and Deserializing 9 | 10 | _**Introduced in Enmap 5.6**_, Serializers and Deserializers are functions that you use to manipulate the data before storing it in the database, or before using it after retrieving it. 11 | 12 | This feature is born from a limitation in Enmap: it cannot store very complex objects, such as the instance of a class, objects with circular references, functions, etc. So, typically when you have such data, you need to manually convert it to some simple representation before storing, and then do the inverse after getting it from enmap. This is a more automated way of doing it. 13 | 14 | ### What are they? 15 | 16 | The Serializer function runs every single time data is stored in the enmap, if one is provided. This function receives the data provided to set\(\) as an input, and must return a value to be stored in the database. This function _MUST_ be synchronous, that is to say, cannot be an async function or return a promise. 17 | 18 | ```javascript 19 | // the default serializer 20 | const serializer = (data, key) => { 21 | return data; 22 | }; 23 | ``` 24 | 25 | The Deserializer function is the reverse, and runs on each value pulled from the database, before it is returned through the get\(\) method. This function receives the data stored in the database and returns the value that you want to use directly. This function _MUST_ be synchronous, that is to say, cannot be an async function or return a promise. 26 | 27 | ```javascript 28 | // the default deserializer 29 | const deserializer = (data, key) => { 30 | return data; 31 | }; 32 | ``` 33 | 34 | ### Examples 35 | 36 | #### Guild Settings: A more sensible example 37 | 38 | Taking a hit from my own example of Per-Server Settings, this is a better example that doesn't require storing just the name of a channel, but straight-up the channel itself. 39 | 40 | ```javascript 41 | // Imagine the client and stuff is already defined. 42 | 43 | 44 | // The function that runs when storing data 45 | const serializeData: data => { 46 | return { 47 | ...data, 48 | // stores the guild as ID 49 | guild: guild.id, 50 | // stores the user as ID 51 | user: user.id, 52 | } 53 | }; 54 | 55 | // This one runs when loading. 56 | const deserializeData: data => { 57 | return { 58 | ...data, 59 | // gets the guild itself from the cache from its ID 60 | guild: client.guilds.cache.get(data.guild), 61 | // Same with the user! 62 | user: client.users.cache.get(data.user), 63 | } 64 | }; 65 | 66 | // Default Settings can no longer store defaults for roles and channels. 67 | const defaultSettings = { 68 | prefix: "!", 69 | modLogChannel: null, 70 | modRole: null, 71 | adminRole: null, 72 | welcomeChannel: null, 73 | welcomeMessage: "Say hello to {{user}}, everyone!" 74 | } 75 | 76 | // Our enmap has shiny new options here! 77 | client.settings = new Enmap({ 78 | name: "settings", 79 | cloneLevel: 'deep', 80 | serializer: serializeData, 81 | deserializer: deserializeData, 82 | // Might as well autoensure, eh? 83 | autoEnsure: defaultSettings, 84 | }); 85 | 86 | 87 | // Store some data, obviously needs to be run in the right place: 88 | client.settings.set(message.guild.id, 89 | message.mentions.channels.first(), 90 | 'welcomeChannel' 91 | ); 92 | 93 | client.settings.set(message.guild.id, 94 | message.mentions.roles.first(), 95 | 'adminRole' 96 | ); 97 | 98 | // GET the data after 99 | const welcomeChannel = client.settings.get(message.guild.id, 'welcomeChannel'); 100 | welcomeChannel.send("This works without having to find or get the channel!"); 101 | ``` 102 | 103 | -------------------------------------------------------------------------------- /docs/usage/using-enmap.multi.md: -------------------------------------------------------------------------------- 1 | # Using Enmap.multi 2 | 3 | To account for people that might use a large number of enmaps in the same project, I've created a new \`multi\(\)\` method that can be used to instanciate multiple peristent enmaps together. 4 | 5 | The method takes 3 arguments: 6 | 7 | * An `array` of names for the enmaps to be created. 8 | * A Provider \(not instanciated\), from any of the available ones. 9 | * An `options` object containing any of the options needed to instanciate the provider. Do not add `name` to this, as it will use the names in the array instead. 10 | 11 | Below, an example that uses destructuring: 12 | 13 | ```javascript 14 | const Enmap = require('enmap'); 15 | const Provider = require('enmap-mongo'); 16 | const { settings, tags, blacklist, langs } = 17 | Enmap.multi(['settings', 'tags', 'blacklist', 'langs'], 18 | Provider, { url: "mongodb://localhost:27017/enmap" }); 19 | ``` 20 | 21 | -------------------------------------------------------------------------------- /docs/usage/using-from-multiple-files.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | This page will describe how to use Enmap from multiple files within your same 4 | project. Note that I mean the same app, process, or shard, but different files 5 | within this one running process. 6 | --- 7 | 8 | # Using from multiple files 9 | 10 | ## A common issue 11 | 12 | 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: 13 | 14 | > 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? 15 | 16 | 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 _[_instance _](https://js.evie.dev/classes)_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. 17 | 18 | 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? 19 | 20 | ## The Shared Variable Method 21 | 22 | Admittedly, 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: 23 | 24 | ```javascript 25 | const Discord = require("discord.js"); 26 | const client = new Discord.Client(); 27 | 28 | const Enmap = require("enmap"); 29 | 30 | // this is the important bit 31 | client.settings = new Enmap({ name: "settings" }); 32 | client.tags = new Enmap({ name: "tags" }); 33 | 34 | // your normal events here 35 | client.on("message", message => { 36 | const guildSettings = client.settings.get(message.guild.id); 37 | // works here 38 | }); 39 | 40 | client.login(token); 41 | ``` 42 | 43 | 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. 44 | 45 | {% hint style="danger" %} 46 | Important Note: Do NOT override Discord.js' existing collections! That means, client.users, client.guilds, etc. [See all the properties and methods for the Discord.js client](https://discord.js.org/#/docs/main/stable/class/Client) - none of these should be overridden. 47 | {% endhint %} 48 | 49 | 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. 50 | 51 | ## The Module Method 52 | 53 | All things considered, [modules ](https://js.evie.dev/modules)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. 54 | 55 | As covered in [My JavaScript Guide](https://js.evie.dev/modules), modules are fairly straightforward. This is how I have done an Enmap shared module before: 56 | 57 | ```javascript 58 | const Enmap = require("enmap"); 59 | 60 | module.exports = { 61 | settings: new Enmap({ 62 | name: "settings", 63 | autoFetch: true, 64 | fetchAll: false 65 | }), 66 | users: new Enmap("users"), 67 | tags: new Emmap({ name : "tags" }) 68 | } 69 | ``` 70 | 71 | 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: 72 | 73 | ```javascript 74 | const db = require("./db.js"); 75 | 76 | console.log(db.settings.size); 77 | db.tags.set("blah", { 78 | guild: "1234", 79 | author: "4231", 80 | name: "blah", 81 | content: "I'm bored, mommy!" 82 | }); 83 | ``` 84 | 85 | 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: 86 | 87 | ```javascript 88 | const guildTags = db.tags.find(tag => tag.guild === message.guild.id); 89 | ``` 90 | 91 | 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: 92 | 93 | ```javascript 94 | const Enmap = require("enmap"); 95 | 96 | module.exports = { 97 | settings: new Enmap({ 98 | name: "settings", 99 | autoFetch: true, 100 | fetchAll: false 101 | }), 102 | users: new Enmap("users"), 103 | tags: new Emmap({ name : "tags" }), 104 | getTags: (guild) => { 105 | return this.tags.find(tag => tag.guild === message.guild.id); 106 | } 107 | } 108 | ``` 109 | 110 | And there you have it! There are other ways to build the exports, you can also split it differently, take a look at [My Modules Guide ](https://js.evie.dev/modules)for more information. 111 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "enmap", 3 | "version": "6.0.5", 4 | "description": "A simple database wrapper to make sqlite database interactions much easier for beginners, with additional array helper methods.", 5 | "main": "src/index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "vitest run", 9 | "build-api-docs": "node ./scripts/build-docs.js", 10 | "build-docs": "cd docs && npx retype build" 11 | }, 12 | "files": [ 13 | "src", 14 | "typings" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/eslachance/enmap.git" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/eslachance/enmap/issues" 22 | }, 23 | "homepage": "https://enmap.alterion.dev/", 24 | "keywords": [ 25 | "sqlite", 26 | "sql", 27 | "sqlite3", 28 | "better-sqlite3", 29 | "db", 30 | "database", 31 | "caching", 32 | "storing", 33 | "easy", 34 | "quick", 35 | "simple", 36 | "wrapper" 37 | ], 38 | "author": "Evelyne Lachance (https://alterion.dev/)", 39 | "license": "Apache-2.0", 40 | "dependencies": { 41 | "better-serialize": "^1.0.0", 42 | "better-sqlite3": "^11.10.0", 43 | "lodash-es": "^4.17.21", 44 | "on-change": "^5.0.1" 45 | }, 46 | "devDependencies": { 47 | "@vitest/coverage-v8": "^1.6.0", 48 | "eslint": "^8.41.0", 49 | "eslint-config-prettier": "^8.8.0", 50 | "eslint-plugin-node": "^11.1.0", 51 | "jsdoc-to-markdown": "^8.0.0", 52 | "limax": "^4.0.0", 53 | "retypeapp": "^3.5.0", 54 | "vitest": "^1.6.0" 55 | }, 56 | "engines": { 57 | "node": ">=16.0.0" 58 | }, 59 | "types": "./typings/index.d.ts" 60 | } 61 | -------------------------------------------------------------------------------- /scripts/build-docs.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-useless-escape */ 2 | import jsdoc2md from 'jsdoc-to-markdown'; 3 | import { writeFile } from 'node:fs/promises'; 4 | import slug from 'limax'; 5 | 6 | const htmlEntities = { 7 | nbsp: ' ', 8 | cent: '¢', 9 | pound: '£', 10 | yen: '¥', 11 | euro: '€', 12 | copy: '©', 13 | reg: '®', 14 | lt: '<', 15 | gt: '>', 16 | quot: '"', 17 | amp: '&', 18 | apos: '\'', 19 | }; 20 | 21 | const unescapeHTML = str => str.replace(/\&([^;]+);/g, (entity, entityCode) => { 22 | let match; 23 | 24 | if (entityCode in htmlEntities) { 25 | return htmlEntities[entityCode]; 26 | /* eslint no-cond-assign: 0 */ 27 | } else if (match = entityCode.match(/^#x([\da-fA-F]+)$/)) { 28 | return String.fromCharCode(parseInt(match[1], 16)); 29 | /* eslint no-cond-assign: 0 */ 30 | } else if (match = entityCode.match(/^#(\d+)$/)) { 31 | return String.fromCharCode(~~match[1]); 32 | } else { 33 | return entity; 34 | } 35 | }); 36 | 37 | const apiDocsHeader = `--- 38 | description: >- 39 | The complete and unadultered API documentation for every single method and 40 | property accessible in Enmap. 41 | --- 42 | 43 | # Full Documentation 44 | 45 | The following is the complete list of methods available in Enmap. As it is auto-generated from the source code and its comments, it's a little more "raw" than the Usage docs. However, it has the benefit of being more complete and usually more up to date than the manually written docs. 46 | 47 | {% hint style="warning" %} 48 | If you're doing a PR on the docs github, please do not manually edit the below contents, as it will be overwritten. Check the src/index.js source code and change the comments there instead! 49 | {% endhint %} 50 | 51 | 52 | `; 53 | 54 | const finalize = str => str 55 | .replace(/\[Enmap\]\(#Enmap\)/gi, 'Enmap') 56 | .replace(/\[Enmap<\/code>\]\(#Enmap\)/gi, 'Enmap') 57 | .replace('* [new Enmap(iterable, [options])](#new_Enmap_new)', '* [new Enmap(iterable, [options])](#new-enmap-iterable-options)') 58 | .split('\n\n')[2].split('')[0]; 59 | 60 | const regexread = /^ {8}\* \[\.(.*?)\]\((.*?)\)(.*?)(\(#.*?\)|)$/gm; 61 | 62 | const parseData = data => finalize(data.replace(regexread, (_, b, __, d) => 63 | ` * [.${b}](#${slug(`enmap.${b} ${unescapeHTML(d.replace(/<\/?code>/g, ''))}`)})${d}`)); 64 | 65 | 66 | const rendered = await jsdoc2md.render({ files: './src/index.js' }); 67 | await writeFile('./docs/api.md', apiDocsHeader + parseData(rendered)); 68 | 69 | -------------------------------------------------------------------------------- /src/error.js: -------------------------------------------------------------------------------- 1 | class CustomError extends Error { 2 | constructor(message, name = null) { 3 | super(); 4 | Error.captureStackTrace(this, this.constructor); 5 | this.name = name || 'EnmapError'; 6 | this.message = message; 7 | } 8 | } 9 | 10 | export default CustomError; 11 | -------------------------------------------------------------------------------- /test/error.spec.js: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import CustomError from '../src/error'; 3 | 4 | describe('CustomError', () => { 5 | test('should create an instance of CustomError', () => { 6 | const error = new CustomError('An error occurred'); 7 | expect(error).toBeInstanceOf(CustomError); 8 | }); 9 | 10 | test('should have a default name of "EnmapError"', () => { 11 | const error = new CustomError('An error occurred'); 12 | expect(error.name).toBe('EnmapError'); 13 | }); 14 | 15 | test('should use the provided name if given', () => { 16 | const error = new CustomError('An error occurred', 'CustomErrorName'); 17 | expect(error.name).toBe('CustomErrorName'); 18 | }); 19 | 20 | test('should have the correct message', () => { 21 | const message = 'An error occurred'; 22 | const error = new CustomError(message); 23 | expect(error.message).toBe(message); 24 | }); 25 | 26 | test('should capture the stack trace', () => { 27 | const error = new CustomError('An error occurred'); 28 | expect(error.stack).toContain('EnmapError'); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test/index.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | describe, 3 | test, 4 | expect, 5 | vi, 6 | } from 'vitest'; 7 | import { parse, stringify } from 'better-serialize'; 8 | import Enmap from '../src'; 9 | import { mkdir, rm } from 'fs/promises'; 10 | import CustomError from '../src/error'; 11 | 12 | describe('Enmap', () => { 13 | process.setMaxListeners(100); 14 | 15 | describe('can instantiate', () => { 16 | test('should create an Enmap', () => { 17 | const enmap = new Enmap({ inMemory: true }); 18 | 19 | expect(enmap).toBeInstanceOf(Enmap); 20 | }); 21 | 22 | test('should create an Enmap w/ warning', () => { 23 | const spy = vi.spyOn(console, 'warn'); 24 | 25 | const enmap = new Enmap({ name: '::memory::' }); 26 | 27 | expect(enmap).toBeInstanceOf(Enmap); 28 | expect(spy).toHaveBeenCalledTimes(1); 29 | }); 30 | 31 | test('should create an Enmap w/ custom serializing', () => { 32 | const enmap = new Enmap({ 33 | inMemory: true, 34 | serializer: JSON.stringify, 35 | deserializer: JSON.parse, 36 | }); 37 | 38 | expect(enmap).toBeInstanceOf(Enmap); 39 | }); 40 | 41 | test('should close database on exit', () => { 42 | let callback; 43 | process.on = (event, cb) => { 44 | if (event === 'exit') { 45 | callback = cb; 46 | } 47 | }; 48 | 49 | const enmap = new Enmap({ inMemory: true }); 50 | 51 | callback(); 52 | 53 | expect(enmap.db.open).toBe(false); 54 | }); 55 | 56 | test('should create a persistent Enmap w/ dir', async () => { 57 | await mkdir('./tmp').catch(() => {}); 58 | 59 | const enmap = new Enmap({ name: 'test', dataDir: './tmp' }); 60 | 61 | expect(enmap).toBeInstanceOf(Enmap); 62 | }); 63 | 64 | test('should load a persistent Enmap w/ dir', () => { 65 | const enmap = new Enmap({ name: 'test', dataDir: './tmp' }); 66 | 67 | expect(enmap).toBeInstanceOf(Enmap); 68 | }); 69 | 70 | test('should fail to create a persistent Enmap w/o dir', async () => { 71 | expect( 72 | () => new Enmap({ name: 'test', dataDir: './data-not-found' }), 73 | ).toThrow(TypeError); 74 | }); 75 | 76 | test('should create/use data dir', async () => { 77 | await rm('./data', { recursive: true }).catch(() => {}); 78 | 79 | const enmap = new Enmap({ name: 'test' }); 80 | const enmap2 = new Enmap({ name: 'test' }); 81 | 82 | expect(enmap).toBeInstanceOf(Enmap); 83 | expect(enmap2).toBeInstanceOf(Enmap); 84 | }); 85 | }); 86 | 87 | describe('should manipulate data', () => { 88 | describe('set', () => { 89 | const enmap = new Enmap({ inMemory: true }); 90 | 91 | test('should set a value w/ string', () => { 92 | enmap.set('setString', 'value'); 93 | 94 | expect(enmap.get('setString')).toBe('value'); 95 | }); 96 | 97 | test('should set a value w/ object', () => { 98 | enmap.set('setObject', { value: 'value' }); 99 | 100 | expect(enmap.get('setObject')).toEqual({ value: 'value' }); 101 | }); 102 | 103 | test('should set a value w/ array', () => { 104 | enmap.set('setArray', ['value']); 105 | 106 | expect(enmap.get('setArray')).toEqual(['value']); 107 | }); 108 | 109 | test('should set a value w/ number', () => { 110 | enmap.set('setNumber', 1); 111 | 112 | expect(enmap.get('setNumber')).toBe(1); 113 | }); 114 | 115 | test('should set a value w/ null', () => { 116 | enmap.set('setNull', null); 117 | 118 | expect(enmap.get('setNull')).toBe(null); 119 | }); 120 | 121 | test('should set a value w/ boolean', () => { 122 | enmap.set('setBool', true); 123 | 124 | expect(enmap.get('setBool')).toBe(true); 125 | }); 126 | 127 | test('should set a value w/ BigInt', () => { 128 | enmap.set('setBigInt', BigInt(1)); 129 | 130 | expect(enmap.get('setBigInt')).toBe(BigInt(1)); 131 | }); 132 | 133 | test('should set a value w/ path', () => { 134 | enmap.set('setPath', 'value', 'sub'); 135 | 136 | expect(enmap.get('setPath', 'sub')).toBe('value'); 137 | }); 138 | 139 | test('should fail to set a value w/ invalid key', () => { 140 | expect(() => enmap.set([], {}, () => {})).toThrow( 141 | `Invalid key for enmap - keys must be a string.`, 142 | ); 143 | // I don't know what happened that made me think this wasn't valid... 144 | enmap.set('$', 'Dollar signs are accepted'); 145 | expect(enmap.get('$')).toBe('Dollar signs are accepted'); 146 | }); 147 | 148 | test('should call callback after set', () => { 149 | const mock = vi.fn(); 150 | enmap.changed(mock); 151 | enmap.set('setCallback', 'value', 'sub'); 152 | expect(mock).toHaveBeenCalledTimes(1); 153 | expect(enmap.get('setCallback', 'sub')).toBe('value'); 154 | }); 155 | }); 156 | 157 | describe('update', () => { 158 | const enmap = new Enmap({ inMemory: true }); 159 | test('should update a value w/ object', () => { 160 | enmap.set('updateObj', { value: 'value' }); 161 | 162 | enmap.update('updateObj', { value: 'new' }); 163 | 164 | expect(enmap.get('updateObj')).toEqual({ value: 'new' }); 165 | }); 166 | 167 | test('should update a value w/ function', () => { 168 | enmap.set('updateFunc', { value: 1 }); 169 | 170 | enmap.update('updateFunc', (val) => { 171 | return { value: val.value + 1 }; 172 | }); 173 | 174 | expect(enmap.get('updateFunc')).toEqual({ value: 2 }); 175 | }); 176 | }); 177 | 178 | describe('get', () => { 179 | const enmap = new Enmap({ inMemory: true }); 180 | const defaultEnmap = new Enmap({ 181 | inMemory: true, 182 | autoEnsure: { hello: 'world' }, 183 | }); 184 | 185 | test('should get a value', () => { 186 | enmap.set('get', 'value'); 187 | 188 | expect(enmap.get('get')).toBe('value'); 189 | }); 190 | 191 | test('should get a value w/ path', () => { 192 | enmap.set('getPath', 'value', 'sub'); 193 | 194 | expect(enmap.get('getPath', 'sub')).toBe('value'); 195 | }); 196 | 197 | test('should get a value w/ default', () => { 198 | expect(defaultEnmap.get('unknown')).toEqual({ hello: 'world' }); 199 | }); 200 | 201 | test('should get a value w/ default', () => { 202 | expect(defaultEnmap.get('unknown', 'hello')).toBe('world'); 203 | }); 204 | }); 205 | 206 | describe('observe', () => { 207 | const enmap = new Enmap({ inMemory: true }); 208 | test('should observe a value', () => { 209 | enmap.set('observe', { value: 'value' }); 210 | const observer = enmap.observe('observe'); 211 | 212 | expect(observer).toEqual({ value: 'value' }); 213 | 214 | observer.value = 'new'; 215 | 216 | expect(enmap.get('observe')).toEqual({ value: 'new' }); 217 | expect(observer).toEqual({ value: 'new' }); 218 | }); 219 | }); 220 | 221 | describe('size', () => { 222 | const enmap = new Enmap({ inMemory: true }); 223 | test('should get size', () => { 224 | enmap.set('size', 'value'); 225 | 226 | expect(enmap.size).toBe(1); 227 | expect(enmap.count).toBe(1); 228 | expect(enmap.length).toBe(1); 229 | }); 230 | }); 231 | 232 | describe('keys', () => { 233 | const enmap = new Enmap({ inMemory: true }); 234 | 235 | test('should get keys', () => { 236 | enmap.set('keys', 'value'); 237 | expect(enmap.keys()).toEqual(['keys']); 238 | expect(enmap.indexes()).toEqual(['keys']); 239 | }); 240 | }); 241 | 242 | describe('values', () => { 243 | const enmap = new Enmap({ inMemory: true }); 244 | 245 | test('should get values', () => { 246 | enmap.set('values', 'value'); 247 | expect(enmap.values()).toEqual(['value']); 248 | }); 249 | }); 250 | 251 | describe('entries', () => { 252 | const enmap = new Enmap({ inMemory: true }); 253 | 254 | test('should get entries', () => { 255 | enmap.set('entries', 'value'); 256 | expect(enmap.entries()).toEqual([['entries', 'value']]); 257 | }); 258 | }); 259 | 260 | describe('autonum', () => { 261 | const enmap = new Enmap({ inMemory: true }); 262 | 263 | test('should autonum', () => { 264 | expect(enmap.autonum).toBe('1'); 265 | expect(enmap.autonum).toBe('2'); 266 | }); 267 | }); 268 | 269 | describe('push', () => { 270 | const enmap = new Enmap({ inMemory: true }); 271 | 272 | test('should push value', () => { 273 | enmap.set('push', []); 274 | enmap.push('push', 'value'); 275 | 276 | expect(enmap.get('push')).toEqual(['value']); 277 | }); 278 | 279 | test('should not push duplicate value', () => { 280 | enmap.set('pushDup', ['value']); 281 | enmap.push('pushDup', 'value'); 282 | 283 | expect(enmap.get('pushDup')).toEqual(['value']); 284 | }); 285 | 286 | test('should push duplicate value', () => { 287 | enmap.set('pushDup2', ['value']); 288 | enmap.push('pushDup2', 'value', null, true); 289 | 290 | expect(enmap.get('pushDup2')).toEqual(['value', 'value']); 291 | }); 292 | 293 | test('should fail to push value w/ path to string', () => { 294 | enmap.set('pushObjStr', { sub: '' }); 295 | expect(() => enmap.push('pushObjStr', 'value', 'sub')).toThrow( 296 | new CustomError('Key does not point to an array', 'EnmapPathError'), 297 | ); 298 | }); 299 | 300 | test('should push value w/ path', () => { 301 | enmap.set('pushObj', { sub: [] }); 302 | enmap.push('pushObj', 'value', 'sub'); 303 | 304 | expect(enmap.get('pushObj', 'sub')).toEqual(['value']); 305 | }); 306 | }); 307 | 308 | describe('math', () => { 309 | const enmap = new Enmap({ inMemory: true }); 310 | 311 | test('should fail w/o base/op/opand', () => { 312 | enmap.set('math', 1); 313 | 314 | expect(() => enmap.math('math')).toThrow( 315 | new CustomError( 316 | 'Math Operation requires base and operation', 317 | 'EnmapTypeError', 318 | ), 319 | ); 320 | }); 321 | 322 | test('should add value', () => { 323 | enmap.set('simplevalue', 1); 324 | enmap.math('simplevalue', '+', 1); 325 | enmap.math('simplevalue', 'add', 1); 326 | enmap.math('simplevalue', 'addition', 1); 327 | 328 | expect(enmap.get('simplevalue')).toBe(4); 329 | }); 330 | 331 | test('should subtract value', () => { 332 | enmap.set('simplevalue', 1); 333 | enmap.math('simplevalue', '-', 1); 334 | enmap.math('simplevalue', 'sub', 1); 335 | enmap.math('simplevalue', 'subtract', 1); 336 | 337 | expect(enmap.get('simplevalue')).toBe(-2); 338 | }); 339 | 340 | test('should multiply value', () => { 341 | enmap.set('simplevalue', 2); 342 | enmap.math('simplevalue', '*', 2); 343 | enmap.math('simplevalue', 'mult', 2); 344 | enmap.math('simplevalue', 'multiply', 2); 345 | 346 | expect(enmap.get('simplevalue')).toBe(16); 347 | }); 348 | 349 | test('should divide value', () => { 350 | enmap.set('simplevalue', 4); 351 | enmap.math('simplevalue', '/', 2); 352 | enmap.math('simplevalue', 'div', 2); 353 | enmap.math('simplevalue', 'divide', 2); 354 | 355 | expect(enmap.get('simplevalue')).toBe(0.5); 356 | }); 357 | 358 | test('should exponent value', () => { 359 | enmap.set('simplevalue', 2); 360 | enmap.math('simplevalue', '^', 2); 361 | enmap.math('simplevalue', 'exp', 2); 362 | enmap.math('simplevalue', 'exponent', 2); 363 | 364 | expect(enmap.get('simplevalue')).toBe(256); 365 | }); 366 | 367 | test('should modulo value', () => { 368 | enmap.set('simplevalue', 5); 369 | enmap.math('simplevalue', '%', 2); 370 | enmap.math('simplevalue', 'mod', 2); 371 | enmap.math('simplevalue', 'modulo', 2); 372 | 373 | expect(enmap.get('simplevalue')).toBe(1); 374 | }); 375 | 376 | test('should random value', () => { 377 | enmap.set('rand', 1); 378 | enmap.math('rand', 'rand', 1); 379 | 380 | expect(enmap.get('rand')).toBeGreaterThanOrEqual(0); 381 | expect(enmap.get('rand')).toBeLessThanOrEqual(1); 382 | }); 383 | 384 | test('should null value', () => { 385 | enmap.set('huh', 1); 386 | enmap.math('huh', 'huh', 1); 387 | 388 | expect(enmap.get('huh')).toBe(null); 389 | }); 390 | 391 | test('should math value w/ path', () => { 392 | enmap.set('pathobj', { a: 1 }); 393 | enmap.math('pathobj', 'sub', 1, 'a'); 394 | expect(enmap.get('pathobj')).toEqual({ a: 0 }); 395 | enmap.inc('pathobj', 'a'); 396 | expect(enmap.get('pathobj')).toEqual({ a: 1 }); 397 | enmap.dec('pathobj', 'a'); 398 | expect(enmap.get('pathobj')).toEqual({ a: 0 }); 399 | }); 400 | }); 401 | 402 | describe('inc', () => { 403 | const enmap = new Enmap({ inMemory: true }); 404 | 405 | test('should increment value', () => { 406 | enmap.set('inc', 1); 407 | enmap.inc('inc'); 408 | 409 | expect(enmap.get('inc')).toBe(2); 410 | }); 411 | }); 412 | 413 | describe('dec', () => { 414 | const enmap = new Enmap({ inMemory: true }); 415 | 416 | test('should decrement value', () => { 417 | enmap.set('dec', 1); 418 | enmap.dec('dec'); 419 | 420 | expect(enmap.get('dec')).toBe(0); 421 | }); 422 | }); 423 | 424 | describe('ensure', () => { 425 | const enmap = new Enmap({ inMemory: true }); 426 | const defaultEnmap = new Enmap({ 427 | inMemory: true, 428 | autoEnsure: { hello: 'world' }, 429 | }); 430 | 431 | test('should ensure value', () => { 432 | enmap.ensure('ensure', 'value'); 433 | 434 | expect(enmap.get('ensure')).toBe('value'); 435 | }); 436 | 437 | test('should ensure value w/ existing value', () => { 438 | enmap.set('ensureExisting', 'value2'); 439 | enmap.ensure('ensureExisting', 'value'); 440 | 441 | expect(enmap.get('ensureExisting')).toBe('value2'); 442 | }); 443 | 444 | test('should ensure value w/ default', () => { 445 | expect(defaultEnmap.ensure('unknown')).toEqual({ hello: 'world' }); 446 | }); 447 | 448 | test('should ensure value w/ path', () => { 449 | enmap.ensure('ensurePath', 'value', 'sub'); 450 | 451 | expect(enmap.get('ensurePath', 'sub')).toBe('value'); 452 | }); 453 | 454 | test('should ensure value w/ existing path', () => { 455 | enmap.set('ensurePathExisting', { sub: 'value2' }); 456 | enmap.ensure('ensurePathExisting', 'value', 'sub'); 457 | 458 | expect(enmap.get('ensurePathExisting', 'sub')).toBe('value2'); 459 | }); 460 | 461 | test('should fail to ensure string w/ object value', () => { 462 | enmap.set('ensureObj', { value: 'value' }); 463 | 464 | expect(() => enmap.ensure('ensureObj', 'value')).toThrow( 465 | new CustomError( 466 | 'Default value for "ensureObj" in enmap "MemoryEnmap" must be an object when merging with an object value.', 467 | 'EnmapArgumentError', 468 | ), 469 | ); 470 | }); 471 | 472 | test('should ignore + warn ensure value w/ default', () => { 473 | const spy = vi.spyOn(process, 'emitWarning'); 474 | 475 | expect(defaultEnmap.ensure('unknown', 'hello')).toEqual({ 476 | hello: 'world', 477 | }); 478 | 479 | expect(spy).toHaveBeenCalledTimes(1); 480 | }); 481 | }); 482 | 483 | describe('has', () => { 484 | const enmap = new Enmap({ inMemory: true }); 485 | 486 | test('should return true if key exists', () => { 487 | enmap.set('has', 'value'); 488 | 489 | expect(enmap.has('has')).toBe(true); 490 | }); 491 | 492 | test("should return false if key doesn't exist", () => { 493 | expect(enmap.has('unknown')).toBe(false); 494 | }); 495 | }); 496 | 497 | describe('includes', () => { 498 | const enmap = new Enmap({ inMemory: true }); 499 | 500 | test('should return true w/ value', () => { 501 | enmap.set('includes', ['value']); 502 | 503 | expect(enmap.includes('includes', 'value')).toBe(true); 504 | }); 505 | 506 | test('should return false w/o value', () => { 507 | enmap.set('includes', ['value']); 508 | 509 | expect(enmap.includes('includes', 'value2')).toBe(false); 510 | }); 511 | }); 512 | 513 | describe('delete', () => { 514 | const enmap = new Enmap({ inMemory: true }); 515 | 516 | test('should delete a key', () => { 517 | enmap.set('delete', 'value'); 518 | enmap.delete('delete'); 519 | 520 | expect(enmap.get('delete')).toBe(null); 521 | }); 522 | 523 | test('should delete a path', () => { 524 | enmap.set('deletePath', 'value', 'sub'); 525 | enmap.delete('deletePath', 'sub'); 526 | 527 | expect(enmap.get('deletePath', 'sub')).toBe(undefined); 528 | }); 529 | }); 530 | 531 | describe('clear', () => { 532 | const enmap = new Enmap({ inMemory: true }); 533 | 534 | test('should clear all keys', () => { 535 | enmap.set('clear', 'value'); 536 | enmap.clear(); 537 | 538 | expect(enmap.get('clear')).toBe(null); 539 | }); 540 | }); 541 | 542 | describe('remove', () => { 543 | const enmap = new Enmap({ inMemory: true }); 544 | 545 | test('should remove a value', () => { 546 | enmap.set('remove', ['value']); 547 | enmap.remove('remove', 'value'); 548 | 549 | expect(enmap.get('remove')).toEqual([]); 550 | }); 551 | 552 | test('should remove a value w/ function', () => { 553 | enmap.set('remove', ['value', 'value2']); 554 | enmap.remove('remove', (val) => val === 'value'); 555 | 556 | expect(enmap.get('remove')).toEqual(['value2']); 557 | }); 558 | 559 | test('should remove a value w/ path', () => { 560 | enmap.set('removePath', { sub: ['value'] }); 561 | enmap.remove('removePath', 'value', 'sub'); 562 | 563 | expect(enmap.get('removePath', 'sub')).toEqual([]); 564 | }); 565 | }); 566 | 567 | describe('export', () => { 568 | const enmap = new Enmap({ inMemory: true }); 569 | 570 | test('should export data', () => { 571 | enmap.set('export', 'value'); 572 | 573 | const output = enmap.export(); 574 | 575 | expect(parse(output)).toMatchObject({ 576 | name: 'MemoryEnmap', 577 | exportDate: expect.any(Number), 578 | version: expect.any(String), 579 | keys: [{ key: 'export', value: stringify('value') }], 580 | }); 581 | }); 582 | }); 583 | 584 | describe('import', () => { 585 | const enmap = new Enmap({ inMemory: true }); 586 | 587 | test('should import data', () => { 588 | enmap.import( 589 | JSON.stringify({ 590 | name: 'MemoryEnmap', 591 | exportDate: Date.now(), 592 | version: '1.0.0', 593 | keys: [{ key: 'import', value: stringify({ hello: 'world' }) }], 594 | }), 595 | ); 596 | 597 | expect(enmap.get('import')).toEqual({ hello: 'world' }); 598 | }); 599 | 600 | test('should import data w/o overwrite', () => { 601 | enmap.set('import', 'value'); 602 | enmap.import( 603 | JSON.stringify({ 604 | name: 'MemoryEnmap', 605 | exportDate: Date.now(), 606 | version: '1.0.0', 607 | keys: [{ key: 'import', value: stringify({ hello: 'world' }) }], 608 | }), 609 | false, 610 | ); 611 | 612 | expect(enmap.get('import')).toBe('value'); 613 | }); 614 | 615 | test('should import data w/ clear w/o overwrite', () => { 616 | enmap.set('import', 'value'); 617 | 618 | enmap.import( 619 | JSON.stringify({ 620 | name: 'MemoryEnmap', 621 | exportDate: Date.now(), 622 | version: '1.0.0', 623 | keys: [{ key: 'import', value: stringify({ hello: 'world' }) }], 624 | }), 625 | false, 626 | true, 627 | ); 628 | 629 | expect(enmap.get('import')).toEqual({ hello: 'world' }); 630 | }); 631 | 632 | test('should fail to import invalid data', () => { 633 | expect(() => enmap.import('invalid')).toThrow( 634 | new CustomError('Data provided is not valid JSON', 'EnmapDataError'), 635 | ); 636 | }); 637 | 638 | test('should fail to import null data', () => { 639 | expect(() => enmap.import('null')).toThrow( 640 | new CustomError( 641 | 'No data provided for import() in "MemoryEnmap"', 642 | 'EnmapImportError', 643 | ), 644 | ); 645 | }); 646 | }); 647 | 648 | describe('multi', () => { 649 | test('should create multiple Enmaps', () => { 650 | const enmaps = Enmap.multi(['multi1', 'multi2'], { inMemory: true }); 651 | 652 | expect(enmaps).toEqual({ 653 | multi1: expect.any(Enmap), 654 | multi2: expect.any(Enmap), 655 | }); 656 | }); 657 | 658 | test('should fail to create empty', () => { 659 | expect(() => Enmap.multi([])).toThrow( 660 | new CustomError( 661 | '"names" argument must be an array of string names.', 662 | 'EnmapTypeError', 663 | ), 664 | ); 665 | }); 666 | }); 667 | 668 | describe('random', () => { 669 | const enmap = new Enmap({ inMemory: true }); 670 | 671 | test('should get random value', () => { 672 | enmap.set('random', 'value'); 673 | 674 | expect(enmap.random()).toEqual([['random', 'value']]); 675 | }); 676 | 677 | test('should get random value w/ count', () => { 678 | enmap.set('random', 'value'); 679 | 680 | expect(enmap.random(2).length).toBe(1); 681 | 682 | enmap.set('random2', 'value'); 683 | 684 | expect(enmap.random(2).length).toBe(2); 685 | }); 686 | }); 687 | 688 | describe('randomKey', () => { 689 | const enmap = new Enmap({ inMemory: true }); 690 | 691 | test('should get random key', () => { 692 | enmap.set('random', 'value'); 693 | 694 | expect(enmap.randomKey()).toEqual(['random']); 695 | }); 696 | 697 | test('should get random key w/ count', () => { 698 | enmap.set('random', 'value'); 699 | 700 | expect(enmap.randomKey(2).length).toBe(1); 701 | 702 | enmap.set('random2', 'value'); 703 | 704 | expect(enmap.randomKey(2).length).toBe(2); 705 | }); 706 | }); 707 | 708 | describe('every', () => { 709 | const enmap = new Enmap({ inMemory: true }); 710 | 711 | test('should return true for all values w/ value', () => { 712 | enmap.set('every', 'value'); 713 | enmap.set('every2', 'value'); 714 | 715 | expect(enmap.every('value')).toBe(true); 716 | }); 717 | 718 | test('should return true for all value w/ function', () => { 719 | enmap.set('every', 'value'); 720 | enmap.set('every2', 'value'); 721 | 722 | expect(enmap.every((val) => val === 'value')).toBe(true); 723 | }); 724 | 725 | test('should return false for all values w/o value', () => { 726 | enmap.set('every', 'value'); 727 | enmap.set('every2', 'value2'); 728 | 729 | expect(enmap.every('value')).toBe(false); 730 | }); 731 | 732 | test('should return false for all value w/ function', () => { 733 | enmap.set('every', 'value'); 734 | enmap.set('every2', 'value2'); 735 | 736 | expect(enmap.every((val) => val === 'value')).toBe(false); 737 | }); 738 | 739 | test('should return false for all values w/ path', () => { 740 | enmap.set('every', { sub: 'value' }); 741 | enmap.set('every2', { sub: 'value2' }); 742 | 743 | expect(enmap.every('value', 'sub')).toBe(false); 744 | }); 745 | 746 | test('should return true for all values w/ path', () => { 747 | enmap.set('every', { sub: 'value' }); 748 | enmap.set('every2', { sub: 'value' }); 749 | 750 | expect(enmap.every('value', 'sub')).toBe(true); 751 | }); 752 | }); 753 | 754 | describe('some', () => { 755 | const enmap = new Enmap({ inMemory: true }); 756 | 757 | test('should return true for some values w/ value', () => { 758 | enmap.set('some', 'value'); 759 | enmap.set('some2', 'value2'); 760 | 761 | expect(enmap.some('value')).toBe(true); 762 | }); 763 | 764 | test('should return true for some value w/ function', () => { 765 | enmap.set('some', 'value'); 766 | enmap.set('some2', 'value2'); 767 | 768 | expect(enmap.some((val) => val === 'value')).toBe(true); 769 | }); 770 | 771 | test('should return false for some values w/o value', () => { 772 | enmap.set('some', 'value'); 773 | enmap.set('some2', 'value2'); 774 | 775 | expect(enmap.some('value3')).toBe(false); 776 | }); 777 | 778 | test('should return false for some value w/ function', () => { 779 | enmap.set('some', 'value'); 780 | enmap.set('some2', 'value2'); 781 | 782 | expect(enmap.some((val) => val === 'value3')).toBe(false); 783 | }); 784 | 785 | test('should return false for some values w/ path', () => { 786 | enmap.set('some', { sub: 'value' }); 787 | enmap.set('some2', { sub: 'value2' }); 788 | 789 | expect(enmap.some('value', 'sub')).toBe(true); 790 | }); 791 | 792 | test('should return true for some values w/ path', () => { 793 | enmap.set('some', { sub: 'value' }); 794 | enmap.set('some2', { sub: 'value' }); 795 | 796 | expect(enmap.some('value', 'sub')).toBe(true); 797 | }); 798 | }); 799 | 800 | describe('map', () => { 801 | const enmap = new Enmap({ inMemory: true }); 802 | 803 | test('should map values', () => { 804 | enmap.set('map', 'value'); 805 | enmap.set('map2', 'value2'); 806 | 807 | expect(enmap.map((val) => val)).toEqual(['value', 'value2']); 808 | }); 809 | 810 | test('should map values w/ path', () => { 811 | enmap.set('map', { sub: 'value' }); 812 | enmap.set('map2', { sub: 'value2' }); 813 | 814 | expect(enmap.map('sub')).toEqual(['value', 'value2']); 815 | }); 816 | }); 817 | 818 | describe('find', () => { 819 | const enmap = new Enmap({ inMemory: true }); 820 | 821 | test('should find value', () => { 822 | enmap.set('find', 'value'); 823 | enmap.set('find2', 'value2'); 824 | 825 | expect(enmap.find((val) => val === 'value')).toBe('value'); 826 | }); 827 | 828 | test('should find value w/ path', () => { 829 | enmap.set('find', { sub: 'value' }); 830 | enmap.set('find2', { sub: 'value2' }); 831 | 832 | expect(enmap.find('sub', 'value')).toEqual({ sub: 'value' }); 833 | }); 834 | 835 | test('should return null if not found', () => { 836 | enmap.set('find', 'value'); 837 | enmap.set('find2', 'value2'); 838 | 839 | expect(enmap.find((val) => val === 'value3')).toBe(null); 840 | }); 841 | }); 842 | 843 | describe('findIndex', () => { 844 | const enmap = new Enmap({ inMemory: true }); 845 | 846 | test('should find index', () => { 847 | enmap.set('find', 'value'); 848 | enmap.set('find2', 'value2'); 849 | 850 | expect(enmap.findIndex((val) => val === 'value')).toBe('find'); 851 | }); 852 | 853 | test('should find index w/ path', () => { 854 | enmap.set('find', { sub: 'value' }); 855 | enmap.set('find2', { sub: 'value2' }); 856 | 857 | expect(enmap.findIndex('sub', 'value')).toBe('find'); 858 | }); 859 | 860 | test('should return null if not found', () => { 861 | enmap.set('find', 'value'); 862 | enmap.set('find2', 'value2'); 863 | 864 | expect(enmap.findIndex((val) => val === 'value3')).toBe(null); 865 | }); 866 | }); 867 | 868 | describe('reduce', () => { 869 | const enmap = new Enmap({ inMemory: true }); 870 | 871 | test('should reduce values', () => { 872 | enmap.set('reduce', 1); 873 | enmap.set('reduce2', 2); 874 | 875 | expect(enmap.reduce((acc, val) => acc + val, 0)).toBe(3); 876 | }); 877 | 878 | test('should reduce values w/ path', () => { 879 | enmap.set('reduce', { sub: 1 }); 880 | enmap.set('reduce2', { sub: 2 }); 881 | 882 | expect(enmap.reduce((acc, val) => acc + val.sub, 0)).toBe(3); 883 | }); 884 | }); 885 | 886 | describe('filter', () => { 887 | const enmap = new Enmap({ inMemory: true }); 888 | 889 | test('should filter values', () => { 890 | enmap.set('filter', 'value'); 891 | enmap.set('filter2', 'value2'); 892 | 893 | expect(enmap.filter((val) => val === 'value')).toEqual(['value']); 894 | }); 895 | 896 | test('should filter values w/ path', () => { 897 | enmap.set('filter', { sub: 'value' }); 898 | enmap.set('filter2', { sub: 'value2' }); 899 | 900 | expect(enmap.filter('sub', 'value')).toEqual([{ sub: 'value' }]); 901 | }); 902 | 903 | test('should fail to filter w/o value', () => { 904 | enmap.set('filter', 'value'); 905 | enmap.set('filter2', 'value2'); 906 | 907 | expect(() => enmap.filter('value')).toThrow( 908 | new CustomError( 909 | 'Value is required for non-function predicate', 910 | 'EnmapValueError', 911 | ), 912 | ); 913 | }); 914 | }); 915 | 916 | describe('sweep', () => { 917 | const enmap = new Enmap({ inMemory: true }); 918 | 919 | test('should sweep values', () => { 920 | enmap.set('sweep', 'value'); 921 | enmap.set('sweep2', 'value2'); 922 | 923 | expect(enmap.sweep((val) => val === 'value')).toEqual(1); 924 | }); 925 | 926 | test('should sweep values w/ path', () => { 927 | enmap.set('sweep', { sub: 'value' }); 928 | enmap.set('sweep2', { sub: 'value2' }); 929 | 930 | expect(enmap.sweep('sub', 'value')).toEqual(1); 931 | }); 932 | 933 | test('should sweep values w/ function', () => { 934 | enmap.set('sweep', 'value'); 935 | enmap.set('sweep2', 'value2'); 936 | 937 | expect(enmap.sweep((val) => val === 'value')).toEqual(1); 938 | }); 939 | }); 940 | 941 | describe('partition', () => { 942 | const enmap = new Enmap({ inMemory: true }); 943 | 944 | test('should partition values', () => { 945 | enmap.set('partition', 'value'); 946 | enmap.set('partition2', 'value2'); 947 | 948 | expect(enmap.partition((val) => val === 'value')).toEqual([ 949 | ['value'], 950 | ['value2'], 951 | ]); 952 | }); 953 | 954 | test('should partition values w/ path', () => { 955 | enmap.set('partition', { sub: 'value' }); 956 | enmap.set('partition2', { sub: 'value2' }); 957 | 958 | expect(enmap.partition('sub', 'value')).toEqual([ 959 | [{ sub: 'value' }], 960 | [{ sub: 'value2' }], 961 | ]); 962 | }); 963 | }); 964 | }); 965 | }); 966 | -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'enmap' { 2 | export interface EnmapOptions { 3 | name?: string; 4 | fetchAll?: boolean; 5 | autoFetch?: boolean; 6 | dataDir?: string; 7 | cloneLevel?: 'none' | 'shallow' | 'deep'; 8 | polling?: boolean; 9 | pollingInterval?: number; 10 | ensureProps?: boolean; 11 | wal?: boolean; 12 | verbose?: (query: string) => void; 13 | autoEnsure?: unknown; 14 | serializer?: (value: V, key: string) => SV; 15 | deserializer?: (value: SV, key: string) => V; 16 | } 17 | 18 | type MathOps = 19 | | 'add' 20 | | 'addition' 21 | | '+' 22 | | 'sub' 23 | | 'subtract' 24 | | '-' 25 | | 'mult' 26 | | 'multiply' 27 | | '*' 28 | | 'div' 29 | | 'divide' 30 | | '/' 31 | | 'exp' 32 | | 'exponent' 33 | | '^' 34 | | 'mod' 35 | | 'modulo' 36 | | '%'; 37 | 38 | /* 39 | * see https://github.com/eslachance/enmap/issues/54 40 | */ 41 | type Path = Key extends string 42 | ? T[Key] extends Record 43 | ? 44 | | `${Key}.${Path> & 45 | string}` 46 | | `${Key}.${Exclude & string}` 47 | | Key 48 | : never 49 | : never; 50 | 51 | type PathValue> = P extends `${infer Key}.${infer Rest}` 52 | ? Key extends keyof T 53 | ? Rest extends Path 54 | ? PathValue 55 | : never 56 | : never 57 | : P extends keyof T 58 | ? T[P] 59 | : never; 60 | 61 | /** 62 | * Hack to work around TypeScript's structural integrity requirement. 63 | * This is the Map class without the delete method since Enmap returns this 64 | * while the standard returns boolean. 65 | */ 66 | class AlmostMap { 67 | readonly size: number; 68 | readonly [Symbol.toStringTag]: 'Map'; 69 | 70 | clear(): void; 71 | forEach( 72 | callbackfn: (value: V, key: K, map: Map) => void, 73 | thisArg?: any, 74 | ): void; 75 | 76 | get(key: K): V | undefined; 77 | has(key: K): boolean; 78 | set(key: K, value: V): this; 79 | 80 | entries(): IterableIterator<[K, V]>; 81 | keys(): IterableIterator; 82 | values(): IterableIterator; 83 | 84 | [Symbol.iterator](): IterableIterator<[K, V]>; 85 | } 86 | 87 | /** 88 | * A enhanced Map structure with additional utility methods. 89 | * Can be made persistent 90 | */ 91 | export default class Enmap< 92 | K extends string | number = string | number, 93 | V = any, 94 | SV = unknown, 95 | > extends AlmostMap { 96 | public readonly cloneLevel: 'none' | 'shallow' | 'deep'; 97 | public readonly name: string; 98 | public readonly dataDir: string; 99 | public readonly fetchAll: boolean; 100 | public readonly autoFetch: boolean; 101 | public readonly defer: Promise; 102 | public readonly persistent: boolean; 103 | public readonly pollingInterval: number; 104 | public readonly polling: boolean; 105 | public readonly isReady: boolean; 106 | public readonly lastSync: Date; 107 | public readonly ensureProps: boolean; 108 | public readonly wal: boolean; 109 | public readonly changedCB: ( 110 | key: K, 111 | oldValue: V | undefined, 112 | newValue: V | undefined, 113 | ) => void; 114 | 115 | private db: any; 116 | private pool: any; 117 | private ready: () => void; 118 | 119 | /** 120 | * Retrieves the number of rows in the database for this enmap, even if they aren't fetched. 121 | * @return The number of rows in the database. 122 | */ 123 | public readonly count: number; 124 | 125 | /** 126 | * Retrieves all the indexes (keys) in the database for this enmap, even if they aren't fetched. 127 | * @return Array of all indexes (keys) in the enmap, cached or not. 128 | */ 129 | public readonly indexes: string[]; 130 | 131 | /** 132 | * Generates an automatic numerical key for inserting a new value. 133 | * This is a "weak" method, it ensures the value isn't duplicated, but does not 134 | * guarantee it's sequential (if a value is deleted, another can take its place). 135 | * Useful for logging, but not much else. 136 | * @example 137 | * enmap.set(enmap.autonum, "This is a new value"); 138 | * @return The generated key number. 139 | */ 140 | public readonly autonum: number; 141 | 142 | /** 143 | * Initializes a new Enmap, with options. 144 | * @param iterable If iterable data, only valid in non-persistent enmaps. 145 | * If this parameter is a string, it is assumed to be the enmap's name, which is a shorthand for adding a name in the options 146 | * and making the enmap persistent. 147 | * @param options Additional options for the enmap. See https://enmap.alterion.dev/usage#enmap-options for details. 148 | * @example 149 | * const Enmap = require("enmap"); 150 | * // Non-persistent enmap: 151 | * const inMemory = new Enmap(); 152 | * 153 | * // Named, Persistent enmap with string option 154 | * const myEnmap = new Enmap("testing"); 155 | * 156 | * // Named, Persistent enmap with a few options: 157 | * const myEnmap = new Enmap({name: "testing", fetchAll: false, autoFetch: true}); 158 | */ 159 | constructor( 160 | iterable?: Iterable<[K, V]> | string | EnmapOptions, 161 | options?: EnmapOptions, 162 | ); 163 | 164 | /** 165 | * Sets a value in Enmap. 166 | * @param key Required. The key of the element to add to The Enmap. 167 | * @param val Required. The value of the element to add to The Enmap. 168 | * If the Enmap is persistent this value MUST be stringifiable as JSON. 169 | * @example 170 | * // Direct Value Examples 171 | * enmap.set('simplevalue', 'this is a string'); 172 | * enmap.set('isEnmapGreat', true); 173 | * enmap.set('TheAnswer', 42); 174 | * enmap.set('IhazObjects', { color: 'black', action: 'paint', desire: true }); 175 | * enmap.set('ArraysToo', [1, "two", "tree", "foor"]) 176 | * 177 | * @returns The enmap. 178 | */ 179 | public set(key: K, val: V): this; 180 | 181 | /** 182 | * Sets a value in Enmap. 183 | * @param key Required. The key of the element to add to The Enmap. 184 | * @param val Required. The value of the element to add to The Enmap. 185 | * If the Enmap is persistent this value MUST be stringifiable as JSON. 186 | * @param path The path to the property to modify inside the value object or array. 187 | * Should be a path with dot notation, such as "prop1.subprop2.subprop3" 188 | * @example 189 | * // Settings Properties 190 | * enmap.set('IhazObjects', 'color', 'blue'); //modified previous object 191 | * enmap.set('ArraysToo', 2, 'three'); // changes "tree" to "three" in array. 192 | * @returns The enmap. 193 | */ 194 | public set(key: K, val: any, path: string): this; 195 | 196 | /** 197 | * Update an existing object value in Enmap by merging new keys. 198 | * This only works on objects, any other value will throw an error. 199 | * Heavily inspired by setState from React's class components. 200 | * This is very useful if you have many different values to update, 201 | * and don't want to have more than one .set(key, value, prop) lines. 202 | * @param key Required. The key of the object to update. 203 | * @param valueOrFunction Required. Either an object to merge with the existing value, 204 | * or a function that provides the existing object and expects a new object as a return value. 205 | * In the case of a straight value, the merge is recursive and will add any missing level. 206 | * If using a function, it is your responsibility to merge the objects together correctly. 207 | */ 208 | public update(key: K, valueOrFunction: V | ((value: V) => V)): V; 209 | 210 | /** 211 | * Retrieves a key from the enmap. If fetchAll is false, returns a promise. 212 | * @param key The key to retrieve from the enmap. 213 | * @param path Optional. The property to retrieve from the object or array. 214 | * Should be a path with dot notation, such as "prop1.subprop2.subprop3" 215 | * @example 216 | * const myKeyValue = enmap.get("myKey"); 217 | * console.log(myKeyValue); 218 | * 219 | * const someSubValue = enmap.get("anObjectKey", "someprop.someOtherSubProp"); 220 | * @return The value for this key. 221 | */ 222 | public get(key: K): V | undefined; 223 | public get

(key: K, path: P): V[P] | undefined; 224 | public get

>(key: K, path: P): PathValue | undefined; 225 | public get(key: K, path: string): unknown; 226 | 227 | /** 228 | * Fetches every key from the persistent enmap and loads them into the current enmap value. 229 | * @return The enmap containing all values. 230 | */ 231 | public fetchEverything(): this; 232 | 233 | /** 234 | * Force fetch one or more key values from the enmap. If the database has changed, that new value is used. 235 | * @param keys A single key or array of keys to force fetch from the enmap database. 236 | * @return The Enmap, including the new fetched values 237 | */ 238 | public fetch(keys: K[]): this; 239 | 240 | /** 241 | * Force fetch one or more key values from the enmap. If the database has changed, that new value is used. 242 | * @param key A single key to force fetch from the enmap database. 243 | * @return The value. 244 | */ 245 | public fetch(key: K): V; 246 | 247 | /** 248 | * Removes a key or keys from the cache - useful when disabling autoFetch. 249 | * @param keyOrArrayOfKeys A single key or array of keys to remove from the cache. 250 | * @returns the Enmap minus the evicted keys. 251 | */ 252 | public evict(keyOrArrayOfKeys: K | K[]): this; 253 | 254 | /** 255 | * Function called whenever data changes within Enmap after the initial load. 256 | * Can be used to detect if another part of your code changed a value in enmap and react on it. 257 | * @example 258 | * enmap.changed((keyName, oldValue, newValue) => { 259 | * console.log(`Value of ${keyName} has changed from: \n${oldValue}\nto\n${newValue}); 260 | * }); 261 | * @param cb A callback function that will be called whenever data changes in the enmap. 262 | */ 263 | public changed( 264 | cb: (key: K, oldValue: V | undefined, newValue: V | undefined) => void, 265 | ): void; 266 | 267 | /** 268 | * Shuts down the database. WARNING: USING THIS MAKES THE ENMAP UNUSEABLE. You should 269 | * only use this method if you are closing your entire application. 270 | * Note that honestly I've never had to use this, shutting down the app without a close() is fine. 271 | * @return The promise of the database closing operation. 272 | */ 273 | public close(): Promise; 274 | 275 | /** 276 | * Modify the property of a value inside the enmap, if the value is an object or array. 277 | * This is a shortcut to loading the key, changing the value, and setting it back. 278 | * @param key Required. The key of the element to add to The Enmap or array. 279 | * This value MUST be a string or number. 280 | * @param path Required. The property to modify inside the value object or array. 281 | * Should be a path with dot notation, such as "prop1.subprop2.subprop3" 282 | * @param val Required. The value to apply to the specified property. 283 | * @returns The enmap. 284 | */ 285 | public setProp(key: K, path: string, val: any): this; 286 | 287 | /** 288 | * Push to an array value in Enmap. 289 | * @param key Required. The key of the array element to push to in Enmap. 290 | * This value MUST be a string or number. 291 | * @param val Required. The value to push to the array. 292 | * @param path Optional. The path to the property to modify inside the value object or array. 293 | * Should be a path with dot notation, such as "prop1.subprop2.subprop3" 294 | * @param allowDupes Optional. Allow duplicate values in the array (default: false). 295 | * @example 296 | * // Assuming 297 | * enmap.set("simpleArray", [1, 2, 3, 4]); 298 | * enmap.set("arrayInObject", {sub: [1, 2, 3, 4]}); 299 | * 300 | * enmap.push("simpleArray", 5); // adds 5 at the end of the array 301 | * enmap.push("arrayInObject", "five", "sub"); adds "five" at the end of the sub array 302 | * @returns The enmap. 303 | */ 304 | public push(key: K, val: any, path?: string, allowDupes?: boolean): this; 305 | 306 | /** 307 | * Push to an array element inside an Object or Array element in Enmap. 308 | * @param key Required. The key of the element. 309 | * This value MUST be a string or number. 310 | * @param path Required. The name of the array property to push to. 311 | * Should be a path with dot notation, such as "prop1.subprop2.subprop3" 312 | * @param val Required. The value push to the array property. 313 | * @param allowDupes Allow duplicate values in the array (default: false). 314 | * @returns The enmap. 315 | */ 316 | public pushIn(key: K, path: string, val: any, allowDupes?: boolean): this; 317 | 318 | // AWESOME MATHEMATICAL METHODS 319 | 320 | /** 321 | * Executes a mathematical operation on a value and saves it in the enmap. 322 | * @param key The enmap key on which to execute the math operation. 323 | * @param operation Which mathematical operation to execute. Supports most 324 | * math ops: =, -, *, /, %, ^, and english spelling of those operations. 325 | * @param operand The right operand of the operation. 326 | * @param path Optional. The property path to execute the operation on, if the value is an object or array. 327 | * @example 328 | * // Assuming 329 | * points.set("number", 42); 330 | * points.set("numberInObject", {sub: { anInt: 5 }}); 331 | * 332 | * points.math("number", "/", 2); // 21 333 | * points.math("number", "add", 5); // 26 334 | * points.math("number", "modulo", 3); // 2 335 | * points.math("numberInObject", "+", 10, "sub.anInt"); 336 | * 337 | * @returns The enmap. 338 | */ 339 | public math( 340 | key: K, 341 | operation: MathOps, 342 | operand: number, 343 | path?: string, 344 | ): this; 345 | 346 | /** 347 | * Increments a key's value or property by 1. Value must be a number, or a path to a number. 348 | * @param key The enmap key where the value to increment is stored. 349 | * @param path Optional. The property path to increment, if the value is an object or array. 350 | * @example 351 | * // Assuming 352 | * points.set("number", 42); 353 | * points.set("numberInObject", {sub: { anInt: 5 }}); 354 | * 355 | * points.inc("number"); // 43 356 | * points.inc("numberInObject", "sub.anInt"); // {sub: { anInt: 6 }} 357 | * @returns The enmap. 358 | */ 359 | public inc(key: K, path?: string): this; 360 | 361 | /** 362 | * Decrements a key's value or property by 1. Value must be a number, or a path to a number. 363 | * @param key The enmap key where the value to decrement is stored. 364 | * @param path Optional. The property path to decrement, if the value is an object or array. 365 | * @example 366 | * // Assuming 367 | * points.set("number", 42); 368 | * points.set("numberInObject", {sub: { anInt: 5 }}); 369 | * 370 | * points.dec("number"); // 41 371 | * points.dec("numberInObject", "sub.anInt"); // {sub: { anInt: 4 }} 372 | * @returns The enmap. 373 | */ 374 | public dec(key: K, path?: string): this; 375 | 376 | /** 377 | * Returns the specific property within a stored value. If the key does not exist or the value is not an object, throws an error. 378 | * @param key Required. The key of the element to get from The Enmap. 379 | * @param path Required. The property to retrieve from the object or array. 380 | * Should be a path with dot notation, such as "prop1.subprop2.subprop3" 381 | * @return The value of the property obtained. 382 | */ 383 | public getProp(key: K, path: string): any; 384 | 385 | /** 386 | * Returns the key's value, or the default given, ensuring that the data is there. 387 | * This is a shortcut to "if enmap doesn't have key, set it, then get it" which is a very common pattern. 388 | * @param key Required. The key you want to make sure exists. 389 | * @param defaultValue Required. The value you want to save in the database and return as default. 390 | * @param path Optional. If presents, ensures both the key exists as an object, and the full path exists. 391 | * Should be a path with dot notation, such as "prop1.subprop2.subprop3" 392 | * @example 393 | * // Simply ensure the data exists (for using property methods): 394 | * enmap.ensure("mykey", {some: "value", here: "as an example"}); 395 | * enmap.has("mykey"); // always returns true 396 | * enmap.get("mykey", "here") // returns "as an example"; 397 | * 398 | * // Get the default value back in a variable: 399 | * const settings = mySettings.ensure("1234567890", defaultSettings); 400 | * console.log(settings) // enmap's value for "1234567890" if it exists, otherwise the defaultSettings value. 401 | * @return The value from the database for the key, or the default value provided for a new key. 402 | */ 403 | public ensure(key: K, defaultValue: V, path?: string): V; 404 | 405 | /* BOOLEAN METHODS THAT CHECKS FOR THINGS IN ENMAP */ 406 | 407 | /** 408 | * Returns whether or not the key exists in the Enmap. 409 | * @param key Required. The key of the element to add to The Enmap or array. 410 | * This value MUST be a string or number. 411 | * @param path Optional. The property to verify inside the value object or array. 412 | * Should be a path with dot notation, such as "prop1.subprop2.subprop3" 413 | * @example 414 | * if(enmap.has("myKey")) { 415 | * // key is there 416 | * } 417 | * 418 | * if(!enmap.has("myOtherKey", "oneProp.otherProp.SubProp")) return false; 419 | */ 420 | public has(key: K, path?: string): boolean; 421 | 422 | /** 423 | * Returns whether or not the property exists within an object or array value in enmap. 424 | * @param key Required. The key of the element to check in the Enmap or array. 425 | * @param path Required. The property to verify inside the value object or array. 426 | * Should be a path with dot notation, such as "prop1.subprop2.subprop3" 427 | * @return Whether the property exists. 428 | */ 429 | public hasProp(key: K, path: string): boolean; 430 | 431 | /** 432 | * Deletes a key in the Enmap. 433 | * Note: Does not return a boolean, unlike the standard Map. 434 | * @param key Required. The key of the element to delete from The Enmap. 435 | * @param path Optional. The name of the property to remove from the object. 436 | * Should be a path with dot notation, such as "prop1.subprop2.subprop3" 437 | * @returns The enmap. 438 | */ 439 | public delete(key: K, path?: string): this; 440 | 441 | /** 442 | * Delete a property from an object or array value in Enmap. 443 | * @param key Required. The key of the element to delete the property from in Enmap. 444 | * @param path Required. The name of the property to remove from the object. 445 | * Should be a path with dot notation, such as "prop1.subprop2.subprop3" 446 | */ 447 | public deleteProp(key: K, path: string): void; 448 | 449 | /** 450 | * Deletes everything from the enmap. If persistent, clears the database of all its data for this table. 451 | */ 452 | public deleteAll(): void; 453 | 454 | public clear(): void; 455 | 456 | /** 457 | * Remove a value in an Array or Object element in Enmap. Note that this only works for 458 | * values, not keys. Complex values such as objects and arrays will not be removed this way. 459 | * @param key Required. The key of the element to remove from in Enmap. 460 | * This value MUST be a string or number. 461 | * @param val Required. The value to remove from the array or object. 462 | * @param path Optional. The name of the array property to remove from. 463 | * Should be a path with dot notation, such as "prop1.subprop2.subprop3". 464 | * If not presents, removes directly from the value. 465 | * @returns The enmap. 466 | */ 467 | public remove(key: K, val: V, path?: string): this; 468 | 469 | /** 470 | * Remove a value from an Array or Object property inside an Array or Object element in Enmap. 471 | * Confusing? Sure is. 472 | * @param key Required. The key of the element. 473 | * This value MUST be a string or number. 474 | * @param path Required. The name of the array property to remove from. 475 | * Should be a path with dot notation, such as "prop1.subprop2.subprop3" 476 | * @param val Required. The value to remove from the array property. 477 | * @returns The enmap. 478 | */ 479 | public removeFrom(key: K, path: string, val: any): this; 480 | 481 | /** 482 | * Exports the enmap data to stringified JSON format. WARNING: Does not work on memory enmaps containing complex data! 483 | * @returns The enmap data in a stringified JSON format. 484 | */ 485 | public export(): string; 486 | 487 | /** 488 | * Import an existing json export from enmap from a string. This data must have been exported from enmap, and must be from a version that's equivalent or lower than where you're importing it. 489 | * @param data The data to import to Enmap. Must contain all the required fields provided by export() 490 | * @param overwrite Defaults to true. Whether to overwrite existing key/value data with incoming imported data 491 | * @param clear Defaults to false. Whether to clear the enmap of all data before importing (WARNING: Any exiting data will be lost! This cannot be undone.) 492 | * @returns The enmap. 493 | */ 494 | public import(data: string, overwrite?: boolean, clear?: boolean): this; 495 | 496 | /** 497 | * Initialize multiple Enmaps easily. 498 | * @param names Array of strings. Each array entry will create a separate enmap with that name. 499 | * @param options Options object to pass to the provider. See provider documentation for its options. 500 | * @example 501 | * // Using local variables and the mongodb provider. 502 | * const Enmap = require('enmap'); 503 | * const { settings, tags, blacklist } = Enmap.multi(['settings', 'tags', 'blacklist']); 504 | * 505 | * // Attaching to an existing object (for instance some API's client) 506 | * const Enmap = require("enmap"); 507 | * Object.assign(client, Enmap.multi(["settings", "tags", "blacklist"])); 508 | * 509 | * @returns An array of initialized Enmaps. 510 | */ 511 | public static multi( 512 | names: string[], 513 | options?: EnmapOptions, 514 | ): Enmap[]; 515 | 516 | /* INTERNAL (Private) METHODS */ 517 | 518 | /** 519 | * Internal Method. Initializes the enmap depending on given values. 520 | * @param pool In order to set data to the Enmap, one must be provided. 521 | * @returns Returns the defer promise to await the ready state. 522 | */ 523 | private _init(pool: any): Promise; 524 | 525 | /** 526 | * INTERNAL method to verify the type of a key or property 527 | * Will THROW AN ERROR on wrong type, to simplify code. 528 | * @param key Required. The key of the element to check 529 | * @param type Required. The javascript constructor to check 530 | * @param path Optional. The dotProp path to the property in the object enmap. 531 | */ 532 | private _check(key: K, type: string, path?: string): void; 533 | 534 | /** 535 | * INTERNAL method to execute a mathematical operation. Cuz... javascript. 536 | * And I didn't want to import mathjs! 537 | * @param base the lefthand operand. 538 | * @param op the operation. 539 | * @param opand the righthand operand. 540 | * @return the result. 541 | */ 542 | private _mathop(base: number, op: string, opand: number): number; 543 | 544 | /** 545 | * Internal method used to validate persistent enmap names (valid Windows filenames) 546 | */ 547 | private _validateName(): void; 548 | 549 | /** 550 | * Internal Method. Verifies if a key needs to be fetched from the database. 551 | * If persistent enmap and autoFetch is on, retrieves the key. 552 | * @param key The key to check or fetch. 553 | */ 554 | private _fetchCheck(key: K, force?: boolean): void; 555 | 556 | /** 557 | * Internal Method. Parses JSON data. 558 | * Reserved for future use (logical checking) 559 | * @param data The data to check/parse 560 | * @returns An object or the original data. 561 | */ 562 | private _parseData(data: any): any; 563 | 564 | /** 565 | * Internal Method. Clones a value or object with the enmap's set clone level. 566 | * @param data The data to clone. 567 | * @return The cloned value. 568 | */ 569 | private _clone(data: any): any; 570 | 571 | /** 572 | * Internal Method. Verifies that the database is ready, assuming persistence is used. 573 | */ 574 | private _readyCheck(): void; 575 | 576 | /* 577 | BELOW IS DISCORD.JS COLLECTION CODE 578 | Per notes in the LICENSE file, this project contains code from Amish Shah's Discord.js 579 | library. The code is from the Collections object, in discord.js version 11. 580 | 581 | All below code is sourced from Collections. 582 | https://github.com/discordjs/discord.js/blob/stable/src/util/Collection.js 583 | */ 584 | 585 | /** 586 | * Creates an ordered array of the values of this Enmap. 587 | * The array will only be reconstructed if an item is added to or removed from the Enmap, 588 | * or if you change the length of the array itself. If you don't want this caching behavior, 589 | * use `Array.from(enmap.values())` instead. 590 | */ 591 | public array(): V[]; 592 | 593 | /** 594 | * Creates an ordered array of the keys of this Enmap 595 | * The array will only be reconstructed if an item is added to or removed from the Enmap, 596 | * or if you change the length of the array itself. If you don't want this caching behavior, 597 | * use `Array.from(enmap.keys())` instead. 598 | */ 599 | public keyArray(): K[]; 600 | 601 | /** 602 | * Obtains a random value from this Enmap. This relies on {@link Enmap#array}. 603 | * @returns A random value from the Enmap. 604 | */ 605 | public random(): V; 606 | 607 | /** 608 | * Obtains random values from this Enmap. This relies on {@link Enmap#array}. 609 | * @param count Number of values to obtain randomly 610 | * @returns An array of values of `count` length 611 | */ 612 | public random(count: number): V[]; 613 | 614 | /** 615 | * Obtains a random key from this Enmap. This relies on {@link Enmap#keyArray} 616 | * @returns A random key from the Enmap 617 | */ 618 | public randomKey(): K; 619 | 620 | /** 621 | * Obtains random keys from this Enmap. This relies on {@link Enmap#keyArray} 622 | * @param count Number of keys to obtain randomly 623 | * @returns An array of keys of `count` length 624 | */ 625 | public randomKey(count: number): V[]; 626 | 627 | /** 628 | * Searches for all items where their specified property's value is identical to the given value 629 | * (`item[prop] === value`). 630 | * @param prop The property to test against 631 | * @param value The expected value 632 | * @example 633 | * enmap.findAll('username', 'Bob'); 634 | */ 635 | public findAll(prop: string, value: any): V[]; 636 | 637 | /** 638 | * Searches for a single item where the given function returns a truthy value. This is identical to [Array.find()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find). 639 | * All Enmap used in Discord.js are mapped using their `id` property, and if you want to find by id you 640 | * should use the `get` method. See 641 | * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) for details. 642 | * @param fn The function to test with 643 | * @example 644 | * enmap.find(val => val.username === 'Bob'); 645 | */ 646 | public find(fn: (val: V, key: K, enmap: this) => boolean): V | undefined; 647 | 648 | /** 649 | * Searches for a single item where its specified property's value is identical to the given value 650 | * (`item[prop] === value`) 651 | * All Enmap used in Discord.js are mapped using their `id` property, and if you want to find by id you 652 | * should use the `get` method. See 653 | * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) for details. 654 | * @param prop The property to test against. 655 | * @param value The expected value 656 | * @example 657 | * enmap.find('username', 'Bob'); 658 | */ 659 | public find(prop: string, value: any): V | undefined; 660 | 661 | /** 662 | * Searches for the key of a single item where the given function returns a truthy value. This is identical to 663 | * [Array.findIndex()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex). 664 | * @param fn The function to test with. 665 | * @example 666 | * enmap.findKey(val => val.username === 'Bob'); 667 | */ 668 | public findKey(fn: (val: V, key: K, enmap: this) => boolean): K | undefined; 669 | /** 670 | * Searches for the key of a single item where its specified property's value is identical to the given value 671 | * (`item[prop] === value`), or the given function returns a truthy value. In the latter case, 672 | * @param prop The property to test against, or the function to test with 673 | * @param value The expected value - only applicable and required if using a property for the first argument 674 | * @example 675 | * enmap.findKey('username', 'Bob'); 676 | */ 677 | public findKey(prop: string, value: any): K | undefined; 678 | 679 | /** 680 | * Searches for the existence of a single item where its specified property's value is identical to the given value 681 | * (`item[prop] === value`). 682 | * Do not use this to check for an item by its ID. Instead, use `enmap.has(id)`. See 683 | * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) for details. 684 | * @param prop The property to test against 685 | * @param value The expected value 686 | * @example 687 | * if (enmap.exists('username', 'Bob')) { 688 | * console.log('user here!'); 689 | * } 690 | */ 691 | public exists(prop: string, value: any): boolean; 692 | 693 | /** 694 | * Removes entries that satisfy the provided filter function. 695 | * @param fn Function used to test (should return a boolean) 696 | * @param thisArg Value to use as `this` when executing function 697 | * @returns The number of removed entries 698 | */ 699 | public sweep( 700 | fn: (val: V, key: K, enmap: this) => boolean, 701 | thisArg?: any, 702 | ): number; 703 | 704 | /** 705 | * Identical to [Array.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter), 706 | * but returns a Enmap instead of an Array. 707 | * @param fn Function used to test (should return a boolean) 708 | * @param thisArg Value to use as `this` when executing function 709 | */ 710 | public filter( 711 | fn: (val: V, key: K, enmap: this) => boolean, 712 | thisArg?: any, 713 | ): this; 714 | 715 | /** 716 | * Identical to 717 | * [Array.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter). 718 | * @param fn Function used to test (should return a boolean) 719 | * @param thisArg Value to use as `this` when executing function 720 | */ 721 | public filterArray( 722 | fn: (val: V, key: K, enmap: this) => boolean, 723 | thisArg?: any, 724 | ): V[]; 725 | 726 | /** 727 | * Partitions the collection into two collections where the first collection 728 | * contains the items that passed and the second contains the items that failed. 729 | * @param fn Function used to test (should return a boolean) 730 | * @param thisArg Value to use as `this` when executing function 731 | * @example 732 | * const [big, small] = collection.partition(guild => guild.memberCount > 250); 733 | */ 734 | public partition( 735 | fn: (val: V, key: K, enmap: this) => boolean, 736 | thisArg?: any, 737 | ): [Enmap, Enmap]; 738 | 739 | /** 740 | * Identical to 741 | * [Array.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map). 742 | * @param fn Function that produces an element of the new array, taking three arguments 743 | * @param thisArg Value to use as `this` when executing function 744 | */ 745 | public map(fn: (val: V, key: K, enmap: this) => R, thisArg?: any): R[]; 746 | 747 | /** 748 | * Identical to 749 | * [Array.some()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some). 750 | * @param fn Function used to test (should return a boolean) 751 | * @param thisArg Value to use as `this` when executing function 752 | */ 753 | public some( 754 | fn: (val: V, key: K, enmap: this) => boolean, 755 | thisArg?: any, 756 | ): boolean; 757 | 758 | /** 759 | * Identical to 760 | * [Array.every()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every). 761 | * @param fn Function used to test (should return a boolean) 762 | * @param thisArg Value to use as `this` when executing function 763 | */ 764 | public every( 765 | fn: (val: V, key: K, enmap: this) => boolean, 766 | thisArg?: any, 767 | ): boolean; 768 | 769 | /** 770 | * Identical to 771 | * [Array.reduce()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce). 772 | * @param fn Function used to reduce, taking four arguments; `accumulator`, `currentValue`, `currentKey`, and `enmap` 773 | * @param initialValue Starting value for the accumulator 774 | */ 775 | public reduce( 776 | fn: (acc: V, val: V, key: K, enmap: this) => V, 777 | initialValue?: V, 778 | ): V; 779 | 780 | /** 781 | * Identical to 782 | * [Array.reduce()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce). 783 | * @param fn Function used to reduce, taking four arguments; `accumulator`, `currentValue`, `currentKey`, and `enmap` 784 | * @param initialValue Starting value for the accumulator 785 | */ 786 | public reduce( 787 | fn: (acc: R, val: V, key: K, enmap: this) => R, 788 | initialValue: R, 789 | ): R; 790 | 791 | /** 792 | * Creates an identical shallow copy of this Enmap. 793 | * @example 794 | * const newColl = someColl.clone(); 795 | */ 796 | public clone(): Enmap; 797 | 798 | /** 799 | * Combines this Enmap with others into a new Enmap. None of the source Enmaps are modified. 800 | * @param enmaps Enmaps to merge 801 | * @example 802 | * const newColl = someColl.concat(someOtherColl, anotherColl, ohBoyAColl); 803 | */ 804 | public concat(...enmaps: Enmap[]): Enmap; 805 | 806 | /** 807 | * Checks if this Enmap shares identical key-value pairings with another. 808 | * This is different to checking for equality using equal-signs, because 809 | * the Enmaps may be different objects, but contain the same data. 810 | * @param enmap Enmap to compare with 811 | * @returns Whether the Enmaps have identical contents 812 | */ 813 | public equals(enmap: Enmap): boolean; 814 | } 815 | } 816 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | coverage: { 6 | reporter: ['text', 'lcov', 'clover'], 7 | exclude: ['scripts', 'coverage'], 8 | clean: false, 9 | }, 10 | }, 11 | }); 12 | --------------------------------------------------------------------------------