├── .editorconfig ├── .github ├── funding.yml └── workflows │ └── ci.yml ├── .gitignore ├── .npmrc ├── license ├── package.json ├── readme.md ├── src └── index.ts ├── test └── index.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.yml] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.github/funding.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: xxczaki 4 | patreon: akepinski 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: CI 5 | 6 | on: 7 | push: 8 | branches: [master] 9 | pull_request: 10 | branches: [master] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [14.x] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v2-beta 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: npm install 27 | - run: npm run build --if-present 28 | - run: npm test -- --colors 29 | 30 | - name: Coveralls 31 | uses: coverallsapp/github-action@master 32 | with: 33 | github-token: ${{ secrets.GITHUB_TOKEN }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Lockfiles 2 | 3 | package-lock.json 4 | yarn.lock 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | *.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env 64 | 65 | # next.js build output 66 | .next 67 | 68 | # Transpilation output 69 | dist 70 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Antoni Kepinski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "attoid", 3 | "version": "1.0.0", 4 | "description": "Secure URL-friendly unique string ID generator in ~10 LOC", 5 | "main": "dist/index.js", 6 | "module": "dist/index.esm.js", 7 | "types": "dist/index.d.ts", 8 | "files": [ 9 | "dist/**/*" 10 | ], 11 | "author": "Antoni Kepinski (https://kepinski.me)", 12 | "bugs": { 13 | "url": "https://github.com/xxczaki/attoid/issues" 14 | }, 15 | "scripts": { 16 | "prebuild": "del-cli dist", 17 | "esm": "tsc --module esnext && cpy dist/index.js dist --rename index.esm.js", 18 | "cjs": "tsc --module commonjs", 19 | "build": "npm run esm && npm run cjs", 20 | "test": "xo && c8 ava && c8 report --reporter=lcov", 21 | "benchmark": "ts-node -T benchmark.ts", 22 | "prepublishOnly": "npm run build" 23 | }, 24 | "engines": { 25 | "node": ">=14.10.0" 26 | }, 27 | "license": "MIT", 28 | "repository": "xxczaki/attoid", 29 | "homepage": "https://github.com/xxczaki/attoid", 30 | "keywords": [ 31 | "attoid", 32 | "atto-id", 33 | "nanoid", 34 | "nano-id", 35 | "uuid", 36 | "uuid-4", 37 | "uuid-v4", 38 | "uuidv4", 39 | "random", 40 | "secure", 41 | "secure-random-string", 42 | "generator", 43 | "id", 44 | "shortid", 45 | "secure-id", 46 | "random-id" 47 | ], 48 | "devDependencies": { 49 | "@akepinski/tsconfig": "0.0.2", 50 | "@typescript-eslint/eslint-plugin": "^4.4.0", 51 | "@typescript-eslint/parser": "^4.4.0", 52 | "ava": "^3.13.0", 53 | "benchmark": "^2.1.4", 54 | "c8": "^7.3.3", 55 | "cli-table3": "^0.6.0", 56 | "coveralls": "^3.1.0", 57 | "cpy-cli": "^3.1.1", 58 | "del-cli": "^3.0.1", 59 | "eslint-config-xo-typescript": "^0.33.0", 60 | "ts-node": "^9.0.0", 61 | "typescript": "^4.0.3", 62 | "xo": "^0.33.1" 63 | }, 64 | "sideEffects": false, 65 | "ava": { 66 | "extensions": [ 67 | "ts" 68 | ], 69 | "require": [ 70 | "ts-node/register" 71 | ] 72 | }, 73 | "xo": { 74 | "extends": "xo-typescript", 75 | "extensions": [ 76 | "ts" 77 | ], 78 | "rules": { 79 | "import/no-anonymous-default-export": 0 80 | } 81 | }, 82 | "dependencies": {} 83 | } 84 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Atto ID 2 | 3 | > Secure URL-friendly unique string ID generator in ~8 LOC. 4 | 5 | [![Build Status](https://github.com/xxczaki/attoid/workflows/CI/badge.svg)](https://github.com/xxczaki/attoid/actions?query=workflow%3ACI) 6 | [![Coverage Status](https://coveralls.io/repos/github/xxczaki/attoid/badge.svg?branch=master)](https://coveralls.io/github/xxczaki/attoid?branch=master) 7 | [![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/xojs/xo) 8 | 9 | This module generates secure, URL-friendly and truly random IDs. It was inspired mainly by [Nano ID](https://github.com/ai/nanoid). It uses the new [`crypto.randomInt`](https://nodejs.org/api/crypto.html#crypto_crypto_randomint_min_max_callback) method introduced in Node.js v14.10.0. Besides limited compatibility, the performance is also relatively low in comparison with similar libraries (about 20k ops/sec). 10 | 11 | --- 12 | 13 | ## Highlights 14 | 15 | - Tiny size (<8 LOC) 16 | - Same default ID length as in Nano ID 17 | - Bigger default dictionary (includes 2 additional characters - `~` and `.`) 18 | - Uses the truly random Crypto API 19 | - URL-friendly 20 | - Ability to customize dictionary 21 | - Well tested 22 | - Written in TypeScript 23 | 24 | ## Install 25 | 26 | ``` 27 | $ npm install attoid 28 | ``` 29 | 30 | ## Usage 31 | 32 | ```js 33 | import {attoid} from 'attoid'; 34 | 35 | attoid(); // => V_SiU1mrfle.wZD9YbBQ 36 | ``` 37 | 38 | ## API 39 | 40 | ### attoid(length?, dictionary?) 41 | 42 | Returns a random string. 43 | 44 | ##### length 45 | 46 | Type: `number`\ 47 | Default: `21` 48 | 49 | Length of the generated string. 50 | 51 | ##### dictionary 52 | 53 | Type: `string`\ 54 | Default: `aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ1234567890-._~` 55 | 56 | Custom dictionary, from which the string should be generated. 57 | 58 | ## License 59 | 60 | MIT © [Antoni Kepinski](https://kepinski.me) 61 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import {randomInt} from 'crypto'; 2 | 3 | /** 4 | * Generate a unique and URL-friendly string. 5 | * 6 | * @param {number=21} length - Length of the generated string, defaults to 21 characters. 7 | * @param {number='aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ1234567890-._~'} dictionary - Custom dictionary, from which the string should be generated. 8 | * @return {string} Random string. 9 | * 10 | * @example 11 | * attoid(); 12 | * attoid(10); 13 | */ 14 | export const attoid = (length = 21, dictionary = 'aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ1234567890-._~'): string => { 15 | let id = ''; 16 | 17 | while (length--) { 18 | id += dictionary[randomInt(dictionary.length)]; 19 | } 20 | 21 | return id; 22 | }; 23 | -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import {attoid} from '../src'; 3 | 4 | test('default length', t => { 5 | const id = attoid(); 6 | 7 | t.is(id.length, 21); 8 | }); 9 | 10 | test('custom length', t => { 11 | const id = attoid(10); 12 | 13 | t.is(id.length, 10); 14 | }); 15 | 16 | test('custom dictionary', t => { 17 | const id = attoid(5, 'a'); 18 | 19 | t.is(id, 'aaaaa'); 20 | }); 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@akepinski/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "target": "es2018", 6 | "sourceMap": false, 7 | "incremental": false, 8 | "lib": [ 9 | "es2018" 10 | ] 11 | }, 12 | "include": [ 13 | "src/**/*" 14 | ] 15 | } 16 | --------------------------------------------------------------------------------