├── docs ├── blog-posts │ ├── README.md │ ├── josh.md │ ├── enmaps-history.md │ ├── why-sqlite-only.md │ └── why-remove-cache.md ├── assets │ ├── favicon.ico │ ├── favicon.png │ ├── nodejs-gyp-install.png │ └── enmap-logo.svg ├── retype.yml ├── usage │ ├── using-enmap.multi.md │ ├── math.md │ ├── arrays.md │ ├── basic.md │ ├── objects.md │ ├── paths.md │ ├── README.md │ ├── serialize.md │ └── using-from-multiple-files.md ├── SUMMARY.md ├── sponsors.md ├── install │ ├── troubleshooting-guide.md │ ├── README.md │ ├── upgrade.md │ └── upgradev6.md ├── help │ └── CJStoESM.md ├── typedoc │ ├── interfaces │ │ └── EnmapOptions.md │ ├── README.md │ ├── usage.md │ └── classes │ │ └── default.md ├── README.md ├── retype.manifest └── api.md ├── .prettierignore ├── .prettierrc ├── CONTRIBUTING.md ├── vitest.config.ts ├── .gitignore ├── src └── error.ts ├── .github └── workflows │ └── ci.yml ├── tsconfig.json ├── test ├── error.spec.js └── index.spec.js ├── .eslintrc.json ├── typedoc.json ├── package.json ├── README.md ├── scripts └── generate-docs-enhanced.js └── LICENSE /docs/blog-posts/README.md: -------------------------------------------------------------------------------- 1 | # Blog Posts 2 | 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | yarn.lock 4 | docs -------------------------------------------------------------------------------- /docs/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eslachance/Enmap/HEAD/docs/assets/favicon.ico -------------------------------------------------------------------------------- /docs/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eslachance/Enmap/HEAD/docs/assets/favicon.png -------------------------------------------------------------------------------- /docs/assets/nodejs-gyp-install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eslachance/Enmap/HEAD/docs/assets/nodejs-gyp-install.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "semi": true, 4 | "singleQuote": true, 5 | "trailingComma": "all", 6 | "endOfLine": "lf" 7 | } 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | - how to clone the app 2 | - how to run in dev 3 | - how to test using npm link 4 | - updating tests 5 | - running docs 6 | - retype needs `npm install retypeapp --global` to work. -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/error.ts: -------------------------------------------------------------------------------- 1 | class CustomError extends Error { 2 | constructor(message: string, name: string | null = 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "lib": ["ES2022"], 7 | "outDir": "./dist", 8 | "rootDir": "./src", 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "skipLibCheck": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "declaration": true, 15 | "declarationMap": true, 16 | "sourceMap": true, 17 | "removeComments": false, 18 | "resolveJsonModule": true, 19 | "useDefineForClassFields": false, 20 | "experimentalDecorators": true, 21 | "emitDecoratorMetadata": true 22 | }, 23 | "include": [ 24 | "src/**/*" 25 | ], 26 | "exclude": [ 27 | "node_modules", 28 | "dist", 29 | "test", 30 | "docs", 31 | "typings" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /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 | * 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. 9 | 10 | Below, an example that uses destructuring: 11 | 12 | ```javascript 13 | import Enmap from 'enmap'; 14 | const { settings, tags, blacklist } = Enmap.multi(['settings', 'tags', 'blacklist']); 15 | 16 | // Attaching to an existing object (for instance some API's client) 17 | import Enmap from 'enmap'; 18 | Object.assign(client, Enmap.multi(["settings", "tags", "blacklist"])); 19 | ``` 20 | -------------------------------------------------------------------------------- /test/error.spec.js: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import CustomError from '../src/error.ts'; 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 | -------------------------------------------------------------------------------- /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 | * [TypeScript Documentation](typedoc/README.md) 18 | * [Usage](typedoc/usage.md) 19 | * [Options](typedoc/interfaces/EnmapOptions.md) 20 | * [Class Instance](typedoc/classes/default.md) 21 | * [Blog Posts](blog-posts/README.md) 22 | * [Enmap's History](blog-posts/enmaps-history.md) 23 | * [V4: Why SQLITE only?](blog-posts/why-sqlite-only.md) 24 | * [V6: Why remove cache?](blog-posts/why-remove-cache.md) 25 | * [Enmap and Josh](blog-posts/josh.md) 26 | 27 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["./src/index.ts"], 3 | "out": "./docs/typedoc", 4 | "plugin": ["typedoc-plugin-markdown"], 5 | "name": "Enmap API Reference", 6 | "readme": "none", 7 | "includeVersion": true, 8 | "excludePrivate": true, 9 | "excludeProtected": true, 10 | "excludeInternal": true, 11 | "hideGenerator": true, 12 | "sort": ["source-order"], 13 | "kindSortOrder": [ 14 | "Project", 15 | "Module", 16 | "Namespace", 17 | "Enum", 18 | "EnumMember", 19 | "Class", 20 | "Interface", 21 | "TypeAlias", 22 | "Constructor", 23 | "Property", 24 | "Variable", 25 | "Function", 26 | "Accessor", 27 | "Method", 28 | "Parameter", 29 | "TypeParameter", 30 | "TypeLiteral", 31 | "CallSignature", 32 | "ConstructorSignature", 33 | "IndexSignature", 34 | "GetSignature", 35 | "SetSignature" 36 | ], 37 | "gitRevision": "main", 38 | "githubPages": false, 39 | "cleanOutputDir": false, 40 | "sanitizeComments": true, 41 | "preserveAnchorCasing": false, 42 | "useCodeBlocks": true, 43 | "expandObjects": false, 44 | "parametersFormat": "table", 45 | "propertiesFormat": "table", 46 | "enumMembersFormat": "table", 47 | "textContentMappings": { 48 | "title.indexPage": "API Reference", 49 | "title.memberPage": "{name}" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /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 | 27 | ## My Question Isn't Answered Here! 28 | 29 | 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). 30 | -------------------------------------------------------------------------------- /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/typedoc/interfaces/EnmapOptions.md: -------------------------------------------------------------------------------- 1 | [**Enmap API Reference v6.1.3**](../README.md) 2 | 3 | *** 4 | 5 | [Enmap API Reference](../README.md) / EnmapOptions 6 | 7 | # EnmapOptions (V, SV) 8 | 9 | Defined in: [index.ts:29](https://github.com/eslachance/enmap/blob/main/src/index.ts#L29) 10 | 11 | ## Type Parameters 12 | 13 | | Type Parameter | Default type | 14 | | ------ | ------ | 15 | | `V` | `unknown` | 16 | | `SV` | `unknown` | 17 | 18 | ## Properties 19 | 20 | | Property | Type | Defined in | 21 | | ------ | ------ | ------ | 22 | | `name?` | `string` | [index.ts:30](https://github.com/eslachance/enmap/blob/main/src/index.ts#L30) | 23 | | `dataDir?` | `string` | [index.ts:31](https://github.com/eslachance/enmap/blob/main/src/index.ts#L31) | 24 | | `ensureProps?` | `boolean` | [index.ts:32](https://github.com/eslachance/enmap/blob/main/src/index.ts#L32) | 25 | | `autoEnsure?` | `V` | [index.ts:33](https://github.com/eslachance/enmap/blob/main/src/index.ts#L33) | 26 | | `serializer?` | (`value`, `key`) => `SV` | [index.ts:34](https://github.com/eslachance/enmap/blob/main/src/index.ts#L34) | 27 | | `deserializer?` | (`value`, `key`) => `V` | [index.ts:35](https://github.com/eslachance/enmap/blob/main/src/index.ts#L35) | 28 | | `inMemory?` | `boolean` | [index.ts:36](https://github.com/eslachance/enmap/blob/main/src/index.ts#L36) | 29 | | `sqliteOptions?` | `Options` | [index.ts:37](https://github.com/eslachance/enmap/blob/main/src/index.ts#L37) | 30 | -------------------------------------------------------------------------------- /docs/typedoc/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: code 3 | expanded: true 4 | order: 90 5 | --- 6 | 7 | # API Reference 8 | 9 | Complete TypeScript API documentation for Enmap with full type information. 10 | 11 | :::hint{type="info"} 12 | This documentation is automatically generated from the TypeScript source code and includes complete type signatures, generics, and cross-references. 13 | ::: 14 | 15 | ## Quick Navigation 16 | 17 | ### Core Classes 18 | - **[Enmap Class](classes/default.md)** - Main database class with all methods 19 | 20 | ### Interfaces 21 | - **[EnmapOptions](interfaces/EnmapOptions.md)** - Configuration options for Enmap 22 | 23 | --- 24 | 25 | ## Key Features 26 | 27 | ### TypeScript Support 28 | - **Full type safety** with generics `Enmap` 29 | - **Custom types** like `Path` for nested object access 30 | - **Precise return types** and parameter validation 31 | 32 | ### Method Categories 33 | 34 | #### Core Operations 35 | - `set()`, `get()`, `has()`, `delete()`, `clear()` 36 | - Type-safe with optional path parameters 37 | 38 | #### Array Operations 39 | - `push()`, `includes()`, `remove()` 40 | - Full array method equivalents: `map()`, `filter()`, `find()`, etc. 41 | 42 | #### Mathematical Operations 43 | - `math()`, `inc()`, `dec()` 44 | - Type-constrained to numeric values 45 | 46 | #### Advanced Features 47 | - `observe()` - Reactive objects that auto-save 48 | - `ensure()` - Type-safe default value assignment 49 | - `update()` - React-style object merging 50 | 51 | --- 52 | 53 | :::tip{type="success"} 54 | The TypeScript API documentation includes live examples, full method signatures, and links to source code for every feature. 55 | ::: 56 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "enmap", 3 | "version": "6.1.3", 4 | "description": "A simple database wrapper to make sqlite database interactions much easier for beginners, with additional array helper methods.", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "type": "module", 8 | "scripts": { 9 | "build": "tsc", 10 | "dev": "tsc --watch", 11 | "prepublishOnly": "npm run build", 12 | "test": "vitest run", 13 | "docs": "node ./scripts/generate-docs-enhanced.js all", 14 | "docs:markdown": "node ./scripts/generate-docs-enhanced.js markdown", 15 | "docs:typedoc": "node ./scripts/generate-docs-enhanced.js typedoc", 16 | "docs:retype": "node ./scripts/generate-docs-enhanced.js retype", 17 | "retype": "cd docs && npx retype build" 18 | }, 19 | "files": [ 20 | "dist", 21 | "src" 22 | ], 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/eslachance/enmap.git" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/eslachance/enmap/issues" 29 | }, 30 | "homepage": "https://enmap.alterion.dev/", 31 | "keywords": [ 32 | "sqlite", 33 | "sql", 34 | "sqlite3", 35 | "better-sqlite3", 36 | "db", 37 | "database", 38 | "caching", 39 | "storing", 40 | "easy", 41 | "quick", 42 | "simple", 43 | "wrapper" 44 | ], 45 | "author": "Eric-Sebastien Lachance (https://alterion.dev/)", 46 | "license": "Apache-2.0", 47 | "dependencies": { 48 | "better-serialize": "^2.0.0", 49 | "better-sqlite3": "^12.2.0", 50 | "lodash-es": "^4.17.21", 51 | "on-change": "^5.0.1" 52 | }, 53 | "devDependencies": { 54 | "@types/better-sqlite3": "^7.6.13", 55 | "@types/lodash-es": "^4.17.12", 56 | "@types/node": "^20.10.0", 57 | "@vitest/coverage-v8": "^1.6.0", 58 | "eslint": "^8.41.0", 59 | "eslint-config-prettier": "^8.8.0", 60 | "eslint-plugin-node": "^11.1.0", 61 | "jsdoc-to-markdown": "^8.0.0", 62 | "limax": "^4.0.0", 63 | "retype": "^0.2.0", 64 | "typedoc": "^0.28.11", 65 | "typedoc-plugin-markdown": "^4.8.1", 66 | "typescript": "^5.9.2", 67 | "vitest": "^1.6.0" 68 | }, 69 | "engines": { 70 | "node": ">=20.0.0" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /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/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/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/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 | import Enmap from '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 | import Enmap from 'enmap'; 20 | 21 | // Normal enmap with default options 22 | const myEnmap = new Enmap({ name: "points" }); 23 | const otherEnmap = new Enmap({ name: "settings" }); 24 | 25 | // In-memory enmap, lost when the app reboots 26 | const ephemeral = new Enmap({ inMemory: true }); 27 | ``` 28 | 29 | ### Enmap Options 30 | 31 | The following is a list of all options that are available in Enmap, when initializing it: 32 | 33 | * `name`: A name for the enmap. Defines the table name in SQLite \(the name is "cleansed" before use\). 34 | * `inMemory`: Defaults to `false`. If set to `true`, no data is saved to disk. Methods will work the same but restarting your app will lose all data. This can be set separately in each enmap. 35 | * `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! 36 | * `ensureProps`: Defaults to `true`. When adding values to an object using a `path`, ensureProps will automatically create any level of object necessary for the value to be written. 37 | * `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. 38 | * `serializer` 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). 39 | * `deserializer` 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). 40 | * `sqliteOptions` Optional. An object of [options](https://github.com/WiseLibs/better-sqlite3/blob/HEAD/docs/api.md#new-databasepath-options) to pass to the better-sqlite3 Database constructor. 41 | -------------------------------------------------------------------------------- /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/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.alterion.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.alterion.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/typedoc/usage.md: -------------------------------------------------------------------------------- 1 | # Typescript Usage 2 | 3 | This page describes how to use Enmap from a typescript perspective, if you want strong types for data stored in Enmap. 4 | 5 | Let's not rehash what's described in the [basics](../usage/README.md) for the Enmap Options, and instead concentrate on the typescript part of this. 6 | 7 | Other than Generics, just consider that methods *should* return the proper value according to the interface provided in your generics. 8 | 9 | ## Basic Example 10 | 11 | Let's get down to brass tacks: Enmap accepts Generics. Here's an example: 12 | 13 | ```ts 14 | import Enmap from 'enmap'; 15 | 16 | interface Something { 17 | a: string; 18 | num: number; 19 | } 20 | 21 | const example = new Enmap({ name: "something" }); 22 | ``` 23 | 24 | ## Full Example 25 | 26 | Here's a complete example from a blog platform scaffold I'm working on, with several types and Enmap instances: 27 | 28 | ```ts 29 | import Enmap from 'enmap'; 30 | 31 | export interface User { 32 | id: string; 33 | username: string; 34 | email: string; 35 | passwordHash: string; 36 | role: 'admin' | 'author' | 'user'; 37 | createdAt: Date; 38 | updatedAt: Date; 39 | } 40 | 41 | export interface Post { 42 | id: string; 43 | title: string; 44 | slug: string; 45 | content: string; 46 | excerpt: string; 47 | authorId: string; 48 | published: boolean; 49 | publishedAt?: Date; 50 | createdAt: Date; 51 | updatedAt: Date; 52 | tags: string[]; 53 | featuredImage?: string; 54 | } 55 | 56 | export interface Comment { 57 | id: string; 58 | postId: string; 59 | authorName: string; 60 | authorEmail: string; 61 | content: string; 62 | approved: boolean; 63 | createdAt: Date; 64 | updatedAt: Date; 65 | } 66 | 67 | export interface Session { 68 | id: string; 69 | userId: string; 70 | expiresAt: Date; 71 | createdAt: Date; 72 | } 73 | 74 | // Initialize database tables 75 | export const users = new Enmap({ name: 'users' }); 76 | export const posts = new Enmap({ name: 'posts' }); 77 | export const comments = new Enmap({ name: 'comments' }); 78 | export const sessions = new Enmap({ name: 'sessions' }); 79 | ``` 80 | 81 | ## Serialized Value 82 | 83 | Enmap accepts a second generic, which is the "Serialized Value". As see in [Serializing Values](../usage/serialize.md), you can define a function that runs on data before actually saving it to the database - this is useful when you have data that can't just be sent through json serialization (class instances, etc). If you're using such a method, you're going to have to actually define the serialized value shape. 84 | 85 | Here's a basic example: 86 | 87 | ```ts 88 | import Enmap from 'enmap'; 89 | 90 | class GoodGreeter { 91 | name: string; 92 | 93 | constructor() { 94 | this.name = "hello"; 95 | } 96 | } 97 | 98 | interface BaseGreeter { 99 | greeter: GoodGreeter; 100 | } 101 | 102 | interface SerializedGreeter { 103 | greeter: { 104 | name: string; 105 | } 106 | } 107 | 108 | const example = new Enmap({ 109 | name: 'greeter', 110 | serializer: (data: BaseGreeter): SerializedGreeter => data.name; 111 | deserializer: (data: SerializedGreeter): BaseGreeter => new GoodGreeter(data); 112 | }); 113 | ``` 114 | 115 | -------------------------------------------------------------------------------- /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/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/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 18, 20, 22 but will most likely _not_ work on 17, 19, 21. 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 | **The Easy Way**: When installing NodeJS, in the "Tools for native modules", make sure to check the option "Automatically install the necessary tools". This will install the required dependencies. 24 | 25 | ![](/assets/nodejs-gyp-install.png "Installing build tools in nodejs") 26 | 27 | **The slightly harder way**: If you've already installed nodejs and don't wish to reinstall it, you can install the tools using [choco](https://chocolatey.org/install). Once choco is installed, simply run the following command, _under an **administrative** command prompt or powershell:_ 28 | 29 | ```cmd 30 | choco install python visualstudio2022-workload-vctools -y 31 | ``` 32 | 33 | !!!danger Danger 34 | If you get an error here READ THIS: It's _very important_ that this be run in the **administrative** prompt, and not a regular one. Run as admin. 35 | !!! 36 | 37 | Once the 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. 38 | {% endtab %} 39 | 40 | {% tab title="Linux" %} 41 | 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. 42 | 43 | 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". 44 | {% endtab %} 45 | 46 | {% tab title="Mac OS" %} 47 | 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. 48 | 49 | * Install [XCode](https://developer.apple.com/xcode/download/) 50 | * Once XCode is installed, go to **Preferences**, **Downloads**, and install the **Command Line Tools**. 51 | 52 | Once installed, you're ready to continue. 53 | {% endtab %} 54 | {% endtabs %} 55 | 56 | ## Installing Enmap 57 | 58 | Once those pre-requisites are installed 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: 59 | 60 | ```text 61 | npm i enmap 62 | ``` 63 | 64 | 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. 65 | 66 | 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\). 67 | -------------------------------------------------------------------------------- /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 | import Enmap from '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 | import Enmap from '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 | import Enmap from '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 | import Enmap from '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 | import Enmap from '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 | import Enmap from '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 | -------------------------------------------------------------------------------- /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 | Enmap is a simple and efficient wrapper around SQLite, based around the idea of being a Map() with additional array methods added on to it. 17 | 18 | Enmap is: 19 | 20 | - **Anti-ORM**, in that it can store any serializable data, and each value can be of a different shape. Compared to an ORM which strictly defines a shape for your data. 21 | - Fully **typescript compatible**. Accepts generics if you *do* want to define a data shape for your own code. Return values are properly typed according to your generics if provided. 22 | - **Synchronous**. Meaning, it doesn't use callbacks or promises, no await, no .then, just call the method and go. This is thanks to [better-sqlite3][https://npm.im/better-sqlite3]! 23 | - **Modern and maintained**. I try to keep up to date with dependency updates, too add new functionality, and resolve issues, as fast as I can. Even if there's months where I don't update Enmap, it is not abandonned. 24 | 25 | ## Documentation 26 | 27 | * [Installation](https://enmap.alterion.dev/install) 28 | * [Basic Setup](https://enmap.alterion.dev/usage) 29 | * [API Reference](https://enmap.alterion.dev/api) 30 | 31 | ## Support 32 | 33 | Support is offered on my official [Alterion.dev Discord](https://discord.gg/N7ZKH3P). 34 | 35 | ## FAQs 36 | 37 | ### Q: What is "Persistent"? 38 | 39 | **A**: By using a database layer with `better-sqlite3`, any data added to the Enmap 40 | is stored up in a local database. This means that 41 | when you restart your project, your data is not lost and is loaded on startup. 42 | 43 | ### Q: How big can the Enmap be? 44 | 45 | **A**: Theoretically there is no limit to how much data you can store, except for the file system limitations. In most cases, it's not something you need to worry about. 46 | 47 | ### Q: Who did you make this for? 48 | 49 | **A**: Enmap was made specifically for beginners in mind. It's for you, the budding javascript developer that wants to save data 50 | 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 51 | a database but doesn't want to have to deal with queries, even if it's not the most efficient way to do things. 52 | 53 | ### Q: What can it be used for? 54 | 55 | **A**: Enmap is useful for storing very simple key/value data for easy retrieval, and also for more complex objects with many properties. 56 | Mainly, because of who I originally made this for, it's used in Discord.js Bots to save currencies, content blocks, server settings, and 57 | user information for bans, blacklists, timers, warning systems, etc. 58 | 59 | ## Testimonials 60 | 61 | Some user comments! 62 | 63 | > 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. 64 | 65 | > I know how to use a real db, but enmap is so sweet and easy to use 66 | 67 | > Thanks to Enmap, I am able to do tons of things that I never thought I would accomplish. 68 | From custom settings to even just saving the little things, it is amazing to use. 69 | 70 | > Enmap helped me, and it stills helps me, because it is very simple and useful. Thank you for creating Enmap. 71 | 72 | > Without your tutorials I didn't have an internship and some work.. :)) 73 | 74 | > 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. 75 | -------------------------------------------------------------------------------- /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/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.alterion.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 | import { Discord } from 'discord.js'; 26 | const client = new Discord.Client(); 27 | 28 | import Enmap from '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.alterion.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.alterion.dev/modules), modules are fairly straightforward. This is how I have done an Enmap shared module before: 56 | 57 | ```javascript 58 | import Enmap from 'enmap'; 59 | 60 | export default { 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 | import db from './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 | import Enmap from 'enmap'; 95 | 96 | export default { 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.alterion.dev/modules)for more information. 111 | -------------------------------------------------------------------------------- /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/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 | import Enmap from '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 | import Enmap from '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 | - import Enmap from '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/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 | -------------------------------------------------------------------------------- /scripts/generate-docs-enhanced.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Enhanced documentation generation script with TypeScript type support 4 | * 5 | * This script provides options for: 6 | * 1. TypeDoc HTML generation (best TypeScript types) 7 | * 2. JSDoc-to-Markdown generation (for existing workflow) 8 | * 3. Both formats for comparison 9 | */ 10 | 11 | import { execSync } from 'child_process'; 12 | import jsdoc2md from 'jsdoc-to-markdown'; 13 | import { writeFile, readFile, readdir } from 'node:fs/promises'; 14 | import { join } from 'node:path'; 15 | 16 | const apiDocsHeader = `--- 17 | description: >- 18 | The complete and unadultered API documentation for every single method and 19 | property accessible in Enmap. 20 | --- 21 | 22 | # Full Documentation 23 | 24 | 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. 25 | 26 | {% hint style="warning" %} 27 | 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.ts source code and change the comments there instead! 28 | {% endhint %} 29 | 30 | 31 | 32 | ## Enmap Class 33 | 34 | The Enmap class provides a simple, synchronous, fast key/value storage built around better-sqlite3. 35 | Contains extra utility methods for managing arrays and objects. 36 | 37 | `; 38 | 39 | /** 40 | * Post-processes TypeDoc-generated markdown files to fix angle bracket issues for Retype 41 | * This prevents Retype's JavaScript from interpreting TypeScript generics as invalid HTML tags 42 | */ 43 | async function fixMarkdownForRetype(docsPath) { 44 | const fixFile = async (filePath) => { 45 | try { 46 | const content = await readFile(filePath, 'utf-8'); 47 | 48 | // Fix problematic patterns that cause Retype to interpret generics as HTML tags 49 | let fixedContent = content 50 | // Fix headings with generics: "# Class: default\" -> "# Class: default (V, SV)" 51 | .replace(/^(#+\s+.*?)\\<([^>]+)\\>/gm, '$1 ($2)') 52 | // Fix method signatures: "**new default**\<`V`, `SV`\>" -> "**new default** (V, SV)" 53 | .replace(/(\*\*[^*]+\*\*)\\<([^>]+)\\>/g, '$1 ($2)') 54 | // Fix inline generics in code: "`Enmap`\<`V`, `SV`\>" -> "`Enmap`" 55 | .replace(/(`[^`]+`)\\<([^>]+)\\>/g, '$1<$2>') 56 | // Fix link references: "[`EnmapOptions`](../interfaces/EnmapOptions.md)\<`V`, `SV`\>" -> "[`EnmapOptions`](../interfaces/EnmapOptions.md)" 57 | .replace(/(\[[^\]]+\]\([^)]+\))\\<([^>]+)\\>/g, '$1<$2>'); 58 | 59 | // Also handle any remaining HTML entities that might cause issues 60 | fixedContent = fixedContent 61 | // Convert problematic HTML entities in headings back to parentheses format 62 | .replace(/^(#+\s+.*?)<([^&]+)>/gm, '$1 ($2)') 63 | // But keep them in code blocks and inline code 64 | .replace(/(`[^`]*?)<([^&]*?)>([^`]*?`)/g, '$1<$2>$3'); 65 | 66 | if (content !== fixedContent) { 67 | await writeFile(filePath, fixedContent); 68 | } 69 | } catch (error) { 70 | console.warn(`⚠️ Could not fix ${filePath}:`, error.message); 71 | } 72 | }; 73 | 74 | const processDirectory = async (dirPath) => { 75 | try { 76 | const entries = await readdir(dirPath, { withFileTypes: true }); 77 | 78 | for (const entry of entries) { 79 | const fullPath = join(dirPath, entry.name); 80 | 81 | if (entry.isDirectory()) { 82 | await processDirectory(fullPath); 83 | } else if (entry.name.endsWith('.md')) { 84 | await fixFile(fullPath); 85 | } 86 | } 87 | } catch (error) { 88 | console.warn(`⚠️ Could not process directory ${dirPath}:`, error.message); 89 | } 90 | }; 91 | 92 | await processDirectory(docsPath); 93 | } 94 | 95 | async function generateJSDocMarkdown() { 96 | console.log('📚 Generating JSDoc-to-Markdown documentation...'); 97 | 98 | // Compile TypeScript first 99 | console.log('📦 Compiling TypeScript...'); 100 | execSync('npm run build', { stdio: 'inherit' }); 101 | 102 | // Generate JSDoc markdown 103 | const rendered = await jsdoc2md.render({ 104 | files: './dist/index.js', 105 | }); 106 | 107 | if (!rendered || rendered.trim().length === 0) { 108 | console.warn('⚠️ Warning: JSDoc generated empty output.'); 109 | return ''; 110 | } 111 | 112 | // Process and format the documentation 113 | const processedOutput = rendered 114 | .replace(/## Members/g, '## Properties') 115 | .replace(/## Functions/g, '## Methods') 116 | .replace(/\*\*Kind\*\*: global function/g, '**Kind**: instance method of Enmap') 117 | .replace(/\*\*Kind\*\*: global variable/g, '**Kind**: instance property of Enmap') 118 | .replace(/\n{3,}/g, '\n\n'); 119 | 120 | const finalOutput = apiDocsHeader + processedOutput; 121 | await writeFile('./docs/api.md', finalOutput, 'utf8'); 122 | 123 | console.log('✅ JSDoc-to-Markdown documentation saved to ./docs/api.md'); 124 | return finalOutput; 125 | } 126 | 127 | async function generateTypeDoc() { 128 | console.log('🔷 Generating TypeDoc HTML documentation...'); 129 | 130 | try { 131 | execSync('npx typedoc --out ./docs-typedoc --plugin default', { stdio: 'inherit' }); 132 | console.log('✅ TypeDoc HTML documentation saved to ./docs-typedoc/'); 133 | return true; 134 | } catch (error) { 135 | console.error('❌ TypeDoc HTML generation failed:', error.message); 136 | return false; 137 | } 138 | } 139 | 140 | async function generateTypeDocMarkdown() { 141 | console.log('📝 Generating TypeDoc Markdown for Retype...'); 142 | 143 | try { 144 | execSync('npx typedoc', { stdio: 'inherit' }); 145 | 146 | // Post-process the generated markdown files to fix angle bracket issues 147 | await fixMarkdownForRetype('./docs/typedoc'); 148 | 149 | // Create the README.md file for the typedoc folder 150 | const retypeReadmeContent = `--- 151 | icon: code 152 | expanded: true 153 | order: 90 154 | --- 155 | 156 | # API Reference 157 | 158 | Complete TypeScript API documentation for Enmap with full type information. 159 | 160 | :::hint{type="info"} 161 | This documentation is automatically generated from the TypeScript source code and includes complete type signatures, generics, and cross-references. 162 | ::: 163 | 164 | ## Quick Navigation 165 | 166 | ### Core Classes 167 | - **[Enmap Class](classes/default.md)** - Main database class with all methods 168 | 169 | ### Interfaces 170 | - **[EnmapOptions](interfaces/EnmapOptions.md)** - Configuration options for Enmap 171 | 172 | --- 173 | 174 | ## Key Features 175 | 176 | ### TypeScript Support 177 | - **Full type safety** with generics \`Enmap\` 178 | - **Custom types** like \`Path\` for nested object access 179 | - **Precise return types** and parameter validation 180 | 181 | ### Method Categories 182 | 183 | #### Core Operations 184 | - \`set()\`, \`get()\`, \`has()\`, \`delete()\`, \`clear()\` 185 | - Type-safe with optional path parameters 186 | 187 | #### Array Operations 188 | - \`push()\`, \`includes()\`, \`remove()\` 189 | - Full array method equivalents: \`map()\`, \`filter()\`, \`find()\`, etc. 190 | 191 | #### Mathematical Operations 192 | - \`math()\`, \`inc()\`, \`dec()\` 193 | - Type-constrained to numeric values 194 | 195 | #### Advanced Features 196 | - \`observe()\` - Reactive objects that auto-save 197 | - \`ensure()\` - Type-safe default value assignment 198 | - \`update()\` - React-style object merging 199 | 200 | --- 201 | 202 | :::tip{type="success"} 203 | The TypeScript API documentation includes live examples, full method signatures, and links to source code for every feature. 204 | ::: 205 | `; 206 | 207 | // Write the README.md into the typedoc folder 208 | await writeFile('./docs/typedoc/README.md', retypeReadmeContent); 209 | 210 | console.log('✅ TypeDoc Markdown documentation saved to ./docs/typedoc/'); 211 | console.log('✅ Retype README.md created at ./docs/typedoc/README.md'); 212 | console.log('✅ Fixed angle brackets in markdown files for Retype compatibility'); 213 | return true; 214 | } catch (error) { 215 | console.error('❌ TypeDoc Markdown generation failed:', error.message); 216 | return false; 217 | } 218 | } 219 | 220 | async function generateAllFormats() { 221 | console.log('🔄 Generating all documentation formats...\n'); 222 | 223 | const [markdownOutput, typedocSuccess, retypeSuccess] = await Promise.all([ 224 | generateJSDocMarkdown(), 225 | generateTypeDoc(), 226 | generateTypeDocMarkdown(), 227 | ]); 228 | 229 | console.log('\n📊 Documentation Generation Summary:'); 230 | console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); 231 | 232 | if (markdownOutput) { 233 | const methodCount = (markdownOutput.match(/## \w+\(/g) || []).length; 234 | const propertyCount = (markdownOutput.match(/⇒ \w+<\/code>/g) || []).length; 235 | 236 | console.log('📄 JSDoc-to-Markdown:'); 237 | console.log(` • Output: ./docs/api.md`); 238 | console.log(` • Content: ${markdownOutput.length} characters`); 239 | console.log(` • Methods: ${methodCount}`); 240 | console.log(` • Properties: ${propertyCount}`); 241 | console.log(` • Use: npm run docs:markdown`); 242 | } 243 | 244 | if (typedocSuccess) { 245 | console.log('\n🔷 TypeDoc HTML:'); 246 | console.log(` • Output: ./docs-typedoc/`); 247 | console.log(` • Features: Rich HTML interface with search`); 248 | console.log(` • Types: Full TypeScript type information`); 249 | console.log(` • Use: npm run docs:typedoc`); 250 | console.log(` • View: Open ./docs-typedoc/index.html in browser`); 251 | } 252 | 253 | if (retypeSuccess) { 254 | console.log('\n📖 Retype Integration:'); 255 | console.log(` • Output: ./docs/typedoc/ (markdown)`); 256 | console.log(` • Features: TypeScript types + Retype styling`); 257 | console.log(` • Integration: ./docs/typedoc/README.md`); 258 | console.log(` • Use: npm run docs:retype`); 259 | console.log(` • View: Your existing Retype documentation site`); 260 | } 261 | 262 | console.log('\n💡 Recommendations:'); 263 | console.log(' • Use Retype integration for your main docs (best of both worlds!)'); 264 | console.log(' • Use TypeDoc HTML for development/API reference'); 265 | console.log(' • JSDoc Markdown as fallback for existing workflows'); 266 | } 267 | 268 | // Parse command line arguments 269 | const args = process.argv.slice(2); 270 | const format = args[0]; 271 | 272 | async function main() { 273 | try { 274 | switch (format) { 275 | case 'markdown': 276 | case 'jsdoc': 277 | await generateJSDocMarkdown(); 278 | break; 279 | case 'typedoc': 280 | case 'html': 281 | await generateTypeDoc(); 282 | break; 283 | case 'retype': 284 | await generateTypeDocMarkdown(); 285 | break; 286 | case 'all': 287 | case 'both': 288 | case undefined: 289 | await generateAllFormats(); 290 | break; 291 | default: 292 | console.log('Usage: node generate-docs-enhanced.js [format]'); 293 | console.log('Formats: markdown, typedoc, retype, all (default)'); 294 | console.log(''); 295 | console.log('Examples:'); 296 | console.log(' node generate-docs-enhanced.js markdown # JSDoc markdown only'); 297 | console.log(' node generate-docs-enhanced.js typedoc # TypeDoc HTML only'); 298 | console.log(' node generate-docs-enhanced.js retype # TypeDoc markdown for Retype'); 299 | console.log(' node generate-docs-enhanced.js all # All formats'); 300 | process.exit(1); 301 | } 302 | } catch (error) { 303 | console.error('❌ Documentation generation failed:', error.message); 304 | process.exit(1); 305 | } 306 | } 307 | 308 | main(); 309 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/index.ts'; 9 | import { mkdir, rm } from 'fs/promises'; 10 | import CustomError from '../src/error.ts'; 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 | -------------------------------------------------------------------------------- /docs/typedoc/classes/default.md: -------------------------------------------------------------------------------- 1 | [**Enmap API Reference v6.1.3**](../README.md) 2 | 3 | *** 4 | 5 | [Enmap API Reference](../README.md) / default 6 | 7 | # default (V, SV) 8 | 9 | Defined in: [index.ts:77](https://github.com/eslachance/enmap/blob/main/src/index.ts#L77) 10 | 11 | A simple, synchronous, fast key/value storage build around better-sqlite3. 12 | Contains extra utility methods for managing arrays and objects. 13 | 14 | ## Type Parameters 15 | 16 | | Type Parameter | Default type | 17 | | ------ | ------ | 18 | | `V` | `any` | 19 | | `SV` | `unknown` | 20 | 21 | ## Constructors 22 | 23 | ### Constructor 24 | 25 | ```ts 26 | new default(options): Enmap; 27 | ``` 28 | 29 | Defined in: [index.ts:109](https://github.com/eslachance/enmap/blob/main/src/index.ts#L109) 30 | 31 | Initializes a new Enmap, with options. 32 | 33 | #### Parameters 34 | 35 | | Parameter | Type | Description | 36 | | ------ | ------ | ------ | 37 | | `options` | [`EnmapOptions`](../interfaces/EnmapOptions.md)<`V`, `SV`> | Options for the enmap. See https://enmap.alterion.dev/usage#enmap-options for details. | 38 | 39 | #### Returns 40 | 41 | `Enmap`<`V`, `SV`> 42 | 43 | #### Example 44 | 45 | ```ts 46 | import Enmap from 'enmap'; 47 | // Named, Persistent enmap 48 | const myEnmap = new Enmap({ name: "testing" }); 49 | 50 | // Memory-only enmap 51 | const memoryEnmap = new Enmap({ inMemory: true }); 52 | 53 | // Enmap that automatically assigns a default object when getting or setting anything. 54 | const autoEnmap = new Enmap({name: "settings", autoEnsure: { setting1: false, message: "default message"}}) 55 | ``` 56 | 57 | ## Accessors 58 | 59 | ### size 60 | 61 | #### Get Signature 62 | 63 | ```ts 64 | get size(): number; 65 | ``` 66 | 67 | Defined in: [index.ts:292](https://github.com/eslachance/enmap/blob/main/src/index.ts#L292) 68 | 69 | Get the number of key/value pairs saved in the enmap. 70 | 71 | ##### Returns 72 | 73 | `number` 74 | 75 | The number of elements in the enmap. 76 | 77 | *** 78 | 79 | ### count 80 | 81 | #### Get Signature 82 | 83 | ```ts 84 | get count(): number; 85 | ``` 86 | 87 | Defined in: [index.ts:300](https://github.com/eslachance/enmap/blob/main/src/index.ts#L300) 88 | 89 | ##### Returns 90 | 91 | `number` 92 | 93 | *** 94 | 95 | ### length 96 | 97 | #### Get Signature 98 | 99 | ```ts 100 | get length(): number; 101 | ``` 102 | 103 | Defined in: [index.ts:304](https://github.com/eslachance/enmap/blob/main/src/index.ts#L304) 104 | 105 | ##### Returns 106 | 107 | `number` 108 | 109 | *** 110 | 111 | ### db 112 | 113 | #### Get Signature 114 | 115 | ```ts 116 | get db(): Database; 117 | ``` 118 | 119 | Defined in: [index.ts:313](https://github.com/eslachance/enmap/blob/main/src/index.ts#L313) 120 | 121 | Get the better-sqlite3 database object. Useful if you want to directly query or interact with the 122 | underlying SQLite database. Use at your own risk, as errors here might cause loss of data or corruption! 123 | 124 | ##### Returns 125 | 126 | `Database` 127 | 128 | *** 129 | 130 | ### autonum 131 | 132 | #### Get Signature 133 | 134 | ```ts 135 | get autonum(): string; 136 | ``` 137 | 138 | Defined in: [index.ts:327](https://github.com/eslachance/enmap/blob/main/src/index.ts#L327) 139 | 140 | Generates an automatic numerical key for inserting a new value. 141 | This is a "weak" method, it ensures the value isn't duplicated, but does not 142 | guarantee it's sequential (if a value is deleted, another can take its place). 143 | Useful for logging, actions, items, etc - anything that doesn't already have a unique ID. 144 | 145 | ##### Example 146 | 147 | ```ts 148 | enmap.set(enmap.autonum, "This is a new value"); 149 | ``` 150 | 151 | ##### Returns 152 | 153 | `string` 154 | 155 | The generated key number. 156 | 157 | ## Methods 158 | 159 | ### set() 160 | 161 | ```ts 162 | set( 163 | key, 164 | value, 165 | path?): this; 166 | ``` 167 | 168 | Defined in: [index.ts:195](https://github.com/eslachance/enmap/blob/main/src/index.ts#L195) 169 | 170 | Sets a value in Enmap. If the key already has a value, overwrites the data (or the value in a path, if provided). 171 | 172 | #### Parameters 173 | 174 | | Parameter | Type | Description | 175 | | ------ | ------ | ------ | 176 | | `key` | `string` | Required. The location in which the data should be saved. | 177 | | `value` | `any` | 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. | 178 | | `path?` | `Path`<`V`, keyof `V`> | 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" | 179 | 180 | #### Returns 181 | 182 | `this` 183 | 184 | #### Example 185 | 186 | ```ts 187 | // Direct Value Examples 188 | enmap.set('simplevalue', 'this is a string'); 189 | enmap.set('isEnmapGreat', true); 190 | enmap.set('TheAnswer', 42); 191 | enmap.set('IhazObjects', { color: 'black', action: 'paint', desire: true }); 192 | enmap.set('ArraysToo', [1, "two", "tree", "foor"]) 193 | 194 | // Settings Properties 195 | enmap.set('IhazObjects', 'blue', 'color'); //modified previous object 196 | enmap.set('ArraysToo', 'three', 2); // changes "tree" to "three" in array. 197 | ``` 198 | 199 | *** 200 | 201 | ### get() 202 | 203 | ```ts 204 | get(key, path?): any; 205 | ``` 206 | 207 | Defined in: [index.ts:221](https://github.com/eslachance/enmap/blob/main/src/index.ts#L221) 208 | 209 | Retrieves a value from the enmap, using its key. 210 | 211 | #### Parameters 212 | 213 | | Parameter | Type | Description | 214 | | ------ | ------ | ------ | 215 | | `key` | `string` | The key to retrieve from the enmap. | 216 | | `path?` | `Path`<`V`, keyof `V`> | Optional. The property to retrieve from the object or array. Should be a path with dot notation, such as "prop1.subprop2.subprop3" | 217 | 218 | #### Returns 219 | 220 | `any` 221 | 222 | The parsed value for this key. 223 | 224 | #### Example 225 | 226 | ```ts 227 | const myKeyValue = enmap.get("myKey"); 228 | console.log(myKeyValue); 229 | 230 | const someSubValue = enmap.get("anObjectKey", "someprop.someOtherSubProp"); 231 | ``` 232 | 233 | *** 234 | 235 | ### has() 236 | 237 | ```ts 238 | has(key): boolean; 239 | ``` 240 | 241 | Defined in: [index.ts:250](https://github.com/eslachance/enmap/blob/main/src/index.ts#L250) 242 | 243 | Returns whether or not the key exists in the Enmap. 244 | 245 | #### Parameters 246 | 247 | | Parameter | Type | Description | 248 | | ------ | ------ | ------ | 249 | | `key` | `string` | Required. The key of the element to add to The Enmap or array. | 250 | 251 | #### Returns 252 | 253 | `boolean` 254 | 255 | #### Example 256 | 257 | ```ts 258 | if(enmap.has("myKey")) { 259 | // key is there 260 | } 261 | ``` 262 | 263 | *** 264 | 265 | ### delete() 266 | 267 | ```ts 268 | delete(key, path?): this; 269 | ``` 270 | 271 | Defined in: [index.ts:263](https://github.com/eslachance/enmap/blob/main/src/index.ts#L263) 272 | 273 | Deletes a key in the Enmap. 274 | 275 | #### Parameters 276 | 277 | | Parameter | Type | Description | 278 | | ------ | ------ | ------ | 279 | | `key` | `string` | Required. The key of the element to delete from The Enmap. | 280 | | `path?` | `Path`<`V`, keyof `V`> | Optional. The name of the property to remove from the object. Should be a path with dot notation, such as "prop1.subprop2.subprop3" | 281 | 282 | #### Returns 283 | 284 | `this` 285 | 286 | *** 287 | 288 | ### clear() 289 | 290 | ```ts 291 | clear(): void; 292 | ``` 293 | 294 | Defined in: [index.ts:282](https://github.com/eslachance/enmap/blob/main/src/index.ts#L282) 295 | 296 | Deletes everything from the enmap. 297 | 298 | #### Returns 299 | 300 | `void` 301 | 302 | *** 303 | 304 | ### keys() 305 | 306 | ```ts 307 | keys(): string[]; 308 | ``` 309 | 310 | Defined in: [index.ts:348](https://github.com/eslachance/enmap/blob/main/src/index.ts#L348) 311 | 312 | Get all the keys of the enmap as an array. 313 | 314 | #### Returns 315 | 316 | `string`[] 317 | 318 | An array of all the keys in the enmap. 319 | 320 | *** 321 | 322 | ### indexes() 323 | 324 | ```ts 325 | indexes(): string[]; 326 | ``` 327 | 328 | Defined in: [index.ts:357](https://github.com/eslachance/enmap/blob/main/src/index.ts#L357) 329 | 330 | #### Returns 331 | 332 | `string`[] 333 | 334 | *** 335 | 336 | ### values() 337 | 338 | ```ts 339 | values(): V[]; 340 | ``` 341 | 342 | Defined in: [index.ts:365](https://github.com/eslachance/enmap/blob/main/src/index.ts#L365) 343 | 344 | Get all the values of the enmap as an array. 345 | 346 | #### Returns 347 | 348 | `V`[] 349 | 350 | An array of all the values in the enmap. 351 | 352 | *** 353 | 354 | ### entries() 355 | 356 | ```ts 357 | entries(): [string, V][]; 358 | ``` 359 | 360 | Defined in: [index.ts:378](https://github.com/eslachance/enmap/blob/main/src/index.ts#L378) 361 | 362 | Get all entries of the enmap as an array, with each item containing the key and value. 363 | 364 | #### Returns 365 | 366 | \[`string`, `V`\][] 367 | 368 | An array of arrays, with each sub-array containing two items, the key and the value. 369 | 370 | *** 371 | 372 | ### update() 373 | 374 | ```ts 375 | update(key, valueOrFunction): V; 376 | ``` 377 | 378 | Defined in: [index.ts:413](https://github.com/eslachance/enmap/blob/main/src/index.ts#L413) 379 | 380 | Update an existing object value in Enmap by merging new keys. **This only works on objects**, any other value will throw an error. 381 | Heavily inspired by setState from React's class components. 382 | 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. 383 | 384 | #### Parameters 385 | 386 | | Parameter | Type | Description | 387 | | ------ | ------ | ------ | 388 | | `key` | `string` | The key of the object to update. | 389 | | `valueOrFunction` | `Partial`<`V`> \| (`data`) => `V` | 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. | 390 | 391 | #### Returns 392 | 393 | `V` 394 | 395 | The modified (merged) value. 396 | 397 | #### Example 398 | 399 | ```ts 400 | // Define an object we're going to update 401 | enmap.set("obj", { a: 1, b: 2, c: 3 }); 402 | 403 | // Direct merge 404 | enmap.update("obj", { d: 4, e: 5 }); 405 | // obj is now { a: 1, b: 2, c: 3, d: 4, e: 5 } 406 | 407 | // Functional update 408 | enmap.update("obj", (previous) => ({ 409 | ...obj, 410 | f: 6, 411 | g: 7 412 | })); 413 | // this example takes heavy advantage of the spread operators. 414 | // More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax 415 | ``` 416 | 417 | *** 418 | 419 | ### observe() 420 | 421 | ```ts 422 | observe(key, path?): any; 423 | ``` 424 | 425 | Defined in: [index.ts:433](https://github.com/eslachance/enmap/blob/main/src/index.ts#L433) 426 | 427 | Returns an observable object. Modifying this object or any of its properties/indexes/children 428 | will automatically save those changes into enmap. This only works on 429 | objects and arrays, not "basic" values like strings or integers. 430 | 431 | #### Parameters 432 | 433 | | Parameter | Type | Description | 434 | | ------ | ------ | ------ | 435 | | `key` | `string` | The key to retrieve from the enmap. | 436 | | `path?` | `Path`<`V`, keyof `V`> | Optional. The property to retrieve from the object or array. | 437 | 438 | #### Returns 439 | 440 | `any` 441 | 442 | The value for this key. 443 | 444 | *** 445 | 446 | ### push() 447 | 448 | ```ts 449 | push( 450 | key, 451 | value, 452 | path?, 453 | allowDupes?): this; 454 | ``` 455 | 456 | Defined in: [index.ts:457](https://github.com/eslachance/enmap/blob/main/src/index.ts#L457) 457 | 458 | Push to an array value in Enmap. 459 | 460 | #### Parameters 461 | 462 | | Parameter | Type | Default value | Description | 463 | | ------ | ------ | ------ | ------ | 464 | | `key` | `string` | `undefined` | Required. The key of the array element to push to in Enmap. | 465 | | `value` | `V` | `undefined` | Required. The value to push to the array. | 466 | | `path?` | `Path`<`V`, keyof `V`> | `undefined` | 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" | 467 | | `allowDupes?` | `boolean` | `false` | Optional. Allow duplicate values in the array (default: false). | 468 | 469 | #### Returns 470 | 471 | `this` 472 | 473 | #### Example 474 | 475 | ```ts 476 | // Assuming 477 | enmap.set("simpleArray", [1, 2, 3, 4]); 478 | enmap.set("arrayInObject", {sub: [1, 2, 3, 4]}); 479 | 480 | enmap.push("simpleArray", 5); // adds 5 at the end of the array 481 | enmap.push("arrayInObject", "five", "sub"); // adds "five" at the end of the sub array 482 | ``` 483 | 484 | *** 485 | 486 | ### math() 487 | 488 | ```ts 489 | math( 490 | key, 491 | operation, 492 | operand, 493 | path?): null | number; 494 | ``` 495 | 496 | Defined in: [index.ts:487](https://github.com/eslachance/enmap/blob/main/src/index.ts#L487) 497 | 498 | Executes a mathematical operation on a value and saves it in the enmap. 499 | 500 | #### Parameters 501 | 502 | | Parameter | Type | Description | 503 | | ------ | ------ | ------ | 504 | | `key` | `string` | The enmap key on which to execute the math operation. | 505 | | `operation` | `MathOps` | Which mathematical operation to execute. Supports most math ops: =, -, *, /, %, ^, and english spelling of those operations. | 506 | | `operand` | `number` | The right operand of the operation. | 507 | | `path?` | `Path`<`V`, keyof `V`> | Optional. The property path to execute the operation on, if the value is an object or array. | 508 | 509 | #### Returns 510 | 511 | `null` \| `number` 512 | 513 | The updated value after the operation 514 | 515 | #### Example 516 | 517 | ```ts 518 | // Assuming 519 | points.set("number", 42); 520 | points.set("numberInObject", {sub: { anInt: 5 }}); 521 | 522 | points.math("number", "/", 2); // 21 523 | points.math("number", "add", 5); // 26 524 | points.math("number", "modulo", 3); // 2 525 | points.math("numberInObject", "+", 10, "sub.anInt"); 526 | ``` 527 | 528 | *** 529 | 530 | ### inc() 531 | 532 | ```ts 533 | inc(key, path?): this; 534 | ``` 535 | 536 | Defined in: [index.ts:512](https://github.com/eslachance/enmap/blob/main/src/index.ts#L512) 537 | 538 | Increments a key's value or property by 1. Value must be a number, or a path to a number. 539 | 540 | #### Parameters 541 | 542 | | Parameter | Type | Description | 543 | | ------ | ------ | ------ | 544 | | `key` | `string` | The enmap key where the value to increment is stored. | 545 | | `path?` | `Path`<`V`, keyof `V`> | Optional. The property path to increment, if the value is an object or array. | 546 | 547 | #### Returns 548 | 549 | `this` 550 | 551 | The udpated value after incrementing. 552 | 553 | #### Example 554 | 555 | ```ts 556 | // Assuming 557 | points.set("number", 42); 558 | points.set("numberInObject", {sub: { anInt: 5 }}); 559 | 560 | points.inc("number"); // 43 561 | points.inc("numberInObject", "sub.anInt"); // {sub: { anInt: 6 }} 562 | ``` 563 | 564 | *** 565 | 566 | ### dec() 567 | 568 | ```ts 569 | dec(key, path?): this; 570 | ``` 571 | 572 | Defined in: [index.ts:533](https://github.com/eslachance/enmap/blob/main/src/index.ts#L533) 573 | 574 | Decrements a key's value or property by 1. Value must be a number, or a path to a number. 575 | 576 | #### Parameters 577 | 578 | | Parameter | Type | Description | 579 | | ------ | ------ | ------ | 580 | | `key` | `string` | The enmap key where the value to decrement is stored. | 581 | | `path?` | `Path`<`V`, keyof `V`> | Optional. The property path to decrement, if the value is an object or array. | 582 | 583 | #### Returns 584 | 585 | `this` 586 | 587 | The enmap. 588 | 589 | #### Example 590 | 591 | ```ts 592 | // Assuming 593 | points.set("number", 42); 594 | points.set("numberInObject", {sub: { anInt: 5 }}); 595 | 596 | points.dec("number"); // 41 597 | points.dec("numberInObject", "sub.anInt"); // {sub: { anInt: 4 }} 598 | ``` 599 | 600 | *** 601 | 602 | ### ensure() 603 | 604 | ```ts 605 | ensure( 606 | key, 607 | defaultValue, 608 | path?): any; 609 | ``` 610 | 611 | Defined in: [index.ts:559](https://github.com/eslachance/enmap/blob/main/src/index.ts#L559) 612 | 613 | Returns the key's value, or the default given, ensuring that the data is there. 614 | This is a shortcut to "if enmap doesn't have key, set it, then get it" which is a very common pattern. 615 | 616 | #### Parameters 617 | 618 | | Parameter | Type | Description | 619 | | ------ | ------ | ------ | 620 | | `key` | `string` | Required. The key you want to make sure exists. | 621 | | `defaultValue` | `any` | Required. The value you want to save in the database and return as default. | 622 | | `path?` | `Path`<`V`, keyof `V`> | 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" | 623 | 624 | #### Returns 625 | 626 | `any` 627 | 628 | The value from the database for the key, or the default value provided for a new key. 629 | 630 | #### Example 631 | 632 | ```ts 633 | // Simply ensure the data exists (for using property methods): 634 | enmap.ensure("mykey", {some: "value", here: "as an example"}); 635 | enmap.has("mykey"); // always returns true 636 | enmap.get("mykey", "here") // returns "as an example"; 637 | 638 | // Get the default value back in a variable: 639 | const settings = mySettings.ensure("1234567890", defaultSettings); 640 | console.log(settings) // enmap's value for "1234567890" if it exists, otherwise the defaultSettings value. 641 | ``` 642 | 643 | *** 644 | 645 | ### includes() 646 | 647 | ```ts 648 | includes( 649 | key, 650 | value, 651 | path?): boolean; 652 | ``` 653 | 654 | Defined in: [index.ts:607](https://github.com/eslachance/enmap/blob/main/src/index.ts#L607) 655 | 656 | Performs Array.includes() on a certain enmap value. Works similar to 657 | [Array.includes()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes). 658 | 659 | #### Parameters 660 | 661 | | Parameter | Type | Description | 662 | | ------ | ------ | ------ | 663 | | `key` | `string` | Required. The key of the array to check the value of. | 664 | | `value` | `V` | Required. The value to check whether it's in the array. | 665 | | `path?` | `Path`<`V`, keyof `V`> | 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" | 666 | 667 | #### Returns 668 | 669 | `boolean` 670 | 671 | Whether the array contains the value. 672 | 673 | *** 674 | 675 | ### remove() 676 | 677 | ```ts 678 | remove( 679 | key, 680 | val, 681 | path?): this; 682 | ``` 683 | 684 | Defined in: [index.ts:632](https://github.com/eslachance/enmap/blob/main/src/index.ts#L632) 685 | 686 | Remove a value in an Array or Object element in Enmap. Note that this only works for 687 | values, not keys. Note that only one value is removed, no more. Arrays of objects must use a function to remove, 688 | as full object matching is not supported. 689 | 690 | #### Parameters 691 | 692 | | Parameter | Type | Description | 693 | | ------ | ------ | ------ | 694 | | `key` | `string` | Required. The key of the element to remove from in Enmap. | 695 | | `val` | `V` \| (`value`) => `boolean` | 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. | 696 | | `path?` | `Path`<`V`, keyof `V`> | 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. | 697 | 698 | #### Returns 699 | 700 | `this` 701 | 702 | #### Example 703 | 704 | ```ts 705 | // Assuming 706 | enmap.set('array', [1, 2, 3]) 707 | enmap.set('objectarray', [{ a: 1, b: 2, c: 3 }, { d: 4, e: 5, f: 6 }]) 708 | 709 | enmap.remove('array', 1); // value is now [2, 3] 710 | enmap.remove('objectarray', (value) => value.e === 5); // value is now [{ a: 1, b: 2, c: 3 }] 711 | ``` 712 | 713 | *** 714 | 715 | ### export() 716 | 717 | ```ts 718 | export(): string; 719 | ``` 720 | 721 | Defined in: [index.ts:650](https://github.com/eslachance/enmap/blob/main/src/index.ts#L650) 722 | 723 | Exports the enmap data to stringified JSON format. 724 | **__WARNING__**: Does not work on memory enmaps containing complex data! 725 | 726 | #### Returns 727 | 728 | `string` 729 | 730 | The enmap data in a stringified JSON format. 731 | 732 | *** 733 | 734 | ### import() 735 | 736 | ```ts 737 | import( 738 | data, 739 | overwrite, 740 | clear): this; 741 | ``` 742 | 743 | Defined in: [index.ts:673](https://github.com/eslachance/enmap/blob/main/src/index.ts#L673) 744 | 745 | Import an existing json export from enmap. This data must have been exported from enmap, 746 | and must be from a version that's equivalent or lower than where you're importing it. 747 | (This means Enmap 5 data is compatible in Enmap 6). 748 | 749 | #### Parameters 750 | 751 | | Parameter | Type | Default value | Description | 752 | | ------ | ------ | ------ | ------ | 753 | | `data` | `string` | `undefined` | The data to import to Enmap. Must contain all the required fields provided by an enmap export(). | 754 | | `overwrite` | `boolean` | `true` | Defaults to `true`. Whether to overwrite existing key/value data with incoming imported data | 755 | | `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.) | 756 | 757 | #### Returns 758 | 759 | `this` 760 | 761 | *** 762 | 763 | ### multi() 764 | 765 | ```ts 766 | static multi(names, options?): Record>; 767 | ``` 768 | 769 | Defined in: [index.ts:715](https://github.com/eslachance/enmap/blob/main/src/index.ts#L715) 770 | 771 | Initialize multiple Enmaps easily. 772 | 773 | #### Type Parameters 774 | 775 | | Type Parameter | Default type | 776 | | ------ | ------ | 777 | | `V` | `unknown` | 778 | | `SV` | `unknown` | 779 | 780 | #### Parameters 781 | 782 | | Parameter | Type | Description | 783 | | ------ | ------ | ------ | 784 | | `names` | `string`[] | Array of strings. Each array entry will create a separate enmap with that name. | 785 | | `options?` | `Omit`<[`EnmapOptions`](../interfaces/EnmapOptions.md)\<`V`, `SV`>, `"name"`\> | Options object to pass to each enmap, excluding the name.. | 786 | 787 | #### Returns 788 | 789 | `Record`<`string`, `Enmap`\<`V`, `SV`>\> 790 | 791 | An array of initialized Enmaps. 792 | 793 | #### Example 794 | 795 | ```ts 796 | // Using local variables. 797 | const Enmap = require('enmap'); 798 | const { settings, tags, blacklist } = Enmap.multi(['settings', 'tags', 'blacklist']); 799 | 800 | // Attaching to an existing object (for instance some API's client) 801 | import Enmap from 'enmap'; 802 | Object.assign(client, Enmap.multi(["settings", "tags", "blacklist"])); 803 | ``` 804 | 805 | *** 806 | 807 | ### random() 808 | 809 | ```ts 810 | random(count?): [string, V][]; 811 | ``` 812 | 813 | Defined in: [index.ts:738](https://github.com/eslachance/enmap/blob/main/src/index.ts#L738) 814 | 815 | Obtains random value(s) from this Enmap. This relies on Enmap#array. 816 | 817 | #### Parameters 818 | 819 | | Parameter | Type | Default value | Description | 820 | | ------ | ------ | ------ | ------ | 821 | | `count?` | `number` | `1` | Number of values to obtain randomly | 822 | 823 | #### Returns 824 | 825 | \[`string`, `V`\][] 826 | 827 | The single value if `count` is undefined, 828 | or an array of values of `count` length 829 | 830 | *** 831 | 832 | ### randomKey() 833 | 834 | ```ts 835 | randomKey(count?): string[]; 836 | ``` 837 | 838 | Defined in: [index.ts:755](https://github.com/eslachance/enmap/blob/main/src/index.ts#L755) 839 | 840 | Obtains random key(s) from this Enmap. This relies on Enmap#keyArray 841 | 842 | #### Parameters 843 | 844 | | Parameter | Type | Default value | Description | 845 | | ------ | ------ | ------ | ------ | 846 | | `count?` | `number` | `1` | Number of keys to obtain randomly | 847 | 848 | #### Returns 849 | 850 | `string`[] 851 | 852 | The single key if `count` is undefined, 853 | or an array of keys of `count` length 854 | 855 | *** 856 | 857 | ### every() 858 | 859 | ```ts 860 | every(valueOrFunction, path?): boolean; 861 | ``` 862 | 863 | Defined in: [index.ts:775](https://github.com/eslachance/enmap/blob/main/src/index.ts#L775) 864 | 865 | Similar to 866 | [Array.every()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every). 867 | Supports either a predicate function or a value to compare. 868 | Returns true only if the predicate function returns true for all elements in the array (or the value is strictly equal in all elements). 869 | 870 | #### Parameters 871 | 872 | | Parameter | Type | Description | 873 | | ------ | ------ | ------ | 874 | | `valueOrFunction` | `any` | Function used to test (should return a boolean), or a value to compare. | 875 | | `path?` | `Path`<`V`, keyof `V`> | Required if the value is an object. The path to the property to compare with. | 876 | 877 | #### Returns 878 | 879 | `boolean` 880 | 881 | *** 882 | 883 | ### some() 884 | 885 | ```ts 886 | some(valueOrFunction, path?): boolean; 887 | ``` 888 | 889 | Defined in: [index.ts:802](https://github.com/eslachance/enmap/blob/main/src/index.ts#L802) 890 | 891 | Similar to 892 | [Array.some()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some). 893 | Supports either a predicate function or a value to compare. 894 | 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). 895 | 896 | #### Parameters 897 | 898 | | Parameter | Type | Description | 899 | | ------ | ------ | ------ | 900 | | `valueOrFunction` | `any` | Function used to test (should return a boolean), or a value to compare. | 901 | | `path?` | `Path`<`V`, keyof `V`> | Required if the value is an object. The path to the property to compare with. | 902 | 903 | #### Returns 904 | 905 | `boolean` 906 | 907 | *** 908 | 909 | ### map() 910 | 911 | ```ts 912 | map(pathOrFn): R[]; 913 | ``` 914 | 915 | Defined in: [index.ts:827](https://github.com/eslachance/enmap/blob/main/src/index.ts#L827) 916 | 917 | Similar to 918 | [Array.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map). 919 | Returns an array of the results of applying the callback to all elements. 920 | 921 | #### Type Parameters 922 | 923 | | Type Parameter | 924 | | ------ | 925 | | `R` | 926 | 927 | #### Parameters 928 | 929 | | Parameter | Type | Description | 930 | | ------ | ------ | ------ | 931 | | `pathOrFn` | `string` \| (`val`, `key`) => `R` | A function that produces an element of the new Array, or a path to the property to map. | 932 | 933 | #### Returns 934 | 935 | `R`[] 936 | 937 | *** 938 | 939 | ### find() 940 | 941 | ```ts 942 | find(pathOrFn, value?): null | V; 943 | ``` 944 | 945 | Defined in: [index.ts:853](https://github.com/eslachance/enmap/blob/main/src/index.ts#L853) 946 | 947 | Searches for a single item where its specified property's value is identical to the given value 948 | (`item[prop] === value`), or the given function returns a truthy value. In the latter case, this is similar to 949 | [Array.find()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find). 950 | 951 | #### Parameters 952 | 953 | | Parameter | Type | Description | 954 | | ------ | ------ | ------ | 955 | | `pathOrFn` | `string` \| (`val`, `key`) => `boolean` | The path to the value to test against, or the function to test with | 956 | | `value?` | `any` | The expected value - only applicable and required if using a property for the first argument | 957 | 958 | #### Returns 959 | 960 | `null` \| `V` 961 | 962 | #### Examples 963 | 964 | ```ts 965 | enmap.find('username', 'Bob'); 966 | ``` 967 | 968 | ```ts 969 | enmap.find(val => val.username === 'Bob'); 970 | ``` 971 | 972 | *** 973 | 974 | ### findIndex() 975 | 976 | ```ts 977 | findIndex(pathOrFn, value?): null | string; 978 | ``` 979 | 980 | Defined in: [index.ts:879](https://github.com/eslachance/enmap/blob/main/src/index.ts#L879) 981 | 982 | Searches for the key of a single item where its specified property's value is identical to the given value 983 | (`item[prop] === value`), or the given function returns a truthy value. In the latter case, this is similar to 984 | [Array.findIndex()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex). 985 | 986 | #### Parameters 987 | 988 | | Parameter | Type | Description | 989 | | ------ | ------ | ------ | 990 | | `pathOrFn` | `string` \| (`val`, `key`) => `boolean` | The path to the value to test against, or the function to test with | 991 | | `value?` | `any` | The expected value - only applicable and required if using a property for the first argument | 992 | 993 | #### Returns 994 | 995 | `null` \| `string` 996 | 997 | #### Examples 998 | 999 | ```ts 1000 | enmap.findIndex('username', 'Bob'); 1001 | ``` 1002 | 1003 | ```ts 1004 | enmap.findIndex(val => val.username === 'Bob'); 1005 | ``` 1006 | 1007 | *** 1008 | 1009 | ### reduce() 1010 | 1011 | ```ts 1012 | reduce(predicate, initialValue?): R; 1013 | ``` 1014 | 1015 | Defined in: [index.ts:900](https://github.com/eslachance/enmap/blob/main/src/index.ts#L900) 1016 | 1017 | Similar to 1018 | [Array.reduce()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce). 1019 | 1020 | #### Type Parameters 1021 | 1022 | | Type Parameter | 1023 | | ------ | 1024 | | `R` | 1025 | 1026 | #### Parameters 1027 | 1028 | | Parameter | Type | Description | 1029 | | ------ | ------ | ------ | 1030 | | `predicate` | (`accumulator`, `val`, `key`) => `R` | Function used to reduce, taking three arguments; `accumulator`, `currentValue`, `currentKey`. | 1031 | | `initialValue?` | `R` | Starting value for the accumulator | 1032 | 1033 | #### Returns 1034 | 1035 | `R` 1036 | 1037 | *** 1038 | 1039 | ### filter() 1040 | 1041 | ```ts 1042 | filter(pathOrFn, value?): V[]; 1043 | ``` 1044 | 1045 | Defined in: [index.ts:920](https://github.com/eslachance/enmap/blob/main/src/index.ts#L920) 1046 | 1047 | Similar to 1048 | [Array.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter). 1049 | Returns an array of values where the given function returns true for that value. 1050 | Alternatively you can provide a value and path to filter by using exact value matching. 1051 | 1052 | #### Parameters 1053 | 1054 | | Parameter | Type | Description | 1055 | | ------ | ------ | ------ | 1056 | | `pathOrFn` | `string` \| (`val`, `key`) => `boolean` | The path to the value to test against, or the function to test with. If using a function, this function should return a boolean. | 1057 | | `value?` | `any` | Value to use as `this` when executing function | 1058 | 1059 | #### Returns 1060 | 1061 | `V`[] 1062 | 1063 | *** 1064 | 1065 | ### sweep() 1066 | 1067 | ```ts 1068 | sweep(pathOrFn, value?): number; 1069 | ``` 1070 | 1071 | Defined in: [index.ts:950](https://github.com/eslachance/enmap/blob/main/src/index.ts#L950) 1072 | 1073 | Deletes entries that satisfy the provided filter function or value matching. 1074 | 1075 | #### Parameters 1076 | 1077 | | Parameter | Type | Description | 1078 | | ------ | ------ | ------ | 1079 | | `pathOrFn` | `string` \| (`val`, `key`) => `boolean` | The path to the value to test against, or the function to test with. | 1080 | | `value?` | `any` | The expected value - only applicable and required if using a property for the first argument. | 1081 | 1082 | #### Returns 1083 | 1084 | `number` 1085 | 1086 | The number of removed entries. 1087 | 1088 | *** 1089 | 1090 | ### changed() 1091 | 1092 | ```ts 1093 | changed(cb): void; 1094 | ``` 1095 | 1096 | Defined in: [index.ts:988](https://github.com/eslachance/enmap/blob/main/src/index.ts#L988) 1097 | 1098 | Function called whenever data changes within Enmap after the initial load. 1099 | Can be used to detect if another part of your code changed a value in enmap and react on it. 1100 | 1101 | #### Parameters 1102 | 1103 | | Parameter | Type | Description | 1104 | | ------ | ------ | ------ | 1105 | | `cb` | (`key`, `oldValue`, `newValue`) => `void` | A callback function that will be called whenever data changes in the enmap. | 1106 | 1107 | #### Returns 1108 | 1109 | `void` 1110 | 1111 | #### Example 1112 | 1113 | ```ts 1114 | enmap.changed((keyName, oldValue, newValue) => { 1115 | console.log(`Value of ${keyName} has changed from: \n${oldValue}\nto\n${newValue}`); 1116 | }); 1117 | ``` 1118 | 1119 | *** 1120 | 1121 | ### partition() 1122 | 1123 | ```ts 1124 | partition(pathOrFn, value?): [V[], V[]]; 1125 | ``` 1126 | 1127 | Defined in: [index.ts:998](https://github.com/eslachance/enmap/blob/main/src/index.ts#L998) 1128 | 1129 | Separates the Enmap into multiple arrays given a function that separates them. 1130 | 1131 | #### Parameters 1132 | 1133 | | Parameter | Type | Description | 1134 | | ------ | ------ | ------ | 1135 | | `pathOrFn` | `string` \| (`val`, `key`) => `boolean` | the path to the value to test against, or the function to test with. | 1136 | | `value?` | `any` | the value to use as a condition for partitioning. | 1137 | 1138 | #### Returns 1139 | 1140 | \[`V`[], `V`[]\] 1141 | 1142 | An array of arrays with the partitioned data. 1143 | -------------------------------------------------------------------------------- /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.ts source code and change the comments there instead! 13 | {% endhint %} 14 | 15 | 16 | 17 | ## Enmap Class 18 | 19 | The Enmap class provides a simple, synchronous, fast key/value storage built around better-sqlite3. 20 | Contains extra utility methods for managing arrays and objects. 21 | 22 | ## Properties 23 | 24 |
25 |
sizenumber
26 |

Get the number of key/value pairs saved in the enmap.

27 |
28 |
dbDatabase
29 |

Get the better-sqlite3 database object. Useful if you want to directly query or interact with the 30 | underlying SQLite database. Use at your own risk, as errors here might cause loss of data or corruption!

31 |
32 |
autonumstring
33 |

Generates an automatic numerical key for inserting a new value. 34 | This is a "weak" method, it ensures the value isn't duplicated, but does not 35 | guarantee it's sequential (if a value is deleted, another can take its place). 36 | Useful for logging, actions, items, etc - anything that doesn't already have a unique ID.

37 |
38 |
39 | 40 | ## Methods 41 | 42 |
43 |
set(key, value, path)
44 |

Sets a value in Enmap. If the key already has a value, overwrites the data (or the value in a path, if provided).

45 |
46 |
get(key, path)
47 |

Retrieves a value from the enmap, using its key.

48 |
49 |
has(key)boolean
50 |

Returns whether or not the key exists in the Enmap.

51 |
52 |
delete(key, path)
53 |

Deletes a key in the Enmap.

54 |
55 |
clear()void
56 |

Deletes everything from the enmap.

57 |
58 |
keys()Array.<string>
59 |

Get all the keys of the enmap as an array.

60 |
61 |
values()Array.<*>
62 |

Get all the values of the enmap as an array.

63 |
64 |
entries()Array.<Array.<*, *>>
65 |

Get all entries of the enmap as an array, with each item containing the key and value.

66 |
67 |
update(key, valueOrFunction)*
68 |

Update an existing object value in Enmap by merging new keys. This only works on objects, any other value will throw an error. 69 | Heavily inspired by setState from React's class components. 70 | 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.

71 |
72 |
observe(key, path)*
73 |

Returns an observable object. Modifying this object or any of its properties/indexes/children 74 | will automatically save those changes into enmap. This only works on 75 | objects and arrays, not "basic" values like strings or integers.

76 |
77 |
push(key, value, path, allowDupes)
78 |

Push to an array value in Enmap.

79 |
80 |
math(key, operation, operand, path)number
81 |

Executes a mathematical operation on a value and saves it in the enmap.

82 |
83 |
inc(key, path)number
84 |

Increments a key's value or property by 1. Value must be a number, or a path to a number.

85 |
86 |
dec(key, path)Enmap
87 |

Decrements a key's value or property by 1. Value must be a number, or a path to a number.

88 |
89 |
ensure(key, defaultValue, path)*
90 |

Returns the key's value, or the default given, ensuring that the data is there. 91 | This is a shortcut to "if enmap doesn't have key, set it, then get it" which is a very common pattern.

92 |
93 |
includes(key, value, path)boolean
94 |

Performs Array.includes() on a certain enmap value. Works similar to 95 | Array.includes().

96 |
97 |
remove(key, val, path)
98 |

Remove a value in an Array or Object element in Enmap. Note that this only works for 99 | values, not keys. Note that only one value is removed, no more. Arrays of objects must use a function to remove, 100 | as full object matching is not supported.

101 |
102 |
export()string
103 |

Exports the enmap data to stringified JSON format. 104 | WARNING: Does not work on memory enmaps containing complex data!

105 |
106 |
import(data, overwrite, clear)
107 |

Import an existing json export from enmap. This data must have been exported from enmap, 108 | and must be from a version that's equivalent or lower than where you're importing it. 109 | (This means Enmap 5 data is compatible in Enmap 6).

110 |
111 |
multi(names, options)Object
112 |

Initialize multiple Enmaps easily.

113 |
114 |
random([count])* | Array.<*>
115 |

Obtains random value(s) from this Enmap. This relies on Enmap#array.

116 |
117 |
randomKey([count])* | Array.<*>
118 |

Obtains random key(s) from this Enmap. This relies on Enmap#keyArray

119 |
120 |
every(valueOrFunction, [path])boolean
121 |

Similar to 122 | Array.every(). 123 | Supports either a predicate function or a value to compare. 124 | Returns true only if the predicate function returns true for all elements in the array (or the value is strictly equal in all elements).

125 |
126 |
some(valueOrFunction, [path])Array
127 |

Similar to 128 | Array.some(). 129 | Supports either a predicate function or a value to compare. 130 | 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).

131 |
132 |
map(pathOrFn)Array
133 |

Similar to 134 | Array.map(). 135 | Returns an array of the results of applying the callback to all elements.

136 |
137 |
find(pathOrFn, [value])*
138 |

Searches for a single item where its specified property's value is identical to the given value 139 | (item[prop] === value), or the given function returns a truthy value. In the latter case, this is similar to 140 | Array.find().

141 |
142 |
findIndex(pathOrFn, [value])string | number
143 |

Searches for the key of a single item where its specified property's value is identical to the given value 144 | (item[prop] === value), or the given function returns a truthy value. In the latter case, this is similar to 145 | Array.findIndex().

146 |
147 |
reduce(predicate, [initialValue])*
148 |

Similar to 149 | Array.reduce().

150 |
151 |
filter(pathOrFn, [value])Enmap
152 |

Similar to 153 | Array.filter(). 154 | Returns an array of values where the given function returns true for that value. 155 | Alternatively you can provide a value and path to filter by using exact value matching.

156 |
157 |
sweep(pathOrFn, [value])number
158 |

Deletes entries that satisfy the provided filter function or value matching.

159 |
160 |
changed(cb)
161 |

Function called whenever data changes within Enmap after the initial load. 162 | Can be used to detect if another part of your code changed a value in enmap and react on it.

163 |
164 |
partition(pathOrFn, value)Array.<Array.<*>>
165 |

Separates the Enmap into multiple arrays given a function that separates them.

166 |
167 |
168 | 169 | 170 | 171 | ## size ⇒ number 172 | Get the number of key/value pairs saved in the enmap. 173 | 174 | **Kind**: instance property of Enmap 175 | **Returns**: number - The number of elements in the enmap. 176 | **Read only**: true 177 | 178 | 179 | ## db ⇒ Database 180 | Get the better-sqlite3 database object. Useful if you want to directly query or interact with the 181 | underlying SQLite database. Use at your own risk, as errors here might cause loss of data or corruption! 182 | 183 | **Kind**: instance property of Enmap 184 | 185 | 186 | ## autonum ⇒ string 187 | Generates an automatic numerical key for inserting a new value. 188 | This is a "weak" method, it ensures the value isn't duplicated, but does not 189 | guarantee it's sequential (if a value is deleted, another can take its place). 190 | Useful for logging, actions, items, etc - anything that doesn't already have a unique ID. 191 | 192 | **Kind**: instance property of Enmap 193 | **Returns**: string - The generated key number. 194 | **Read only**: true 195 | **Example** 196 | ```js 197 | enmap.set(enmap.autonum, "This is a new value"); 198 | ``` 199 | 200 | 201 | ## set(key, value, path) 202 | Sets a value in Enmap. If the key already has a value, overwrites the data (or the value in a path, if provided). 203 | 204 | **Kind**: instance method of Enmap 205 | 206 | | Param | Description | 207 | | --- | --- | 208 | | key | Required. The location in which the data should be saved. | 209 | | 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. | 210 | | path | 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" | 211 | 212 | **Example** 213 | ```js 214 | // Direct Value Examples 215 | enmap.set('simplevalue', 'this is a string'); 216 | enmap.set('isEnmapGreat', true); 217 | enmap.set('TheAnswer', 42); 218 | enmap.set('IhazObjects', { color: 'black', action: 'paint', desire: true }); 219 | enmap.set('ArraysToo', [1, "two", "tree", "foor"]) 220 | 221 | // Settings Properties 222 | enmap.set('IhazObjects', 'blue', 'color'); //modified previous object 223 | enmap.set('ArraysToo', 'three', 2); // changes "tree" to "three" in array. 224 | ``` 225 | 226 | 227 | ## get(key, path) ⇒ 228 | Retrieves a value from the enmap, using its key. 229 | 230 | **Kind**: instance method of Enmap 231 | **Returns**: The parsed value for this key. 232 | 233 | | Param | Description | 234 | | --- | --- | 235 | | key | The key to retrieve from the enmap. | 236 | | path | Optional. The property to retrieve from the object or array. Should be a path with dot notation, such as "prop1.subprop2.subprop3" | 237 | 238 | **Example** 239 | ```js 240 | const myKeyValue = enmap.get("myKey"); 241 | console.log(myKeyValue); 242 | 243 | const someSubValue = enmap.get("anObjectKey", "someprop.someOtherSubProp"); 244 | ``` 245 | 246 | 247 | ## has(key) ⇒ boolean 248 | Returns whether or not the key exists in the Enmap. 249 | 250 | **Kind**: instance method of Enmap 251 | 252 | | Param | Description | 253 | | --- | --- | 254 | | key | Required. The key of the element to add to The Enmap or array. | 255 | 256 | **Example** 257 | ```js 258 | if(enmap.has("myKey")) { 259 | // key is there 260 | } 261 | ``` 262 | 263 | 264 | ## delete(key, path) 265 | Deletes a key in the Enmap. 266 | 267 | **Kind**: instance method of Enmap 268 | 269 | | Param | Description | 270 | | --- | --- | 271 | | key | Required. The key of the element to delete from The Enmap. | 272 | | path | Optional. The name of the property to remove from the object. Should be a path with dot notation, such as "prop1.subprop2.subprop3" | 273 | 274 | 275 | 276 | ## clear() ⇒ void 277 | Deletes everything from the enmap. 278 | 279 | **Kind**: instance method of Enmap 280 | 281 | 282 | ## keys() ⇒ Array.<string> 283 | Get all the keys of the enmap as an array. 284 | 285 | **Kind**: instance method of Enmap 286 | **Returns**: Array.<string> - An array of all the keys in the enmap. 287 | 288 | 289 | ## values() ⇒ Array.<\*> 290 | Get all the values of the enmap as an array. 291 | 292 | **Kind**: instance method of Enmap 293 | **Returns**: Array.<\*> - An array of all the values in the enmap. 294 | 295 | 296 | ## entries() ⇒ Array.<Array.<\*, \*>> 297 | Get all entries of the enmap as an array, with each item containing the key and value. 298 | 299 | **Kind**: instance method of Enmap 300 | **Returns**: Array.<Array.<\*, \*>> - An array of arrays, with each sub-array containing two items, the key and the value. 301 | 302 | 303 | ## update(key, valueOrFunction) ⇒ \* 304 | Update an existing object value in Enmap by merging new keys. **This only works on objects**, any other value will throw an error. 305 | Heavily inspired by setState from React's class components. 306 | 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. 307 | 308 | **Kind**: instance method of Enmap 309 | **Returns**: \* - The modified (merged) value. 310 | 311 | | Param | Type | Description | 312 | | --- | --- | --- | 313 | | key | string | The key of the object to update. | 314 | | 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. | 315 | 316 | **Example** 317 | ```js 318 | // Define an object we're going to update 319 | enmap.set("obj", { a: 1, b: 2, c: 3 }); 320 | 321 | // Direct merge 322 | enmap.update("obj", { d: 4, e: 5 }); 323 | // obj is now { a: 1, b: 2, c: 3, d: 4, e: 5 } 324 | 325 | // Functional update 326 | enmap.update("obj", (previous) => ({ 327 | ...obj, 328 | f: 6, 329 | g: 7 330 | })); 331 | // this example takes heavy advantage of the spread operators. 332 | // More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax 333 | ``` 334 | 335 | 336 | ## observe(key, path) ⇒ \* 337 | Returns an observable object. Modifying this object or any of its properties/indexes/children 338 | will automatically save those changes into enmap. This only works on 339 | objects and arrays, not "basic" values like strings or integers. 340 | 341 | **Kind**: instance method of Enmap 342 | **Returns**: \* - The value for this key. 343 | 344 | | Param | Type | Description | 345 | | --- | --- | --- | 346 | | key | \* | The key to retrieve from the enmap. | 347 | | path | string | Optional. The property to retrieve from the object or array. | 348 | 349 | 350 | 351 | ## push(key, value, path, allowDupes) 352 | Push to an array value in Enmap. 353 | 354 | **Kind**: instance method of Enmap 355 | 356 | | Param | Type | Default | Description | 357 | | --- | --- | --- | --- | 358 | | key | string | | Required. The key of the array element to push to in Enmap. | 359 | | value | \* | | Required. The value to push to the array. | 360 | | 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" | 361 | | allowDupes | boolean | false | Optional. Allow duplicate values in the array (default: false). | 362 | 363 | **Example** 364 | ```js 365 | // Assuming 366 | enmap.set("simpleArray", [1, 2, 3, 4]); 367 | enmap.set("arrayInObject", {sub: [1, 2, 3, 4]}); 368 | 369 | enmap.push("simpleArray", 5); // adds 5 at the end of the array 370 | enmap.push("arrayInObject", "five", "sub"); // adds "five" at the end of the sub array 371 | ``` 372 | 373 | 374 | ## math(key, operation, operand, path) ⇒ number 375 | Executes a mathematical operation on a value and saves it in the enmap. 376 | 377 | **Kind**: instance method of Enmap 378 | **Returns**: number - The updated value after the operation 379 | 380 | | Param | Type | Description | 381 | | --- | --- | --- | 382 | | key | string | The enmap key on which to execute the math operation. | 383 | | operation | string | Which mathematical operation to execute. Supports most math ops: =, -, *, /, %, ^, and english spelling of those operations. | 384 | | operand | number | The right operand of the operation. | 385 | | path | string | Optional. The property path to execute the operation on, if the value is an object or array. | 386 | 387 | **Example** 388 | ```js 389 | // Assuming 390 | points.set("number", 42); 391 | points.set("numberInObject", {sub: { anInt: 5 }}); 392 | 393 | points.math("number", "/", 2); // 21 394 | points.math("number", "add", 5); // 26 395 | points.math("number", "modulo", 3); // 2 396 | points.math("numberInObject", "+", 10, "sub.anInt"); 397 | ``` 398 | 399 | 400 | ## inc(key, path) ⇒ number 401 | Increments a key's value or property by 1. Value must be a number, or a path to a number. 402 | 403 | **Kind**: instance method of Enmap 404 | **Returns**: number - The udpated value after incrementing. 405 | 406 | | Param | Type | Description | 407 | | --- | --- | --- | 408 | | key | string | The enmap key where the value to increment is stored. | 409 | | path | string | Optional. The property path to increment, if the value is an object or array. | 410 | 411 | **Example** 412 | ```js 413 | // Assuming 414 | points.set("number", 42); 415 | points.set("numberInObject", {sub: { anInt: 5 }}); 416 | 417 | points.inc("number"); // 43 418 | points.inc("numberInObject", "sub.anInt"); // {sub: { anInt: 6 }} 419 | ``` 420 | 421 | 422 | ## dec(key, path) ⇒ Enmap 423 | Decrements a key's value or property by 1. Value must be a number, or a path to a number. 424 | 425 | **Kind**: instance method of Enmap 426 | **Returns**: Enmap - The enmap. 427 | 428 | | Param | Type | Description | 429 | | --- | --- | --- | 430 | | key | string | The enmap key where the value to decrement is stored. | 431 | | path | string | Optional. The property path to decrement, if the value is an object or array. | 432 | 433 | **Example** 434 | ```js 435 | // Assuming 436 | points.set("number", 42); 437 | points.set("numberInObject", {sub: { anInt: 5 }}); 438 | 439 | points.dec("number"); // 41 440 | points.dec("numberInObject", "sub.anInt"); // {sub: { anInt: 4 }} 441 | ``` 442 | 443 | 444 | ## ensure(key, defaultValue, path) ⇒ \* 445 | Returns the key's value, or the default given, ensuring that the data is there. 446 | This is a shortcut to "if enmap doesn't have key, set it, then get it" which is a very common pattern. 447 | 448 | **Kind**: instance method of Enmap 449 | **Returns**: \* - The value from the database for the key, or the default value provided for a new key. 450 | 451 | | Param | Type | Description | 452 | | --- | --- | --- | 453 | | key | string | Required. The key you want to make sure exists. | 454 | | defaultValue | \* | Required. The value you want to save in the database and return as default. | 455 | | 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" | 456 | 457 | **Example** 458 | ```js 459 | // Simply ensure the data exists (for using property methods): 460 | enmap.ensure("mykey", {some: "value", here: "as an example"}); 461 | enmap.has("mykey"); // always returns true 462 | enmap.get("mykey", "here") // returns "as an example"; 463 | 464 | // Get the default value back in a variable: 465 | const settings = mySettings.ensure("1234567890", defaultSettings); 466 | console.log(settings) // enmap's value for "1234567890" if it exists, otherwise the defaultSettings value. 467 | ``` 468 | 469 | 470 | ## includes(key, value, path) ⇒ boolean 471 | Performs Array.includes() on a certain enmap value. Works similar to 472 | [Array.includes()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes). 473 | 474 | **Kind**: instance method of Enmap 475 | **Returns**: boolean - Whether the array contains the value. 476 | 477 | | Param | Type | Description | 478 | | --- | --- | --- | 479 | | key | string | Required. The key of the array to check the value of. | 480 | | value | string \| number | Required. The value to check whether it's in the array. | 481 | | 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" | 482 | 483 | 484 | 485 | ## remove(key, val, path) 486 | Remove a value in an Array or Object element in Enmap. Note that this only works for 487 | values, not keys. Note that only one value is removed, no more. Arrays of objects must use a function to remove, 488 | as full object matching is not supported. 489 | 490 | **Kind**: instance method of Enmap 491 | 492 | | Param | Type | Description | 493 | | --- | --- | --- | 494 | | key | string | Required. The key of the element to remove from in Enmap. | 495 | | 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. | 496 | | 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. | 497 | 498 | **Example** 499 | ```js 500 | // Assuming 501 | enmap.set('array', [1, 2, 3]) 502 | enmap.set('objectarray', [{ a: 1, b: 2, c: 3 }, { d: 4, e: 5, f: 6 }]) 503 | 504 | enmap.remove('array', 1); // value is now [2, 3] 505 | enmap.remove('objectarray', (value) => value.e === 5); // value is now [{ a: 1, b: 2, c: 3 }] 506 | ``` 507 | 508 | 509 | ## export() ⇒ string 510 | Exports the enmap data to stringified JSON format. 511 | **__WARNING__**: Does not work on memory enmaps containing complex data! 512 | 513 | **Kind**: instance method of Enmap 514 | **Returns**: string - The enmap data in a stringified JSON format. 515 | 516 | 517 | ## import(data, overwrite, clear) 518 | Import an existing json export from enmap. This data must have been exported from enmap, 519 | and must be from a version that's equivalent or lower than where you're importing it. 520 | (This means Enmap 5 data is compatible in Enmap 6). 521 | 522 | **Kind**: instance method of Enmap 523 | 524 | | Param | Type | Default | Description | 525 | | --- | --- | --- | --- | 526 | | data | string | | The data to import to Enmap. Must contain all the required fields provided by an enmap export(). | 527 | | overwrite | boolean | true | Defaults to `true`. Whether to overwrite existing key/value data with incoming imported data | 528 | | 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.) | 529 | 530 | 531 | 532 | ## multi(names, options) ⇒ Object 533 | Initialize multiple Enmaps easily. 534 | 535 | **Kind**: instance method of Enmap 536 | **Returns**: Object - An array of initialized Enmaps. 537 | 538 | | Param | Type | Description | 539 | | --- | --- | --- | 540 | | names | Array.<string> | Array of strings. Each array entry will create a separate enmap with that name. | 541 | | options | Object | Options object to pass to each enmap, excluding the name.. | 542 | 543 | **Example** 544 | ```js 545 | // Using local variables. 546 | const Enmap = require('enmap'); 547 | const { settings, tags, blacklist } = Enmap.multi(['settings', 'tags', 'blacklist']); 548 | 549 | // Attaching to an existing object (for instance some API's client) 550 | import Enmap from 'enmap'; 551 | Object.assign(client, Enmap.multi(["settings", "tags", "blacklist"])); 552 | ``` 553 | 554 | 555 | ## random([count]) ⇒ \* \| Array.<\*> 556 | Obtains random value(s) from this Enmap. This relies on [Enmap#array](Enmap#array). 557 | 558 | **Kind**: instance method of Enmap 559 | **Returns**: \* \| Array.<\*> - The single value if `count` is undefined, 560 | or an array of values of `count` length 561 | 562 | | Param | Type | Default | Description | 563 | | --- | --- | --- | --- | 564 | | [count] | number | 1 | Number of values to obtain randomly | 565 | 566 | 567 | 568 | ## randomKey([count]) ⇒ \* \| Array.<\*> 569 | Obtains random key(s) from this Enmap. This relies on [Enmap#keyArray](Enmap#keyArray) 570 | 571 | **Kind**: instance method of Enmap 572 | **Returns**: \* \| Array.<\*> - The single key if `count` is undefined, 573 | or an array of keys of `count` length 574 | 575 | | Param | Type | Default | Description | 576 | | --- | --- | --- | --- | 577 | | [count] | number | 1 | Number of keys to obtain randomly | 578 | 579 | 580 | 581 | ## every(valueOrFunction, [path]) ⇒ boolean 582 | Similar to 583 | [Array.every()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every). 584 | Supports either a predicate function or a value to compare. 585 | Returns true only if the predicate function returns true for all elements in the array (or the value is strictly equal in all elements). 586 | 587 | **Kind**: instance method of Enmap 588 | 589 | | Param | Type | Description | 590 | | --- | --- | --- | 591 | | valueOrFunction | function \| string | Function used to test (should return a boolean), or a value to compare. | 592 | | [path] | string | Required if the value is an object. The path to the property to compare with. | 593 | 594 | 595 | 596 | ## some(valueOrFunction, [path]) ⇒ Array 597 | Similar to 598 | [Array.some()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some). 599 | Supports either a predicate function or a value to compare. 600 | 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). 601 | 602 | **Kind**: instance method of Enmap 603 | 604 | | Param | Type | Description | 605 | | --- | --- | --- | 606 | | valueOrFunction | function \| string | Function used to test (should return a boolean), or a value to compare. | 607 | | [path] | string | Required if the value is an object. The path to the property to compare with. | 608 | 609 | 610 | 611 | ## map(pathOrFn) ⇒ Array 612 | Similar to 613 | [Array.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map). 614 | Returns an array of the results of applying the callback to all elements. 615 | 616 | **Kind**: instance method of Enmap 617 | 618 | | Param | Type | Description | 619 | | --- | --- | --- | 620 | | pathOrFn | function \| string | A function that produces an element of the new Array, or a path to the property to map. | 621 | 622 | 623 | 624 | ## find(pathOrFn, [value]) ⇒ \* 625 | Searches for a single item where its specified property's value is identical to the given value 626 | (`item[prop] === value`), or the given function returns a truthy value. In the latter case, this is similar to 627 | [Array.find()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find). 628 | 629 | **Kind**: instance method of Enmap 630 | 631 | | Param | Type | Description | 632 | | --- | --- | --- | 633 | | pathOrFn | string \| function | The path to the value to test against, or the function to test with | 634 | | [value] | \* | The expected value - only applicable and required if using a property for the first argument | 635 | 636 | **Example** 637 | ```js 638 | enmap.find('username', 'Bob'); 639 | ``` 640 | **Example** 641 | ```js 642 | enmap.find(val => val.username === 'Bob'); 643 | ``` 644 | 645 | 646 | ## findIndex(pathOrFn, [value]) ⇒ string \| number 647 | Searches for the key of a single item where its specified property's value is identical to the given value 648 | (`item[prop] === value`), or the given function returns a truthy value. In the latter case, this is similar to 649 | [Array.findIndex()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex). 650 | 651 | **Kind**: instance method of Enmap 652 | 653 | | Param | Type | Description | 654 | | --- | --- | --- | 655 | | pathOrFn | string \| function | The path to the value to test against, or the function to test with | 656 | | [value] | \* | The expected value - only applicable and required if using a property for the first argument | 657 | 658 | **Example** 659 | ```js 660 | enmap.findIndex('username', 'Bob'); 661 | ``` 662 | **Example** 663 | ```js 664 | enmap.findIndex(val => val.username === 'Bob'); 665 | ``` 666 | 667 | 668 | ## reduce(predicate, [initialValue]) ⇒ \* 669 | Similar to 670 | [Array.reduce()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce). 671 | 672 | **Kind**: instance method of Enmap 673 | 674 | | Param | Type | Description | 675 | | --- | --- | --- | 676 | | predicate | function | Function used to reduce, taking three arguments; `accumulator`, `currentValue`, `currentKey`. | 677 | | [initialValue] | \* | Starting value for the accumulator | 678 | 679 | 680 | 681 | ## filter(pathOrFn, [value]) ⇒ Enmap 682 | Similar to 683 | [Array.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter). 684 | Returns an array of values where the given function returns true for that value. 685 | Alternatively you can provide a value and path to filter by using exact value matching. 686 | 687 | **Kind**: instance method of Enmap 688 | 689 | | Param | Type | Description | 690 | | --- | --- | --- | 691 | | 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. | 692 | | [value] | string | Value to use as `this` when executing function | 693 | 694 | 695 | 696 | ## sweep(pathOrFn, [value]) ⇒ number 697 | Deletes entries that satisfy the provided filter function or value matching. 698 | 699 | **Kind**: instance method of Enmap 700 | **Returns**: number - The number of removed entries. 701 | 702 | | Param | Type | Description | 703 | | --- | --- | --- | 704 | | pathOrFn | function \| string | The path to the value to test against, or the function to test with. | 705 | | [value] | \* | The expected value - only applicable and required if using a property for the first argument. | 706 | 707 | 708 | 709 | ## changed(cb) 710 | Function called whenever data changes within Enmap after the initial load. 711 | Can be used to detect if another part of your code changed a value in enmap and react on it. 712 | 713 | **Kind**: instance method of Enmap 714 | 715 | | Param | Type | Description | 716 | | --- | --- | --- | 717 | | cb | function | A callback function that will be called whenever data changes in the enmap. | 718 | 719 | **Example** 720 | ```js 721 | enmap.changed((keyName, oldValue, newValue) => { 722 | console.log(`Value of ${keyName} has changed from: \n${oldValue}\nto\n${newValue}`); 723 | }); 724 | ``` 725 | 726 | 727 | ## partition(pathOrFn, value) ⇒ Array.<Array.<\*>> 728 | Separates the Enmap into multiple arrays given a function that separates them. 729 | 730 | **Kind**: instance method of Enmap 731 | **Returns**: Array.<Array.<\*>> - An array of arrays with the partitioned data. 732 | 733 | | Param | Type | Description | 734 | | --- | --- | --- | 735 | | pathOrFn | \* | the path to the value to test against, or the function to test with. | 736 | | value | \* | the value to use as a condition for partitioning. | 737 | 738 | --------------------------------------------------------------------------------