├── .eslintrc.cjs ├── .gitignore ├── LICENSE ├── README.md ├── build.js ├── dist ├── okeydb.browser.js ├── okeydb.browser.mjs └── okeydb.cjs ├── index.js ├── lib ├── db.js ├── operator.js ├── platform │ ├── browser │ │ ├── index.js │ │ └── storage.js │ ├── index.js │ └── node │ │ ├── index.js │ │ └── storage.js ├── query.js ├── table.js └── utils.js ├── package.json └── test ├── dist └── okeydb.browser.mjs ├── index.html ├── index.js ├── index.test.js └── operator.test.js /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | module.exports = { 3 | extends: 'eslint:recommended', 4 | env: { 5 | browser: true, 6 | node: true, 7 | es2021: true, 8 | jest: true, 9 | }, 10 | overrides: [ 11 | { 12 | env: { 13 | node: true, 14 | }, 15 | files: [ 16 | '.eslintrc.{js,cjs}', 17 | ], 18 | parserOptions: { 19 | sourceType: 'script', 20 | }, 21 | }, 22 | ], 23 | parserOptions: { 24 | ecmaVersion: 'latest', 25 | sourceType: 'module', 26 | }, 27 | rules: { 28 | semi: 'error', 29 | complexity: ['warn', 25], 30 | 'no-var': 'error', 31 | 'no-unused-vars': 'warn', 32 | 'no-restricted-globals': 'off', 33 | 'max-params': ['warn', 7], 34 | 'no-console': 'warn', 35 | 'no-new-func': 'off', 36 | 'import/no-named-as-default': 'off', 37 | 'import/no-named-as-default-member': 'off', 38 | 'no-return-await': 'off', 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log* 3 | package-lock.json 4 | .nyc_*/ 5 | .dir-locals.el 6 | .DS_Store 7 | .test 8 | .db 9 | .meta -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Yvo 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OkeyDB 2 | 3 | OkeyDB is a light-weight document oriented NoSQL database based on local file-system and [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API). 4 | 5 | Pure JavaScript NoSQL database with no dependency. IndexedDB, Flat file, JSON based document database, running in Web Browser and Node.js. 6 | 7 | **Its api is compatible with the [aircode database API](https://docs.aircode.io/reference/server/database-api).** 8 | 9 | ## Features 10 | 11 | - Available in Web Browser and Node.js. 12 | - Use IndexedDB in Web Browser. 13 | - Use JSON file storage in Node.js, no need to install any database. 14 | - A convenient and easy-to-use chained API that returns Promises. 15 | - Powerful combined conditional queries. 16 | 17 | ## Usage 18 | 19 | ### In Web Browser 20 | 21 | ```html 22 | 23 | 24 | 25 | 26 | 27 | Document 28 | 29 | 30 | 61 | 62 | 63 | ``` 64 | 65 | ### In Node.js 66 | 67 | ```js 68 | import {OkeyDB} from 'okeydb'; 69 | 70 | const db = new OkeyDB(); 71 | const personTable = db.table('person'); 72 | const students = []; 73 | 74 | function randomScore(low = 0, high = 100) { 75 | return low + Math.floor(Math.random() * (high - low + 1)); 76 | } 77 | 78 | for(let i = 0; i < 1000; i++) { 79 | const student = {name: `student${i}`, score: randomScore()}; 80 | students.push(student); 81 | } 82 | await personTable.save(students); 83 | 84 | // find all students that score >= 60 85 | const result = await personTable.where({score: db.gte(60)}).find(); 86 | console.log(result); 87 | ``` 88 | 89 | ### Database API 90 | 91 | - [Table](https://docs.aircode.io/reference/server/database-api#table) 92 | - [db.table(tableName)](https://docs.aircode.io/reference/server/database-api#db-table-tablename) 93 | - [Table.save(record | arrayOfRecords)](https://docs.aircode.io/reference/server/database-api#table-save-record-arrayofrecords) 94 | - [Table.delete(record | arrayOfRecords)](https://docs.aircode.io/reference/server/database-api#table-delete-record-arrayofrecords) 95 | - [Table.where([conditions])](https://docs.aircode.io/reference/server/database-api#table-where-conditions) 96 | - [Query Commands](https://docs.aircode.io/reference/server/database-api#query-commands) 97 | - [Query.find()](https://docs.aircode.io/reference/server/database-api#query-find) 98 | - [Query.findOne()](https://docs.aircode.io/reference/server/database-api#query-findone) 99 | - [Query.count()](https://docs.aircode.io/reference/server/database-api#query-count) 100 | - [Query.save()](https://docs.aircode.io/reference/server/database-api#query-save) 101 | - [Query.delete()](https://docs.aircode.io/reference/server/database-api#query-delete) 102 | - [Sort and Pagination Chain](https://docs.aircode.io/reference/server/database-api#sort-and-pagination-chain) 103 | - [Query.sort(conditions)](https://docs.aircode.io/reference/server/database-api#query-sort-conditions) 104 | - [Query.skip(n)](https://docs.aircode.io/reference/server/database-api#query-skip-n) 105 | - [Query.limit(n)](https://docs.aircode.io/reference/server/database-api#query-limit-n) 106 | - [Projection Chain](https://docs.aircode.io/reference/server/database-api#projection-chain) 107 | - [Query.projection(conditions)](https://docs.aircode.io/reference/server/database-api#query-projection-conditions) 108 | - [Update Chain](https://docs.aircode.io/reference/server/database-api#update-chain) 109 | - [Query.set(conditions)](https://docs.aircode.io/reference/server/database-api#query-set-conditions) 110 | - [Query.upsert([boolean=true])](https://docs.aircode.io/reference/server/database-api#query-upsert-boolean-true) 111 | - [Query.setOnInsert(object)](https://docs.aircode.io/reference/server/database-api#query-setoninsert-object) 112 | - [Logical Chain](https://docs.aircode.io/reference/server/database-api#logical-chain) 113 | - [Query.and(...filters)](https://docs.aircode.io/reference/server/database-api#query-and-filters) 114 | - [Query.or(...filters)](https://docs.aircode.io/reference/server/database-api#query-or-filters) 115 | - [Query.nor(...filters)](https://docs.aircode.io/reference/server/database-api#query-nor-filters) 116 | - [Comparison Operators](https://docs.aircode.io/reference/server/database-api#comparison-operators) 117 | - [db.gt(value)](https://docs.aircode.io/reference/server/database-api#db-gt-value) 118 | - [db.gte(value)](https://docs.aircode.io/reference/server/database-api#db-gte-value) 119 | - [db.lt(value)](https://docs.aircode.io/reference/server/database-api#db-lt-value) 120 | - [db.lte(value)](https://docs.aircode.io/reference/server/database-api#db-lte-value) 121 | - [db.ne(value)](https://docs.aircode.io/reference/server/database-api#db-ne-value) 122 | - [db.in(array)](https://docs.aircode.io/reference/server/database-api#db-in-array) 123 | - [db.nin(array)](https://docs.aircode.io/reference/server/database-api#db-nin-array) 124 | - [Element Operators](https://docs.aircode.io/reference/server/database-api#element-operators) 125 | - [db.exists(boolean)](https://docs.aircode.io/reference/server/database-api#db-exists-boolean) 126 | - [db.type(typeString)](https://docs.aircode.io/reference/server/database-api#db-type-typestring) 127 | - [Evaluation Operators](https://docs.aircode.io/reference/server/database-api#evaluation-operators) 128 | - [db.mod(divisor, remainder)](https://docs.aircode.io/reference/server/database-api#db-mod-divisor-remainder) 129 | - [Array Operators](https://docs.aircode.io/reference/server/database-api#array-operators) 130 | - [db.all(array)](https://docs.aircode.io/reference/server/database-api#db-all-array) 131 | - [db.elemMatch(conditions)](https://docs.aircode.io/reference/server/database-api#db-elemmatch-conditions) 132 | - [db.size(n)](https://docs.aircode.io/reference/server/database-api#db-size-n) 133 | - [Bitwise Operators](https://docs.aircode.io/reference/server/database-api#bitwise-operators) 134 | - [db.bitsAllClear(positions)](https://docs.aircode.io/reference/server/database-api#db-bitsallclear-positions) 135 | - [db.bitsAllSet(positions)](https://docs.aircode.io/reference/server/database-api#db-bitsallset-positions) 136 | - [db.bitsAnyClear(positions)](https://docs.aircode.io/reference/server/database-api#db-bitsanyclear-positions) 137 | - [db.bitsAnySet(positions)](https://docs.aircode.io/reference/server/database-api#db-bitsanyset-positions) 138 | - ~~Geospatial Objects~~ 139 | - ~~Point~~ 140 | - ~~LineString~~ 141 | - ~~Polygon~~ 142 | - ~~MultiPoint~~ 143 | - ~~MultiLineString~~ 144 | - ~~MultiPolygon~~ 145 | - ~~GeometryCollection~~ 146 | - ~~Geospatial Operators~~ 147 | - ~~db.geoIntersects(conditions)~~ 148 | - ~~db.geoWithin(conditions)~~ 149 | - ~~db.near(conditions)~~ 150 | - ~~db.nearSphere(conditions)~~ 151 | - [Update Operators](https://docs.aircode.io/reference/server/database-api#update-operators) 152 | - [db.inc(value)](https://docs.aircode.io/reference/server/database-api#db-inc-value) 153 | - [db.mul(value)](https://docs.aircode.io/reference/server/database-api#db-mul-value) 154 | - [db.min(value)](https://docs.aircode.io/reference/server/database-api#db-min-value) 155 | - [db.max(value)](https://docs.aircode.io/reference/server/database-api#db-max-value) 156 | - [db.rename(name)](https://docs.aircode.io/reference/server/database-api#db-rename-name) 157 | - [db.unset()](https://docs.aircode.io/reference/server/database-api#db-unset) 158 | - [db.currentDate()](https://docs.aircode.io/reference/server/database-api#db-currentdate) 159 | - [Logical Operators](https://docs.aircode.io/reference/server/database-api#logical-operators) 160 | - [db.and(...filters)](https://docs.aircode.io/reference/server/database-api#db-and-filters) 161 | - [db.or(...filters)](https://docs.aircode.io/reference/server/database-api#db-or-filters) 162 | - [db.nor(...filters)](https://docs.aircode.io/reference/server/database-api#db-nor-filters) 163 | - [db.not(condition)](https://docs.aircode.io/reference/server/database-api#db-not-condition) 164 | 165 | ## Limits 166 | 167 | You can use OkeyDB in your web app as client database. 168 | 169 | - **But due to the following reasons, it is NOT recommended for use in a Node.js production environment:** 170 | - This is just a single-instance text database and does not have the ability to scale across multiple servers. 171 | - Without using schema constraints and indexes(not implemented yet), the storage performance is limited by the usage of JSON.parse and JSON.stringify. Additionally, the query efficiency is limited by the size of the data. 172 | 173 | If you want to deploy your code to a production environment, you can seamlessly migrate to [AirCode](https://aircode.io/) and use `aircode.db`. 174 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | import * as esbuild from 'esbuild'; 2 | 3 | 4 | if(process.env.mode === 'production') { 5 | await esbuild.build({ 6 | entryPoints: ['index.js'], 7 | bundle: true, 8 | platform: 'node', 9 | format: 'cjs', 10 | outfile: 'dist/okeydb.cjs', 11 | define: { 12 | 'ESB_PLATFORM': '"node"', 13 | } 14 | }); 15 | 16 | await esbuild.build({ 17 | entryPoints: ['index.js'], 18 | bundle: true, 19 | platform: 'browser', 20 | globalName: 'OkeyDB', 21 | outfile: 'dist/okeydb.browser.js', 22 | define: { 23 | 'ESB_PLATFORM': '"browser"', 24 | } 25 | }); 26 | 27 | await esbuild.build({ 28 | entryPoints: ['index.js'], 29 | bundle: true, 30 | platform: 'browser', 31 | format: 'esm', 32 | outfile: 'dist/okeydb.browser.mjs', 33 | define: { 34 | 'ESB_PLATFORM': '"browser"', 35 | } 36 | }); 37 | } else { 38 | const ctx = await esbuild.context({ 39 | entryPoints: ['index.js'], 40 | bundle: true, 41 | platform: 'browser', 42 | format: 'esm', 43 | outfile: 'test/dist/okeydb.browser.mjs', 44 | define: { 45 | 'ESB_PLATFORM': '"browser"', 46 | } 47 | }); 48 | const server = await ctx.serve({ 49 | servedir: './test', 50 | }); 51 | console.log(`Server is running at ${server.host}:${server.port}`); 52 | } 53 | 54 | -------------------------------------------------------------------------------- /dist/okeydb.browser.mjs: -------------------------------------------------------------------------------- 1 | var __defProp = Object.defineProperty; 2 | var __getOwnPropNames = Object.getOwnPropertyNames; 3 | var __esm = (fn, res) => function __init() { 4 | return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; 5 | }; 6 | var __export = (target, all) => { 7 | for (var name in all) 8 | __defProp(target, name, { get: all[name], enumerable: true }); 9 | }; 10 | 11 | // lib/platform/browser/storage.js 12 | var Storage; 13 | var init_storage = __esm({ 14 | "lib/platform/browser/storage.js"() { 15 | Storage = class { 16 | #storage; 17 | constructor(storage) { 18 | this.#storage = storage; 19 | } 20 | transaction(type = "readonly") { 21 | const name = this.#storage.tableName; 22 | return this.#storage.db.transaction([name], type).objectStore(name); 23 | } 24 | add(records) { 25 | const promises = records.map((record) => { 26 | return new Promise((resolve, reject) => { 27 | const request = this.transaction("readwrite").add(record); 28 | request.onsuccess = function() { 29 | resolve(request.result); 30 | }; 31 | request.onerror = function() { 32 | reject(new Error("Datebase error.")); 33 | }; 34 | }); 35 | }); 36 | return Promise.all(promises); 37 | } 38 | getItemIndex(id) { 39 | return id; 40 | } 41 | put(idx, record) { 42 | return new Promise((resolve, reject) => { 43 | const request = this.transition("readwrite").put(record); 44 | request.onsuccess = function() { 45 | resolve(request.result); 46 | }; 47 | request.onerror = function() { 48 | reject(new Error("Datebase error.")); 49 | }; 50 | }); 51 | } 52 | delete(deleteMap) { 53 | const promises = []; 54 | for (const id of Object.keys(deleteMap)) { 55 | promises.push(new Promise((resolve, reject) => { 56 | const request = this.transaction("readwrite").delete(id); 57 | request.onsuccess = function() { 58 | resolve(request.result); 59 | }; 60 | request.onerror = function() { 61 | reject(new Error("Datebase error.")); 62 | }; 63 | })); 64 | } 65 | return Promise.all(promises); 66 | } 67 | }; 68 | } 69 | }); 70 | 71 | // lib/platform/browser/index.js 72 | var browser_exports = {}; 73 | __export(browser_exports, { 74 | createTable: () => createTable, 75 | fileSync: () => fileSync, 76 | flushData: () => flushData, 77 | getRecords: () => getRecords 78 | }); 79 | function upgradeDB(metaDB) { 80 | return new Promise((resolve, reject) => { 81 | const transaction = metaDB.transaction(["version"], "readwrite"); 82 | const objectStore = transaction.objectStore("version"); 83 | const request = objectStore.get(1); 84 | request.onerror = function() { 85 | reject(new Error(request)); 86 | }; 87 | request.onsuccess = function() { 88 | const req = objectStore.put({ id: 1, version: request.result.version + 1 }); 89 | req.onerror = function() { 90 | reject(new Error(req)); 91 | }; 92 | req.onsuccess = function() { 93 | resolve(request.result.version + 1); 94 | }; 95 | }; 96 | }); 97 | } 98 | async function createTable(table) { 99 | const dbName = table.database.name; 100 | const meta = `${dbName}.__meta__`; 101 | const tableName = table.name; 102 | if (!dbInstances[tableName]) { 103 | const metaDB = await new Promise((resolve, reject) => { 104 | const request = window.indexedDB.open(meta); 105 | request.onerror = function() { 106 | reject(new Error(request)); 107 | }; 108 | request.onsuccess = function() { 109 | const db2 = request.result; 110 | resolve(db2); 111 | }; 112 | request.onupgradeneeded = function() { 113 | const db2 = request.result; 114 | db2.createObjectStore("version", { keyPath: "id" }); 115 | db2.createObjectStore("tables", { keyPath: "name" }); 116 | }; 117 | }); 118 | if (!version) 119 | version = await new Promise((resolve, reject) => { 120 | const transaction = metaDB.transaction(["version"], "readwrite"); 121 | const objectStore = transaction.objectStore("version"); 122 | const request = objectStore.get(1); 123 | request.onerror = function() { 124 | reject(new Error(request)); 125 | }; 126 | request.onsuccess = function() { 127 | if (!request.result) { 128 | const req = objectStore.add({ id: 1, version: 0 }); 129 | req.onerror = function() { 130 | reject(new Error(req)); 131 | }; 132 | req.onsuccess = function() { 133 | resolve(0); 134 | }; 135 | } else { 136 | resolve(request.result.version); 137 | } 138 | }; 139 | }); 140 | const tableData = await new Promise((resolve, reject) => { 141 | const transaction = metaDB.transaction(["tables"], "readwrite"); 142 | const objectStore = transaction.objectStore("tables"); 143 | const request = objectStore.get(tableName); 144 | request.onerror = function() { 145 | reject(new Error(request)); 146 | }; 147 | request.onsuccess = function() { 148 | resolve(request.result); 149 | }; 150 | }); 151 | if (!tableData) { 152 | await new Promise((resolve, reject) => { 153 | const transaction = metaDB.transaction(["tables"], "readwrite"); 154 | const objectStore = transaction.objectStore("tables"); 155 | const request = objectStore.add({ name: tableName, indexes: table.indexes }); 156 | request.onerror = function() { 157 | reject(new Error(request)); 158 | }; 159 | request.onsuccess = function() { 160 | resolve(request.result); 161 | }; 162 | }); 163 | version = await upgradeDB(metaDB); 164 | } else { 165 | const needsUpdate = await new Promise((resolve, reject) => { 166 | const transaction = metaDB.transaction(["tables"], "readwrite"); 167 | const objectStore = transaction.objectStore("tables"); 168 | const request = objectStore.get(tableName); 169 | request.onerror = function() { 170 | reject(new Error(request)); 171 | }; 172 | request.onsuccess = function() { 173 | if (JSON.stringify(request.result.indexes) === JSON.stringify(table.indexes)) { 174 | resolve(false); 175 | } else { 176 | const req = objectStore.put({ name: tableName, indexes: table.indexes }); 177 | req.onerror = function() { 178 | reject(new Error(req)); 179 | }; 180 | req.onsuccess = function() { 181 | resolve(true); 182 | }; 183 | } 184 | }; 185 | }); 186 | if (needsUpdate) { 187 | version = await upgradeDB(metaDB); 188 | } 189 | } 190 | dbInstances[tableName] = await new Promise((resolve, reject) => { 191 | const request = window.indexedDB.open(dbName, version); 192 | request.onerror = function() { 193 | reject(new Error(request)); 194 | }; 195 | request.onsuccess = function() { 196 | resolve(request.result); 197 | }; 198 | request.onupgradeneeded = function() { 199 | const db2 = request.result; 200 | const upgradeTransaction = request.transaction; 201 | let objectStore; 202 | if (!db2.objectStoreNames.contains(tableName)) { 203 | objectStore = db2.createObjectStore(tableName, { keyPath: "_id" }); 204 | } else { 205 | objectStore = upgradeTransaction.objectStore(tableName); 206 | } 207 | const indexes = table.indexes; 208 | const len = objectStore.indexNames.length; 209 | for (let i = len - 1; i >= 0; i--) { 210 | objectStore.deleteIndex(objectStore.indexNames[i]); 211 | } 212 | for (const [k, v] of Object.entries(indexes)) { 213 | if (k !== "_id") { 214 | if (!objectStore.indexNames.contains(k)) { 215 | objectStore.createIndex(k, k, { unique: v }); 216 | } 217 | } 218 | } 219 | }; 220 | }); 221 | } 222 | const db = dbInstances[tableName]; 223 | table.database.instance = db; 224 | return new Storage({ db, tableName }); 225 | } 226 | async function fileSync() { 227 | } 228 | async function flushData() { 229 | } 230 | async function getRecords(table, { filter, sorter, skip, limit, filterIndexes, rawSorter } = {}) { 231 | const objectStore = table._storage.transaction(); 232 | const notIndexFilter = table[_notIndexFilter2]; 233 | if (filterIndexes) { 234 | const records = []; 235 | const indexes = Object.keys(filterIndexes); 236 | let singleIndex = false; 237 | for (let i = 0; i < indexes.length; i++) { 238 | const indexName = indexes[i]; 239 | const isUnique = table.indexes[indexName]; 240 | const indexValues = [...filterIndexes[indexName]]; 241 | const ret2 = await Promise.all(indexValues.map(async (value) => { 242 | if (indexName === "_id") { 243 | return new Promise((resolve, reject) => { 244 | const request = objectStore.get(value); 245 | request.onerror = function() { 246 | reject(new Error(request)); 247 | }; 248 | request.onsuccess = function() { 249 | resolve(request.result); 250 | }; 251 | }); 252 | } else if (isUnique && value && typeof value !== "function" && typeof value[_filter3] !== "function" && !(value instanceof RegExp)) { 253 | return new Promise((resolve, reject) => { 254 | const request = objectStore.index(indexName).get(value); 255 | request.onerror = function() { 256 | reject(new Error(request)); 257 | }; 258 | request.onsuccess = function() { 259 | resolve(request.result); 260 | }; 261 | }); 262 | } else if (value && typeof value !== "function" && typeof value[_filter3] !== "function" && !(value instanceof RegExp)) { 263 | return new Promise((resolve, reject) => { 264 | const request = objectStore.index(indexName).openCursor(IDBKeyRange.only(value)); 265 | const records2 = []; 266 | request.onerror = function() { 267 | reject(new Error(request)); 268 | }; 269 | request.onsuccess = function() { 270 | const cursor = request.result; 271 | if (cursor) { 272 | records2.push(cursor.value); 273 | cursor.continue(); 274 | } else { 275 | resolve(records2); 276 | } 277 | }; 278 | }); 279 | } else { 280 | const type = value._type; 281 | let range = null; 282 | if (type === "gt") { 283 | range = IDBKeyRange.lowerBound(value._value, true); 284 | } else if (type === "gte") { 285 | range = IDBKeyRange.lowerBound(value._value); 286 | } else if (type === "lt") { 287 | range = IDBKeyRange.upperBound(value._value, true); 288 | } else if (type === "lte") { 289 | range = IDBKeyRange.upperBound(value._value); 290 | } else if (type === "gtlt") { 291 | range = IDBKeyRange.bound(...value._value, true, true); 292 | } else if (type === "gtlte") { 293 | range = IDBKeyRange.bound(...value._value, true, false); 294 | } else if (type === "gtelt") { 295 | range = IDBKeyRange.bound(...value._value, false, true); 296 | } else if (type === "gtelte") { 297 | range = IDBKeyRange.bound(...value._value, false, false); 298 | } 299 | let direction = "next"; 300 | if (rawSorter) { 301 | const order = rawSorter[indexName]; 302 | if (order === -1 || order === "desc") 303 | direction = "prev"; 304 | if (indexes.length === 1 && indexValues.length === 1) { 305 | singleIndex = true; 306 | const keys = Object.keys(rawSorter); 307 | if (keys.length === 1 && keys[0] === indexName) { 308 | sorter = null; 309 | } 310 | } 311 | } 312 | return new Promise((resolve, reject) => { 313 | const request = objectStore.index(indexName).openCursor(range, direction); 314 | const records2 = []; 315 | request.onerror = function() { 316 | reject(new Error(request)); 317 | }; 318 | request.onsuccess = function() { 319 | const cursor = request.result; 320 | if (cursor) { 321 | if (singleIndex && !notIndexFilter && !sorter) { 322 | if (skip > 0) { 323 | cursor.advance(skip); 324 | skip = 0; 325 | } else { 326 | records2.push(cursor.value); 327 | if (records2.length === limit) { 328 | resolve(records2); 329 | } else { 330 | cursor.continue(); 331 | } 332 | } 333 | } else { 334 | if (filter(cursor.value)) { 335 | records2.push(cursor.value); 336 | } 337 | if (singleIndex && !sorter && records2.length === skip + limit) { 338 | resolve(records2); 339 | } else { 340 | cursor.continue(); 341 | } 342 | } 343 | } else { 344 | resolve(records2); 345 | } 346 | }; 347 | }); 348 | } 349 | })); 350 | records.push(...ret2.flat()); 351 | } 352 | if (singleIndex) { 353 | if (sorter) 354 | records.sort(sorter); 355 | return records.slice(skip, skip + limit); 356 | } 357 | const ret = []; 358 | const ids = /* @__PURE__ */ new Set(); 359 | for (let i = 0; i < records.length; i++) { 360 | const record = records[i]; 361 | if (!record || ids.has(record._id) || !filter(record)) 362 | continue; 363 | ids.add(record._id); 364 | ret.push(record); 365 | if (!sorter && (skip > 0 || Number.isFinite(limit)) && ret.length >= skip + limit) { 366 | return ret.slice(skip, skip + limit); 367 | } 368 | } 369 | if (sorter) 370 | ret.sort(sorter); 371 | if (skip > 0 || Number.isFinite(limit)) { 372 | return ret.slice(skip, skip + limit); 373 | } 374 | return ret; 375 | } else { 376 | if (rawSorter) { 377 | const keys = Object.keys(rawSorter); 378 | if (keys.length === 1) { 379 | const key = keys[0]; 380 | const order = rawSorter[key]; 381 | if (table.indexes[key] != null) { 382 | let direction = "next"; 383 | if (order === -1 || order === "desc") 384 | direction = "prev"; 385 | const records2 = await new Promise((resolve, reject) => { 386 | const request = objectStore.index(key).openCursor(null, direction); 387 | const records3 = []; 388 | request.onerror = function() { 389 | reject(new Error(request)); 390 | }; 391 | request.onsuccess = function() { 392 | const cursor = request.result; 393 | if (cursor) { 394 | if (!notIndexFilter) { 395 | if (skip > 0) { 396 | cursor.advance(skip); 397 | skip = 0; 398 | } else { 399 | records3.push(cursor.value); 400 | if (records3.length === limit) { 401 | resolve(records3); 402 | } else { 403 | cursor.continue(); 404 | } 405 | } 406 | } else { 407 | if (filter(cursor.value)) { 408 | records3.push(cursor.value); 409 | } 410 | if (records3.length === skip + limit) { 411 | resolve(records3); 412 | } else { 413 | cursor.continue(); 414 | } 415 | } 416 | } else { 417 | resolve(records3); 418 | } 419 | }; 420 | }); 421 | return records2.slice(skip, skip + limit); 422 | } 423 | } 424 | } 425 | const records = await new Promise((resolve, reject) => { 426 | const request = objectStore.index("createdAt").getAll(); 427 | request.onerror = function() { 428 | reject(new Error(request)); 429 | }; 430 | request.onsuccess = function() { 431 | resolve(request.result); 432 | }; 433 | }); 434 | let filtedRecords; 435 | if (!sorter && skip === 0 && limit === 1) { 436 | filtedRecords = records.find(filter); 437 | if (filtedRecords) 438 | return [filtedRecords]; 439 | return []; 440 | } else { 441 | filtedRecords = records.filter(filter); 442 | } 443 | if (sorter) 444 | filtedRecords.sort(sorter); 445 | if (skip > 0 || Number.isFinite(limit)) { 446 | filtedRecords = filtedRecords.slice(skip, skip + limit); 447 | } 448 | return filtedRecords; 449 | } 450 | } 451 | var dbInstances, _filter3, _notIndexFilter2, version; 452 | var init_browser = __esm({ 453 | "lib/platform/browser/index.js"() { 454 | init_storage(); 455 | dbInstances = {}; 456 | _filter3 = Symbol.for("okeydb-filter"); 457 | _notIndexFilter2 = Symbol.for("not-index-filter"); 458 | version = 0; 459 | } 460 | }); 461 | 462 | // lib/utils.js 463 | var _filter = Symbol.for("okeydb-filter"); 464 | function parseCondition(condition = {}) { 465 | if (typeof condition === "function") 466 | return condition; 467 | if (condition[_filter]) 468 | return condition[_filter]; 469 | const filters = []; 470 | for (const [k, v] of Object.entries(condition)) { 471 | if (typeof v === "function") { 472 | filters.push((d) => v(d[k], k, d)); 473 | } else if (v && typeof v[_filter] === "function") { 474 | const f = v[_filter]; 475 | filters.push((d) => f(d[k], k, d)); 476 | } else if (v instanceof RegExp) { 477 | filters.push((d) => d[k] && typeof d[k].match === "function" && d[k].match(v) != null); 478 | } else { 479 | filters.push((d) => d[k] === v); 480 | } 481 | } 482 | return (record) => filters.every((f) => f(record)); 483 | } 484 | function mergeConditions(conditions, type = "and") { 485 | const filters = []; 486 | for (let i = 0; i < conditions.length; i++) { 487 | filters.push(parseCondition(conditions[i])); 488 | } 489 | if (type === "and") { 490 | return (record) => filters.every((f) => f(record)); 491 | } else if (type === "or") { 492 | return (record) => filters.some((f) => f(record)); 493 | } else if (type === "nor") { 494 | return (record) => !filters.some((f) => f(record)); 495 | } 496 | } 497 | function getType(value) { 498 | let type = typeof value; 499 | if (type === "object" && Array.isArray(value)) { 500 | type = "array"; 501 | } else if (type === "object" && value instanceof Date) { 502 | type = "date"; 503 | } else if (type === "object" && value instanceof RegExp) { 504 | type = "regexp"; 505 | } else if (value == null) { 506 | type = "null"; 507 | } 508 | return type; 509 | } 510 | 511 | // lib/query.js 512 | var _notIndexFilter = Symbol.for("not-index-filter"); 513 | function updateFilterIndex(query, conditions, filterIndexes = {}, phase = "and") { 514 | const indexes = query.table.indexes; 515 | let notIndexFilter = false; 516 | for (let i = 0; i < conditions.length; i++) { 517 | const condition = conditions[i]; 518 | let hasIndex = false; 519 | for (const [k, v] of Object.entries(condition)) { 520 | if (k in indexes) { 521 | hasIndex = true; 522 | filterIndexes[k] = filterIndexes[k] || /* @__PURE__ */ new Set(); 523 | filterIndexes[k].add(v); 524 | if (phase === "and" && filterIndexes[k].size > 1) 525 | filterIndexes[k].clear(); 526 | } else { 527 | notIndexFilter = true; 528 | } 529 | } 530 | if (!hasIndex && phase === "or") { 531 | query.table[_notIndexFilter] = notIndexFilter; 532 | return null; 533 | } 534 | } 535 | query.table[_notIndexFilter] = notIndexFilter; 536 | return filterIndexes; 537 | } 538 | var _filter2 = Symbol.for("okeydb-filter"); 539 | var query_default = class { 540 | #table; 541 | #filter; 542 | #records; 543 | #sorter = null; 544 | #rawSorter; 545 | #skip = 0; 546 | #limit = Infinity; 547 | #projection = null; 548 | #updateFields = null; 549 | #insertFields = {}; 550 | #setOnInsertFields = null; 551 | #upsert = false; 552 | #filterIndexes = {}; 553 | constructor(condition, table) { 554 | this.#table = table; 555 | if (condition) { 556 | this.#filter = mergeConditions([condition]); 557 | this.#insertFields = { ...condition }; 558 | this.#filterIndexes = updateFilterIndex(this, [condition], {}, "and"); 559 | } 560 | } 561 | and(...conditions) { 562 | const left = this.#filter; 563 | const right = mergeConditions(conditions); 564 | if (left) { 565 | this.#filter = (record) => left(record) && right(record); 566 | } else { 567 | this.#filter = right; 568 | } 569 | for (let i = 0; i < conditions.length; i++) { 570 | Object.assign(this.#insertFields, conditions[i]); 571 | } 572 | if (this.#filterIndexes) 573 | this.#filterIndexes = updateFilterIndex(this, conditions, this.#filterIndexes, "and"); 574 | return this; 575 | } 576 | or(...conditions) { 577 | const left = this.#filter; 578 | const right = mergeConditions(conditions, "or"); 579 | if (left) { 580 | this.#filter = (record) => left(record) || right(record); 581 | } else { 582 | this.#filter = right; 583 | } 584 | this.#insertFields = {}; 585 | if (this.#filterIndexes) 586 | this.#filterIndexes = updateFilterIndex(this, conditions, this.#filterIndexes, "or"); 587 | return this; 588 | } 589 | nor(...conditions) { 590 | const left = this.#filter; 591 | const right = mergeConditions(conditions, "or"); 592 | if (left) { 593 | this.#filter = (record) => !(left(record) || right(record)); 594 | } else { 595 | this.#filter = (record) => !right(record); 596 | } 597 | this.#insertFields = {}; 598 | this.#filterIndexes = null; 599 | this.table[_notIndexFilter] = true; 600 | return this; 601 | } 602 | async find() { 603 | let filtedRecords = await this.#table.getRecords({ 604 | filter: this.#filter || function() { 605 | return true; 606 | }, 607 | sorter: this.#sorter, 608 | rawSorter: this.#rawSorter, 609 | skip: this.#skip, 610 | limit: this.#limit, 611 | filterIndexes: this.filterIndexes 612 | }); 613 | if (this.#projection) { 614 | const { type, fields } = this.#projection; 615 | if (type === "inclusion") { 616 | filtedRecords = filtedRecords.map((r) => { 617 | const ret = {}; 618 | fields.forEach((f) => ret[f] = r[f]); 619 | return ret; 620 | }); 621 | } else if (type === "exclusion") { 622 | filtedRecords = filtedRecords.map((r) => { 623 | const ret = { ...r }; 624 | fields.forEach((f) => delete ret[f]); 625 | return ret; 626 | }); 627 | } 628 | } 629 | this.#records = filtedRecords; 630 | return filtedRecords; 631 | } 632 | async findOne() { 633 | const records = await this.#table.getRecords({ 634 | filter: this.#filter || function() { 635 | return true; 636 | }, 637 | sorter: this.#sorter, 638 | rawSorter: this.#rawSorter, 639 | skip: this.#skip, 640 | limit: 1, 641 | filterIndexes: this.filterIndexes 642 | }); 643 | const record = records[0]; 644 | if (this.#projection) { 645 | const { type, fields } = this.#projection; 646 | const ret = {}; 647 | if (type === "inclusion") { 648 | fields.forEach((f) => ret[f] = record[f]); 649 | } else if (type === "exclusion") { 650 | Object.assign(ret, record); 651 | fields.forEach((f) => delete ret[f]); 652 | } 653 | return ret; 654 | } 655 | return record; 656 | } 657 | async count() { 658 | if (this.#records) 659 | return this.#records.length; 660 | return await this.find().length; 661 | } 662 | set(fields) { 663 | this.#updateFields = fields; 664 | return this; 665 | } 666 | setOnInsert(fields) { 667 | this.#setOnInsertFields = fields; 668 | return this; 669 | } 670 | upsert(flag) { 671 | this.#upsert = flag; 672 | return this; 673 | } 674 | async save() { 675 | if (this.#updateFields || this.#upsert) { 676 | let records = this.#records; 677 | if (!records) 678 | records = await this.find(); 679 | if (records.length <= 0 && this.#upsert) { 680 | records = Object.assign({}, this.#insertFields, this.#setOnInsertFields); 681 | for (let [k, v] of Object.entries(records)) { 682 | if (v && typeof v[_filter2] === "function") { 683 | v = v[_filter2]; 684 | } 685 | if (typeof v === "function") { 686 | records[k] = v(records[k], k, records); 687 | } 688 | } 689 | if (this.#updateFields) { 690 | const updateFields = this.#updateFields; 691 | for (let [k, v] of Object.entries(updateFields)) { 692 | if (v && typeof v[_filter2] === "function") { 693 | v = v[_filter2]; 694 | } 695 | if (typeof v !== "function") { 696 | records[k] = v; 697 | } else { 698 | records[k] = v(records[k], k, records); 699 | if (records[k] === void 0) 700 | delete records[k]; 701 | } 702 | } 703 | } 704 | } else if (this.#updateFields) { 705 | const updateFields = this.#updateFields; 706 | records = records.map((record) => { 707 | const ret = { ...record }; 708 | for (let [k, v] of Object.entries(updateFields)) { 709 | if (v && typeof v[_filter2] === "function") { 710 | v = v[_filter2]; 711 | } 712 | if (typeof v !== "function") { 713 | ret[k] = v; 714 | } else { 715 | ret[k] = v(ret[k], k, ret); 716 | if (ret[k] === void 0) 717 | delete ret[k]; 718 | } 719 | } 720 | return ret; 721 | }); 722 | } else { 723 | return await this.#table.save([], true); 724 | } 725 | return await this.#table.save(records, true); 726 | } 727 | throw new Error("Must use set or upsert at least once"); 728 | } 729 | async delete() { 730 | let records = this.#records; 731 | if (!records) 732 | records = await this.find(); 733 | return await this.#table.delete(records); 734 | } 735 | sort(conditions) { 736 | const conds = Object.entries(conditions); 737 | this.#rawSorter = conditions; 738 | this.#sorter = (a, b) => { 739 | for (let [k, v] of conds) { 740 | if (typeof v === "string") { 741 | if (v.toLowerCase() === "asc") { 742 | v = 1; 743 | } else if (v.toLowerCase() === "desc") { 744 | v = -1; 745 | } 746 | } 747 | if (v !== 1 && v !== -1) 748 | throw new Error(`Invalid sort condition: ${k} ${v}`); 749 | if (a[k] != b[k]) { 750 | return a[k] > b[k] ? v * 1 : v * -1; 751 | } 752 | } 753 | return 0; 754 | }; 755 | return this; 756 | } 757 | skip(n) { 758 | this.#skip = n; 759 | return this; 760 | } 761 | limit(n) { 762 | this.#limit = n; 763 | return this; 764 | } 765 | projection(conditions) { 766 | let type = null; 767 | const fields = []; 768 | let ignoreId = false; 769 | for (const [k, v] of Object.entries(conditions)) { 770 | if (k === "_id") { 771 | ignoreId = !v; 772 | continue; 773 | } 774 | if (!type && v) 775 | type = "inclusion"; 776 | else if (!type && !v) 777 | type = "exclusion"; 778 | else if (type === "inclusion" && !v || type === "exclusion" && v) 779 | throw new Error("Projection cannot have a mix of inclusion and exclusion."); 780 | fields.push(k); 781 | } 782 | if (type === "exclusion" && ignoreId || type === "inclusion" && !ignoreId) { 783 | fields.push("_id"); 784 | } 785 | if (type === "exclusion" && !ignoreId) { 786 | throw new Error("Projection cannot have a mix of inclusion and exclusion."); 787 | } 788 | this.#projection = { type: type || "inclusion", fields }; 789 | return this; 790 | } 791 | get table() { 792 | return this.#table; 793 | } 794 | get filterIndexes() { 795 | const filterIndexes = this.#filterIndexes || {}; 796 | if (Object.keys(filterIndexes).length) 797 | return filterIndexes; 798 | return null; 799 | } 800 | }; 801 | 802 | // node_modules/uuid/dist/esm-browser/rng.js 803 | var getRandomValues; 804 | var rnds8 = new Uint8Array(16); 805 | function rng() { 806 | if (!getRandomValues) { 807 | getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto); 808 | if (!getRandomValues) { 809 | throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported"); 810 | } 811 | } 812 | return getRandomValues(rnds8); 813 | } 814 | 815 | // node_modules/uuid/dist/esm-browser/stringify.js 816 | var byteToHex = []; 817 | for (let i = 0; i < 256; ++i) { 818 | byteToHex.push((i + 256).toString(16).slice(1)); 819 | } 820 | function unsafeStringify(arr, offset = 0) { 821 | return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); 822 | } 823 | 824 | // node_modules/uuid/dist/esm-browser/native.js 825 | var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto); 826 | var native_default = { 827 | randomUUID 828 | }; 829 | 830 | // node_modules/uuid/dist/esm-browser/v4.js 831 | function v4(options, buf, offset) { 832 | if (native_default.randomUUID && !buf && !options) { 833 | return native_default.randomUUID(); 834 | } 835 | options = options || {}; 836 | const rnds = options.random || (options.rng || rng)(); 837 | rnds[6] = rnds[6] & 15 | 64; 838 | rnds[8] = rnds[8] & 63 | 128; 839 | if (buf) { 840 | offset = offset || 0; 841 | for (let i = 0; i < 16; ++i) { 842 | buf[offset + i] = rnds[i]; 843 | } 844 | return buf; 845 | } 846 | return unsafeStringify(rnds); 847 | } 848 | var v4_default = v4; 849 | 850 | // lib/table.js 851 | var Table = (() => { 852 | let platform; 853 | if (true) { 854 | platform = Promise.resolve().then(() => (init_browser(), browser_exports)); 855 | } else { 856 | platform = null; 857 | } 858 | RegExp.prototype.toJSON = function() { 859 | return { type: "RegExp", source: this.source, flags: this.flags }; 860 | }; 861 | return class { 862 | #name; 863 | #db; 864 | #ready; 865 | #indexes; 866 | constructor(name, { root = ".db", meta = ".meta", database, indexes } = {}) { 867 | if (name.startsWith(".")) { 868 | throw new TypeError("The table name cannot starts with '.'."); 869 | } 870 | this.#name = name; 871 | this.#db = database; 872 | this.#indexes = { 873 | _id: true, 874 | // indent 875 | createdAt: false, 876 | updatedAt: false, 877 | ...indexes 878 | }; 879 | this.#ready = platform.then(({ createTable: createTable2 }) => { 880 | return createTable2(this, root, meta); 881 | }).then((res) => { 882 | this._storage = res; 883 | }); 884 | } 885 | get indexes() { 886 | return this.#indexes; 887 | } 888 | get database() { 889 | return this.#db; 890 | } 891 | get name() { 892 | return this.#name; 893 | } 894 | async getRecords({ filter, sorter, skip, limit, filterIndexes, rawSorter } = {}) { 895 | await this.#ready; 896 | const { getRecords: getRecords2 } = await platform; 897 | return getRecords2(this, { filter, sorter, skip, limit, filterIndexes, rawSorter }); 898 | } 899 | async save(records = [], countResult = false) { 900 | await this.#ready; 901 | const originalRecords = records; 902 | if (!Array.isArray(records)) { 903 | records = [records]; 904 | } 905 | const { flushData: flushData2 } = await platform; 906 | await flushData2(this); 907 | const insertRecords = []; 908 | const datetime = /* @__PURE__ */ new Date(); 909 | for (let i = 0; i < records.length; i++) { 910 | const record = records[i]; 911 | record.createdAt = record.createdAt || datetime; 912 | record.updatedAt = datetime; 913 | if (record._id != null) { 914 | const idx = this._storage.getItemIndex(record._id); 915 | if (idx >= 0) { 916 | await this._storage.put(idx, record); 917 | } 918 | } else { 919 | record._id = record._id || v4_default(); 920 | insertRecords.push(record); 921 | } 922 | } 923 | const upsertedCount = insertRecords.length; 924 | const modifiedCount = records.length - upsertedCount; 925 | await this._storage.add(insertRecords); 926 | const { fileSync: fileSync2 } = await platform; 927 | await fileSync2(this); 928 | if (countResult) 929 | return { modifiedCount, upsertedCount }; 930 | return originalRecords; 931 | } 932 | async delete(records = []) { 933 | await this.#ready; 934 | if (!Array.isArray(records)) 935 | records = [records]; 936 | const { flushData: flushData2 } = await platform; 937 | await flushData2(this); 938 | let deletedCount = 0; 939 | const filterMap = {}; 940 | for (let i = 0; i < records.length; i++) { 941 | const record = records[i]; 942 | const idx = this._storage.getItemIndex(record._id); 943 | if (idx >= 0) 944 | deletedCount++; 945 | filterMap[idx] = true; 946 | } 947 | await this._storage.delete(filterMap); 948 | const { fileSync: fileSync2 } = await platform; 949 | await fileSync2(this); 950 | return { deletedCount }; 951 | } 952 | where(condition = null) { 953 | const query = new query_default(condition, this); 954 | return query; 955 | } 956 | }; 957 | })(); 958 | var table_default = Table; 959 | 960 | // lib/operator.js 961 | var _filter4 = Symbol.for("okeydb-filter"); 962 | var Operator = class _Operator { 963 | constructor(filter, prev) { 964 | if (filter && prev) { 965 | this[_filter4] = this.and(prev, filter)[_filter4]; 966 | } else if (filter) { 967 | this[_filter4] = filter; 968 | } 969 | } 970 | gt(value) { 971 | const fn = (d) => d > value; 972 | const ret = new _Operator(fn, this[_filter4]); 973 | if (!this[_filter4]) { 974 | ret._type = "gt"; 975 | ret._value = value; 976 | } else if (this._type === "lt" || this._type === "lte") { 977 | ret._type = `gt${this._type}`; 978 | ret._value = [value, this._value]; 979 | } 980 | return ret; 981 | } 982 | greaterThan(value) { 983 | return this.gt(value); 984 | } 985 | gte(value) { 986 | const fn = (d) => d >= value; 987 | const ret = new _Operator(fn, this[_filter4]); 988 | if (!this[_filter4]) { 989 | ret._type = "gte"; 990 | ret._value = value; 991 | } else if (this._type === "lt" || this._type === "lte") { 992 | ret._type = `gte${this._type}`; 993 | ret._value = [value, this._value]; 994 | } 995 | return ret; 996 | } 997 | greaterThanOrEqual(value) { 998 | return this.gte(value); 999 | } 1000 | lt(value) { 1001 | const fn = (d) => d < value; 1002 | const ret = new _Operator(fn, this[_filter4]); 1003 | if (!this[_filter4]) { 1004 | ret._type = "lt"; 1005 | ret._value = value; 1006 | } else if (this._type === "gt" || this._type === "gte") { 1007 | ret._type = `${this._type}lt`; 1008 | ret._value = [this._value, value]; 1009 | } 1010 | return ret; 1011 | } 1012 | lessThan(value) { 1013 | return this.lt(value); 1014 | } 1015 | lte(value) { 1016 | const fn = (d) => d <= value; 1017 | const ret = new _Operator(fn, this[_filter4]); 1018 | if (!this[_filter4]) { 1019 | ret._type = "lte"; 1020 | ret._value = value; 1021 | } else if (this._type === "gt" || this._type === "gte") { 1022 | ret._type = `${this._type}lte`; 1023 | ret._value = [this._value, value]; 1024 | } 1025 | return ret; 1026 | } 1027 | lessThanOrEqual(value) { 1028 | return this.lte(value); 1029 | } 1030 | eq(value) { 1031 | return new _Operator((d) => d == value, this[_filter4]); 1032 | } 1033 | equal(value) { 1034 | return new _Operator((d) => d == value, this[_filter4]); 1035 | } 1036 | ne(value) { 1037 | return new _Operator((d) => d != value, this[_filter4]); 1038 | } 1039 | notEqual(value) { 1040 | return new _Operator((d) => d != value, this[_filter4]); 1041 | } 1042 | mod(divisor, remainder) { 1043 | return new _Operator((d) => d % divisor === remainder, this[_filter4]); 1044 | } 1045 | in(list) { 1046 | return new _Operator((d) => { 1047 | if (Array.isArray(d)) { 1048 | return d.some((item) => list.includes(item)); 1049 | } 1050 | return list.includes(d); 1051 | }, this[_filter4]); 1052 | } 1053 | nin(list) { 1054 | return new _Operator((d) => { 1055 | if (Array.isArray(d)) { 1056 | return !d.some((item) => list.includes(item)); 1057 | } 1058 | return !list.includes(d); 1059 | }, this[_filter4]); 1060 | } 1061 | all(list) { 1062 | return new _Operator((d) => { 1063 | if (Array.isArray(d)) { 1064 | return d.every((item) => list.includes(item)); 1065 | } 1066 | }, this[_filter4]); 1067 | } 1068 | size(len) { 1069 | return new _Operator((d) => { 1070 | if (Array.isArray(d)) { 1071 | return d.length === len; 1072 | } 1073 | }, this[_filter4]); 1074 | } 1075 | bitsAllClear(positions) { 1076 | return new _Operator((d) => { 1077 | if (typeof d === "number") { 1078 | let mask = 0; 1079 | positions.forEach((p) => mask |= 1 << p); 1080 | return (d & mask) === 0; 1081 | } 1082 | }, this[_filter4]); 1083 | } 1084 | bitsAnyClear(positions) { 1085 | return new _Operator((d) => { 1086 | if (typeof d === "number") { 1087 | let mask = 0; 1088 | positions.forEach((p) => mask |= 1 << p); 1089 | return (d & mask) < mask; 1090 | } 1091 | }, this[_filter4]); 1092 | } 1093 | bitsAllSet(positions) { 1094 | return new _Operator((d) => { 1095 | if (typeof d === "number") { 1096 | let mask = 0; 1097 | positions.forEach((p) => mask |= 1 << p); 1098 | return (d & mask) === mask; 1099 | } 1100 | }, this[_filter4]); 1101 | } 1102 | bitsAnySet(positions) { 1103 | return new _Operator((d) => { 1104 | if (typeof d === "number") { 1105 | let mask = 0; 1106 | positions.forEach((p) => mask |= 1 << p); 1107 | return (d & mask) > 0; 1108 | } 1109 | }, this[_filter4]); 1110 | } 1111 | elemMatch(conditions) { 1112 | if (conditions instanceof _Operator) { 1113 | conditions = conditions[_filter4]; 1114 | } 1115 | const filter = typeof conditions === "function" ? conditions : mergeConditions(conditions); 1116 | return new _Operator((d) => { 1117 | if (Array.isArray(d)) { 1118 | return d.some((item) => filter(item)); 1119 | } 1120 | }, this[_filter4]); 1121 | } 1122 | exists(flag) { 1123 | return new _Operator((d, k, o) => k in o == flag, this[_filter4]); 1124 | } 1125 | type(t) { 1126 | return new _Operator((d, k, o) => k in o && getType(d) === t, this[_filter4]); 1127 | } 1128 | not(condition) { 1129 | if (condition instanceof _Operator) { 1130 | condition = condition[_filter4]; 1131 | } else if (typeof condition !== "function") { 1132 | condition = (d) => d === condition; 1133 | } 1134 | return new _Operator((d, k, o) => !condition(d, k, o), this[_filter4]); 1135 | } 1136 | and(...conditions) { 1137 | return new _Operator(mergeConditions(conditions, "and"), this[_filter4]); 1138 | } 1139 | or(...conditions) { 1140 | return new _Operator(mergeConditions(conditions, "or"), this[_filter4]); 1141 | } 1142 | nor(...conditions) { 1143 | return new _Operator(mergeConditions(conditions, "nor"), this[_filter4]); 1144 | } 1145 | inc(value) { 1146 | const filter = this[_filter4]; 1147 | return new _Operator((d) => { 1148 | if (filter) 1149 | d = filter(d); 1150 | if (typeof d !== "number") { 1151 | throw new Error("Cannot apply $inc to a value of non-numeric type."); 1152 | } 1153 | return d + value; 1154 | }); 1155 | } 1156 | mul(value) { 1157 | const filter = this[_filter4]; 1158 | return new _Operator((d) => { 1159 | if (filter) 1160 | d = filter(d); 1161 | if (typeof d !== "number") { 1162 | throw new Error("Cannot apply $inc to a value of non-numeric type."); 1163 | } 1164 | return d * value; 1165 | }); 1166 | } 1167 | min(value) { 1168 | const filter = this[_filter4]; 1169 | return new _Operator((d) => { 1170 | if (filter) 1171 | d = filter(d); 1172 | if (typeof d !== "number") { 1173 | throw new Error("Cannot apply $inc to a value of non-numeric type."); 1174 | } 1175 | return Math.min(d, value); 1176 | }); 1177 | } 1178 | max(value) { 1179 | const filter = this[_filter4]; 1180 | return new _Operator((d) => { 1181 | if (filter) 1182 | d = filter(d); 1183 | if (typeof d !== "number") { 1184 | throw new Error("Cannot apply $inc to a value of non-numeric type."); 1185 | } 1186 | return Math.max(d, value); 1187 | }); 1188 | } 1189 | rename(newKey) { 1190 | return new _Operator((d, k, o) => { 1191 | if (newKey !== k) { 1192 | o[newKey] = o[k]; 1193 | } 1194 | return; 1195 | }); 1196 | } 1197 | unset() { 1198 | return new _Operator(() => { 1199 | return; 1200 | }); 1201 | } 1202 | currentDate() { 1203 | return new _Operator(() => /* @__PURE__ */ new Date()); 1204 | } 1205 | regex(value) { 1206 | return new _Operator((d) => { 1207 | const exp = new RegExp(value); 1208 | return d.match(exp) != null; 1209 | }); 1210 | } 1211 | }; 1212 | 1213 | // lib/db.js 1214 | var db_default = class extends Operator { 1215 | #root; 1216 | #meta; 1217 | #name; 1218 | #version; 1219 | #tables = {}; 1220 | constructor({ root = ".db", meta = ".meta", name = "okeydb", version: version2 = 1 } = {}) { 1221 | super(); 1222 | this.#root = root; 1223 | this.#meta = meta; 1224 | this.#name = name; 1225 | this.#version = version2; 1226 | } 1227 | get name() { 1228 | return this.#name; 1229 | } 1230 | get version() { 1231 | return this.#version; 1232 | } 1233 | close() { 1234 | if (this.instance) 1235 | this.instance.close(); 1236 | } 1237 | table(name, { indexes } = {}) { 1238 | if (!this.#tables[name]) 1239 | this.#tables[name] = new table_default(name, { root: this.#root, meta: this.#meta, database: this, indexes }); 1240 | return this.#tables[name]; 1241 | } 1242 | }; 1243 | 1244 | // index.js 1245 | var airdb_lite_default = db_default; 1246 | export { 1247 | db_default as OkeyDB, 1248 | airdb_lite_default as default 1249 | }; 1250 | -------------------------------------------------------------------------------- /dist/okeydb.cjs: -------------------------------------------------------------------------------- 1 | var __create = Object.create; 2 | var __defProp = Object.defineProperty; 3 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 4 | var __getOwnPropNames = Object.getOwnPropertyNames; 5 | var __getProtoOf = Object.getPrototypeOf; 6 | var __hasOwnProp = Object.prototype.hasOwnProperty; 7 | var __esm = (fn, res) => function __init() { 8 | return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; 9 | }; 10 | var __export = (target, all) => { 11 | for (var name in all) 12 | __defProp(target, name, { get: all[name], enumerable: true }); 13 | }; 14 | var __copyProps = (to, from, except, desc) => { 15 | if (from && typeof from === "object" || typeof from === "function") { 16 | for (let key of __getOwnPropNames(from)) 17 | if (!__hasOwnProp.call(to, key) && key !== except) 18 | __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); 19 | } 20 | return to; 21 | }; 22 | var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( 23 | // If the importer is in node compatibility mode or this is not an ESM 24 | // file that has been converted to a CommonJS file using a Babel- 25 | // compatible transform (i.e. "__esModule" has not been set), then set 26 | // "default" to the CommonJS "module.exports" for node compatibility. 27 | isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, 28 | mod 29 | )); 30 | var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); 31 | 32 | // lib/utils.js 33 | function parseCondition(condition = {}) { 34 | if (typeof condition === "function") 35 | return condition; 36 | if (condition[_filter]) 37 | return condition[_filter]; 38 | const filters = []; 39 | for (const [k, v] of Object.entries(condition)) { 40 | if (typeof v === "function") { 41 | filters.push((d) => v(d[k], k, d)); 42 | } else if (v && typeof v[_filter] === "function") { 43 | const f = v[_filter]; 44 | filters.push((d) => f(d[k], k, d)); 45 | } else if (v instanceof RegExp) { 46 | filters.push((d) => d[k] && typeof d[k].match === "function" && d[k].match(v) != null); 47 | } else { 48 | filters.push((d) => d[k] === v); 49 | } 50 | } 51 | return (record) => filters.every((f) => f(record)); 52 | } 53 | function mergeConditions(conditions, type = "and") { 54 | const filters = []; 55 | for (let i = 0; i < conditions.length; i++) { 56 | filters.push(parseCondition(conditions[i])); 57 | } 58 | if (type === "and") { 59 | return (record) => filters.every((f) => f(record)); 60 | } else if (type === "or") { 61 | return (record) => filters.some((f) => f(record)); 62 | } else if (type === "nor") { 63 | return (record) => !filters.some((f) => f(record)); 64 | } 65 | } 66 | function getType(value) { 67 | let type = typeof value; 68 | if (type === "object" && Array.isArray(value)) { 69 | type = "array"; 70 | } else if (type === "object" && value instanceof Date) { 71 | type = "date"; 72 | } else if (type === "object" && value instanceof RegExp) { 73 | type = "regexp"; 74 | } else if (value == null) { 75 | type = "null"; 76 | } 77 | return type; 78 | } 79 | function sleep(ms) { 80 | return new Promise((resolve) => setTimeout(resolve, ms)); 81 | } 82 | var _filter; 83 | var init_utils = __esm({ 84 | "lib/utils.js"() { 85 | _filter = Symbol.for("okeydb-filter"); 86 | } 87 | }); 88 | 89 | // node_modules/uuid/dist/esm-node/rng.js 90 | function rng() { 91 | if (poolPtr > rnds8Pool.length - 16) { 92 | import_crypto.default.randomFillSync(rnds8Pool); 93 | poolPtr = 0; 94 | } 95 | return rnds8Pool.slice(poolPtr, poolPtr += 16); 96 | } 97 | var import_crypto, rnds8Pool, poolPtr; 98 | var init_rng = __esm({ 99 | "node_modules/uuid/dist/esm-node/rng.js"() { 100 | import_crypto = __toESM(require("crypto")); 101 | rnds8Pool = new Uint8Array(256); 102 | poolPtr = rnds8Pool.length; 103 | } 104 | }); 105 | 106 | // node_modules/uuid/dist/esm-node/stringify.js 107 | function unsafeStringify(arr, offset = 0) { 108 | return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); 109 | } 110 | var byteToHex; 111 | var init_stringify = __esm({ 112 | "node_modules/uuid/dist/esm-node/stringify.js"() { 113 | byteToHex = []; 114 | for (let i = 0; i < 256; ++i) { 115 | byteToHex.push((i + 256).toString(16).slice(1)); 116 | } 117 | } 118 | }); 119 | 120 | // node_modules/uuid/dist/esm-node/native.js 121 | var import_crypto2, native_default; 122 | var init_native = __esm({ 123 | "node_modules/uuid/dist/esm-node/native.js"() { 124 | import_crypto2 = __toESM(require("crypto")); 125 | native_default = { 126 | randomUUID: import_crypto2.default.randomUUID 127 | }; 128 | } 129 | }); 130 | 131 | // node_modules/uuid/dist/esm-node/v4.js 132 | function v4(options, buf, offset) { 133 | if (native_default.randomUUID && !buf && !options) { 134 | return native_default.randomUUID(); 135 | } 136 | options = options || {}; 137 | const rnds = options.random || (options.rng || rng)(); 138 | rnds[6] = rnds[6] & 15 | 64; 139 | rnds[8] = rnds[8] & 63 | 128; 140 | if (buf) { 141 | offset = offset || 0; 142 | for (let i = 0; i < 16; ++i) { 143 | buf[offset + i] = rnds[i]; 144 | } 145 | return buf; 146 | } 147 | return unsafeStringify(rnds); 148 | } 149 | var v4_default; 150 | var init_v4 = __esm({ 151 | "node_modules/uuid/dist/esm-node/v4.js"() { 152 | init_native(); 153 | init_rng(); 154 | init_stringify(); 155 | v4_default = v4; 156 | } 157 | }); 158 | 159 | // node_modules/uuid/dist/esm-node/index.js 160 | var init_esm_node = __esm({ 161 | "node_modules/uuid/dist/esm-node/index.js"() { 162 | init_v4(); 163 | } 164 | }); 165 | 166 | // lib/platform/node/storage.js 167 | var Storage; 168 | var init_storage = __esm({ 169 | "lib/platform/node/storage.js"() { 170 | init_utils(); 171 | Storage = class { 172 | #storage; 173 | constructor(storage) { 174 | this.#storage = storage; 175 | } 176 | toJSON() { 177 | const _schema = this.#storage.records.map((d) => { 178 | const s = {}; 179 | for (const [k, v] of Object.entries(d)) { 180 | s[k] = getType(v); 181 | } 182 | return s; 183 | }); 184 | return { 185 | _ids: this.#storage._ids, 186 | records: this.#storage.records, 187 | _schema 188 | }; 189 | } 190 | get records() { 191 | return this.#storage.records; 192 | } 193 | add(records) { 194 | const start = this.#storage.records.length; 195 | for (let i = 0; i < records.length; i++) { 196 | const id = records[i]._id; 197 | this.#storage._ids[id] = start + i; 198 | } 199 | this.#storage.records = [...this.#storage.records, ...records]; 200 | } 201 | getItemIndex(id) { 202 | return this.#storage._ids[id]; 203 | } 204 | put(idx, record) { 205 | this.#storage.records[idx] = record; 206 | } 207 | delete(deleteMap) { 208 | this.#storage.records = this.#storage.records.filter((_, idx) => !deleteMap[idx]); 209 | this.#storage._ids = {}; 210 | for (let i = 0; i < this.#storage.records.length; i++) { 211 | const record = this.#storage.records[i]; 212 | this.#storage._ids[record._id] = i; 213 | } 214 | } 215 | }; 216 | } 217 | }); 218 | 219 | // lib/platform/node/index.js 220 | var node_exports = {}; 221 | __export(node_exports, { 222 | createTable: () => createTable, 223 | fileSync: () => fileSync, 224 | flushData: () => flushData, 225 | getRecords: () => getRecords 226 | }); 227 | async function getRecordsFromFile(filepath2) { 228 | await _fileLock(filepath2); 229 | let records = await (0, import_promises.readFile)(filepath2, { charset: "utf8" }); 230 | await _fileUnlock(filepath2); 231 | records = JSON.parse(records); 232 | records.records = records.records.map((r, i) => { 233 | const schema = records._schema[i]; 234 | for (const [k, v] of Object.entries(schema)) { 235 | if (v === "date") { 236 | r[k] = new Date(r[k]); 237 | } else if (v === "regexp") { 238 | r[k] = new RegExp(r[k].source, r[k].flags); 239 | } 240 | } 241 | return r; 242 | }); 243 | delete records._schema; 244 | return new Storage(records); 245 | } 246 | async function _fileLock(filepath2, unlock = false) { 247 | const locker = `${filepath2}.lck`; 248 | while ((0, import_node_fs.existsSync)(locker)) { 249 | await sleep(10); 250 | } 251 | if (!unlock) 252 | await (0, import_promises.writeFile)(locker, ""); 253 | } 254 | async function _fileUnlock(filepath2) { 255 | const locker = `${filepath2}.lck`; 256 | await (0, import_promises.unlink)(locker); 257 | } 258 | async function _updateMeta(metafile, version) { 259 | await _fileLock(metafile); 260 | const metadata = JSON.parse(await (0, import_promises.readFile)(metafile, { charset: "utf8" })); 261 | metadata.version = version; 262 | await (0, import_promises.writeFile)(metafile, JSON.stringify(metadata), { charset: "utf8" }); 263 | await _fileUnlock(metafile); 264 | } 265 | function metapath(table) { 266 | return import_node_path.default.join(table[_meta], `${table.name}.meta`); 267 | } 268 | function filepath(table) { 269 | return import_node_path.default.join(table[_root], table.name); 270 | } 271 | async function createTable(table, root, meta) { 272 | table[_root] = root; 273 | table[_meta] = meta; 274 | if (!(0, import_node_fs.existsSync)(table[_root])) { 275 | await (0, import_promises.mkdir)(table[_root]); 276 | } 277 | if (!(0, import_node_fs.existsSync)(table[_meta])) { 278 | await (0, import_promises.mkdir)(table[_meta]); 279 | } 280 | if (!(0, import_node_fs.existsSync)(metapath(table))) { 281 | table._version = v4_default(); 282 | await (0, import_promises.writeFile)(metapath(table), JSON.stringify({ version: table._version }), { charset: "utf8" }); 283 | } else { 284 | const { version } = JSON.parse(await (0, import_promises.readFile)(metapath(table), { charset: "utf8" })); 285 | table._version = version; 286 | } 287 | if (!(0, import_node_fs.existsSync)(filepath(table))) { 288 | const records = { 289 | _ids: {}, 290 | records: [] 291 | }; 292 | await (0, import_promises.writeFile)(filepath(table), JSON.stringify(records), { charset: "utf8" }); 293 | return new Storage(records); 294 | } 295 | return null; 296 | } 297 | async function fileSync(table) { 298 | await _fileLock(filepath(table)); 299 | await (0, import_promises.writeFile)(filepath(table), JSON.stringify(table._storage), { charset: "utf8" }); 300 | const version = v4_default(); 301 | await _updateMeta(metapath(table), version); 302 | table._version = version; 303 | await _fileUnlock(filepath(table)); 304 | } 305 | async function flushData(table) { 306 | await _fileLock(metapath(table)); 307 | const { version } = JSON.parse(await (0, import_promises.readFile)(metapath(table), { charset: "utf8" })); 308 | if (!table._storage || table._version !== version) { 309 | table._storage = await getRecordsFromFile(filepath(table)); 310 | } 311 | table._version = version; 312 | await _fileUnlock(metapath(table)); 313 | } 314 | async function getRecords(table, { filter, sorter, skip, limit } = {}) { 315 | await flushData(table); 316 | const records = table._storage.records; 317 | let filtedRecords; 318 | if (!sorter && skip === 0 && limit === 1) { 319 | filtedRecords = records.find(filter); 320 | if (filtedRecords) 321 | return [filtedRecords]; 322 | return []; 323 | } else { 324 | filtedRecords = records.filter(filter); 325 | } 326 | if (sorter) 327 | filtedRecords.sort(sorter); 328 | if (skip > 0 || Number.isFinite(limit)) { 329 | filtedRecords = filtedRecords.slice(skip, skip + limit); 330 | } 331 | return filtedRecords; 332 | } 333 | var import_node_path, import_node_fs, import_promises, _root, _meta; 334 | var init_node = __esm({ 335 | "lib/platform/node/index.js"() { 336 | init_utils(); 337 | init_storage(); 338 | import_node_path = __toESM(require("node:path"), 1); 339 | import_node_fs = require("node:fs"); 340 | import_promises = require("node:fs/promises"); 341 | init_esm_node(); 342 | _root = Symbol("root"); 343 | _meta = Symbol("meta"); 344 | } 345 | }); 346 | 347 | // index.js 348 | var airdb_lite_exports = {}; 349 | __export(airdb_lite_exports, { 350 | OkeyDB: () => db_default, 351 | default: () => airdb_lite_default 352 | }); 353 | module.exports = __toCommonJS(airdb_lite_exports); 354 | 355 | // lib/query.js 356 | init_utils(); 357 | var _notIndexFilter = Symbol.for("not-index-filter"); 358 | function updateFilterIndex(query, conditions, filterIndexes = {}, phase = "and") { 359 | const indexes = query.table.indexes; 360 | let notIndexFilter = false; 361 | for (let i = 0; i < conditions.length; i++) { 362 | const condition = conditions[i]; 363 | let hasIndex = false; 364 | for (const [k, v] of Object.entries(condition)) { 365 | if (k in indexes) { 366 | hasIndex = true; 367 | filterIndexes[k] = filterIndexes[k] || /* @__PURE__ */ new Set(); 368 | filterIndexes[k].add(v); 369 | if (phase === "and" && filterIndexes[k].size > 1) 370 | filterIndexes[k].clear(); 371 | } else { 372 | notIndexFilter = true; 373 | } 374 | } 375 | if (!hasIndex && phase === "or") { 376 | query.table[_notIndexFilter] = notIndexFilter; 377 | return null; 378 | } 379 | } 380 | query.table[_notIndexFilter] = notIndexFilter; 381 | return filterIndexes; 382 | } 383 | var _filter2 = Symbol.for("okeydb-filter"); 384 | var query_default = class { 385 | #table; 386 | #filter; 387 | #records; 388 | #sorter = null; 389 | #rawSorter; 390 | #skip = 0; 391 | #limit = Infinity; 392 | #projection = null; 393 | #updateFields = null; 394 | #insertFields = {}; 395 | #setOnInsertFields = null; 396 | #upsert = false; 397 | #filterIndexes = {}; 398 | constructor(condition, table) { 399 | this.#table = table; 400 | if (condition) { 401 | this.#filter = mergeConditions([condition]); 402 | this.#insertFields = { ...condition }; 403 | this.#filterIndexes = updateFilterIndex(this, [condition], {}, "and"); 404 | } 405 | } 406 | and(...conditions) { 407 | const left = this.#filter; 408 | const right = mergeConditions(conditions); 409 | if (left) { 410 | this.#filter = (record) => left(record) && right(record); 411 | } else { 412 | this.#filter = right; 413 | } 414 | for (let i = 0; i < conditions.length; i++) { 415 | Object.assign(this.#insertFields, conditions[i]); 416 | } 417 | if (this.#filterIndexes) 418 | this.#filterIndexes = updateFilterIndex(this, conditions, this.#filterIndexes, "and"); 419 | return this; 420 | } 421 | or(...conditions) { 422 | const left = this.#filter; 423 | const right = mergeConditions(conditions, "or"); 424 | if (left) { 425 | this.#filter = (record) => left(record) || right(record); 426 | } else { 427 | this.#filter = right; 428 | } 429 | this.#insertFields = {}; 430 | if (this.#filterIndexes) 431 | this.#filterIndexes = updateFilterIndex(this, conditions, this.#filterIndexes, "or"); 432 | return this; 433 | } 434 | nor(...conditions) { 435 | const left = this.#filter; 436 | const right = mergeConditions(conditions, "or"); 437 | if (left) { 438 | this.#filter = (record) => !(left(record) || right(record)); 439 | } else { 440 | this.#filter = (record) => !right(record); 441 | } 442 | this.#insertFields = {}; 443 | this.#filterIndexes = null; 444 | this.table[_notIndexFilter] = true; 445 | return this; 446 | } 447 | async find() { 448 | let filtedRecords = await this.#table.getRecords({ 449 | filter: this.#filter || function() { 450 | return true; 451 | }, 452 | sorter: this.#sorter, 453 | rawSorter: this.#rawSorter, 454 | skip: this.#skip, 455 | limit: this.#limit, 456 | filterIndexes: this.filterIndexes 457 | }); 458 | if (this.#projection) { 459 | const { type, fields } = this.#projection; 460 | if (type === "inclusion") { 461 | filtedRecords = filtedRecords.map((r) => { 462 | const ret = {}; 463 | fields.forEach((f) => ret[f] = r[f]); 464 | return ret; 465 | }); 466 | } else if (type === "exclusion") { 467 | filtedRecords = filtedRecords.map((r) => { 468 | const ret = { ...r }; 469 | fields.forEach((f) => delete ret[f]); 470 | return ret; 471 | }); 472 | } 473 | } 474 | this.#records = filtedRecords; 475 | return filtedRecords; 476 | } 477 | async findOne() { 478 | const records = await this.#table.getRecords({ 479 | filter: this.#filter || function() { 480 | return true; 481 | }, 482 | sorter: this.#sorter, 483 | rawSorter: this.#rawSorter, 484 | skip: this.#skip, 485 | limit: 1, 486 | filterIndexes: this.filterIndexes 487 | }); 488 | const record = records[0]; 489 | if (this.#projection) { 490 | const { type, fields } = this.#projection; 491 | const ret = {}; 492 | if (type === "inclusion") { 493 | fields.forEach((f) => ret[f] = record[f]); 494 | } else if (type === "exclusion") { 495 | Object.assign(ret, record); 496 | fields.forEach((f) => delete ret[f]); 497 | } 498 | return ret; 499 | } 500 | return record; 501 | } 502 | async count() { 503 | if (this.#records) 504 | return this.#records.length; 505 | return await this.find().length; 506 | } 507 | set(fields) { 508 | this.#updateFields = fields; 509 | return this; 510 | } 511 | setOnInsert(fields) { 512 | this.#setOnInsertFields = fields; 513 | return this; 514 | } 515 | upsert(flag) { 516 | this.#upsert = flag; 517 | return this; 518 | } 519 | async save() { 520 | if (this.#updateFields || this.#upsert) { 521 | let records = this.#records; 522 | if (!records) 523 | records = await this.find(); 524 | if (records.length <= 0 && this.#upsert) { 525 | records = Object.assign({}, this.#insertFields, this.#setOnInsertFields); 526 | for (let [k, v] of Object.entries(records)) { 527 | if (v && typeof v[_filter2] === "function") { 528 | v = v[_filter2]; 529 | } 530 | if (typeof v === "function") { 531 | records[k] = v(records[k], k, records); 532 | } 533 | } 534 | if (this.#updateFields) { 535 | const updateFields = this.#updateFields; 536 | for (let [k, v] of Object.entries(updateFields)) { 537 | if (v && typeof v[_filter2] === "function") { 538 | v = v[_filter2]; 539 | } 540 | if (typeof v !== "function") { 541 | records[k] = v; 542 | } else { 543 | records[k] = v(records[k], k, records); 544 | if (records[k] === void 0) 545 | delete records[k]; 546 | } 547 | } 548 | } 549 | } else if (this.#updateFields) { 550 | const updateFields = this.#updateFields; 551 | records = records.map((record) => { 552 | const ret = { ...record }; 553 | for (let [k, v] of Object.entries(updateFields)) { 554 | if (v && typeof v[_filter2] === "function") { 555 | v = v[_filter2]; 556 | } 557 | if (typeof v !== "function") { 558 | ret[k] = v; 559 | } else { 560 | ret[k] = v(ret[k], k, ret); 561 | if (ret[k] === void 0) 562 | delete ret[k]; 563 | } 564 | } 565 | return ret; 566 | }); 567 | } else { 568 | return await this.#table.save([], true); 569 | } 570 | return await this.#table.save(records, true); 571 | } 572 | throw new Error("Must use set or upsert at least once"); 573 | } 574 | async delete() { 575 | let records = this.#records; 576 | if (!records) 577 | records = await this.find(); 578 | return await this.#table.delete(records); 579 | } 580 | sort(conditions) { 581 | const conds = Object.entries(conditions); 582 | this.#rawSorter = conditions; 583 | this.#sorter = (a, b) => { 584 | for (let [k, v] of conds) { 585 | if (typeof v === "string") { 586 | if (v.toLowerCase() === "asc") { 587 | v = 1; 588 | } else if (v.toLowerCase() === "desc") { 589 | v = -1; 590 | } 591 | } 592 | if (v !== 1 && v !== -1) 593 | throw new Error(`Invalid sort condition: ${k} ${v}`); 594 | if (a[k] != b[k]) { 595 | return a[k] > b[k] ? v * 1 : v * -1; 596 | } 597 | } 598 | return 0; 599 | }; 600 | return this; 601 | } 602 | skip(n) { 603 | this.#skip = n; 604 | return this; 605 | } 606 | limit(n) { 607 | this.#limit = n; 608 | return this; 609 | } 610 | projection(conditions) { 611 | let type = null; 612 | const fields = []; 613 | let ignoreId = false; 614 | for (const [k, v] of Object.entries(conditions)) { 615 | if (k === "_id") { 616 | ignoreId = !v; 617 | continue; 618 | } 619 | if (!type && v) 620 | type = "inclusion"; 621 | else if (!type && !v) 622 | type = "exclusion"; 623 | else if (type === "inclusion" && !v || type === "exclusion" && v) 624 | throw new Error("Projection cannot have a mix of inclusion and exclusion."); 625 | fields.push(k); 626 | } 627 | if (type === "exclusion" && ignoreId || type === "inclusion" && !ignoreId) { 628 | fields.push("_id"); 629 | } 630 | if (type === "exclusion" && !ignoreId) { 631 | throw new Error("Projection cannot have a mix of inclusion and exclusion."); 632 | } 633 | this.#projection = { type: type || "inclusion", fields }; 634 | return this; 635 | } 636 | get table() { 637 | return this.#table; 638 | } 639 | get filterIndexes() { 640 | const filterIndexes = this.#filterIndexes || {}; 641 | if (Object.keys(filterIndexes).length) 642 | return filterIndexes; 643 | return null; 644 | } 645 | }; 646 | 647 | // lib/table.js 648 | init_esm_node(); 649 | var Table = (() => { 650 | let platform; 651 | if (false) { 652 | platform = null; 653 | } else { 654 | platform = Promise.resolve().then(() => (init_node(), node_exports)); 655 | } 656 | RegExp.prototype.toJSON = function() { 657 | return { type: "RegExp", source: this.source, flags: this.flags }; 658 | }; 659 | return class { 660 | #name; 661 | #db; 662 | #ready; 663 | #indexes; 664 | constructor(name, { root = ".db", meta = ".meta", database, indexes } = {}) { 665 | if (name.startsWith(".")) { 666 | throw new TypeError("The table name cannot starts with '.'."); 667 | } 668 | this.#name = name; 669 | this.#db = database; 670 | this.#indexes = { 671 | _id: true, 672 | // indent 673 | createdAt: false, 674 | updatedAt: false, 675 | ...indexes 676 | }; 677 | this.#ready = platform.then(({ createTable: createTable2 }) => { 678 | return createTable2(this, root, meta); 679 | }).then((res) => { 680 | this._storage = res; 681 | }); 682 | } 683 | get indexes() { 684 | return this.#indexes; 685 | } 686 | get database() { 687 | return this.#db; 688 | } 689 | get name() { 690 | return this.#name; 691 | } 692 | async getRecords({ filter, sorter, skip, limit, filterIndexes, rawSorter } = {}) { 693 | await this.#ready; 694 | const { getRecords: getRecords2 } = await platform; 695 | return getRecords2(this, { filter, sorter, skip, limit, filterIndexes, rawSorter }); 696 | } 697 | async save(records = [], countResult = false) { 698 | await this.#ready; 699 | const originalRecords = records; 700 | if (!Array.isArray(records)) { 701 | records = [records]; 702 | } 703 | const { flushData: flushData2 } = await platform; 704 | await flushData2(this); 705 | const insertRecords = []; 706 | const datetime = /* @__PURE__ */ new Date(); 707 | for (let i = 0; i < records.length; i++) { 708 | const record = records[i]; 709 | record.createdAt = record.createdAt || datetime; 710 | record.updatedAt = datetime; 711 | if (record._id != null) { 712 | const idx = this._storage.getItemIndex(record._id); 713 | if (idx >= 0) { 714 | await this._storage.put(idx, record); 715 | } 716 | } else { 717 | record._id = record._id || v4_default(); 718 | insertRecords.push(record); 719 | } 720 | } 721 | const upsertedCount = insertRecords.length; 722 | const modifiedCount = records.length - upsertedCount; 723 | await this._storage.add(insertRecords); 724 | const { fileSync: fileSync2 } = await platform; 725 | await fileSync2(this); 726 | if (countResult) 727 | return { modifiedCount, upsertedCount }; 728 | return originalRecords; 729 | } 730 | async delete(records = []) { 731 | await this.#ready; 732 | if (!Array.isArray(records)) 733 | records = [records]; 734 | const { flushData: flushData2 } = await platform; 735 | await flushData2(this); 736 | let deletedCount = 0; 737 | const filterMap = {}; 738 | for (let i = 0; i < records.length; i++) { 739 | const record = records[i]; 740 | const idx = this._storage.getItemIndex(record._id); 741 | if (idx >= 0) 742 | deletedCount++; 743 | filterMap[idx] = true; 744 | } 745 | await this._storage.delete(filterMap); 746 | const { fileSync: fileSync2 } = await platform; 747 | await fileSync2(this); 748 | return { deletedCount }; 749 | } 750 | where(condition = null) { 751 | const query = new query_default(condition, this); 752 | return query; 753 | } 754 | }; 755 | })(); 756 | var table_default = Table; 757 | 758 | // lib/operator.js 759 | init_utils(); 760 | var _filter3 = Symbol.for("okeydb-filter"); 761 | var Operator = class _Operator { 762 | constructor(filter, prev) { 763 | if (filter && prev) { 764 | this[_filter3] = this.and(prev, filter)[_filter3]; 765 | } else if (filter) { 766 | this[_filter3] = filter; 767 | } 768 | } 769 | gt(value) { 770 | const fn = (d) => d > value; 771 | const ret = new _Operator(fn, this[_filter3]); 772 | if (!this[_filter3]) { 773 | ret._type = "gt"; 774 | ret._value = value; 775 | } else if (this._type === "lt" || this._type === "lte") { 776 | ret._type = `gt${this._type}`; 777 | ret._value = [value, this._value]; 778 | } 779 | return ret; 780 | } 781 | greaterThan(value) { 782 | return this.gt(value); 783 | } 784 | gte(value) { 785 | const fn = (d) => d >= value; 786 | const ret = new _Operator(fn, this[_filter3]); 787 | if (!this[_filter3]) { 788 | ret._type = "gte"; 789 | ret._value = value; 790 | } else if (this._type === "lt" || this._type === "lte") { 791 | ret._type = `gte${this._type}`; 792 | ret._value = [value, this._value]; 793 | } 794 | return ret; 795 | } 796 | greaterThanOrEqual(value) { 797 | return this.gte(value); 798 | } 799 | lt(value) { 800 | const fn = (d) => d < value; 801 | const ret = new _Operator(fn, this[_filter3]); 802 | if (!this[_filter3]) { 803 | ret._type = "lt"; 804 | ret._value = value; 805 | } else if (this._type === "gt" || this._type === "gte") { 806 | ret._type = `${this._type}lt`; 807 | ret._value = [this._value, value]; 808 | } 809 | return ret; 810 | } 811 | lessThan(value) { 812 | return this.lt(value); 813 | } 814 | lte(value) { 815 | const fn = (d) => d <= value; 816 | const ret = new _Operator(fn, this[_filter3]); 817 | if (!this[_filter3]) { 818 | ret._type = "lte"; 819 | ret._value = value; 820 | } else if (this._type === "gt" || this._type === "gte") { 821 | ret._type = `${this._type}lte`; 822 | ret._value = [this._value, value]; 823 | } 824 | return ret; 825 | } 826 | lessThanOrEqual(value) { 827 | return this.lte(value); 828 | } 829 | eq(value) { 830 | return new _Operator((d) => d == value, this[_filter3]); 831 | } 832 | equal(value) { 833 | return new _Operator((d) => d == value, this[_filter3]); 834 | } 835 | ne(value) { 836 | return new _Operator((d) => d != value, this[_filter3]); 837 | } 838 | notEqual(value) { 839 | return new _Operator((d) => d != value, this[_filter3]); 840 | } 841 | mod(divisor, remainder) { 842 | return new _Operator((d) => d % divisor === remainder, this[_filter3]); 843 | } 844 | in(list) { 845 | return new _Operator((d) => { 846 | if (Array.isArray(d)) { 847 | return d.some((item) => list.includes(item)); 848 | } 849 | return list.includes(d); 850 | }, this[_filter3]); 851 | } 852 | nin(list) { 853 | return new _Operator((d) => { 854 | if (Array.isArray(d)) { 855 | return !d.some((item) => list.includes(item)); 856 | } 857 | return !list.includes(d); 858 | }, this[_filter3]); 859 | } 860 | all(list) { 861 | return new _Operator((d) => { 862 | if (Array.isArray(d)) { 863 | return d.every((item) => list.includes(item)); 864 | } 865 | }, this[_filter3]); 866 | } 867 | size(len) { 868 | return new _Operator((d) => { 869 | if (Array.isArray(d)) { 870 | return d.length === len; 871 | } 872 | }, this[_filter3]); 873 | } 874 | bitsAllClear(positions) { 875 | return new _Operator((d) => { 876 | if (typeof d === "number") { 877 | let mask = 0; 878 | positions.forEach((p) => mask |= 1 << p); 879 | return (d & mask) === 0; 880 | } 881 | }, this[_filter3]); 882 | } 883 | bitsAnyClear(positions) { 884 | return new _Operator((d) => { 885 | if (typeof d === "number") { 886 | let mask = 0; 887 | positions.forEach((p) => mask |= 1 << p); 888 | return (d & mask) < mask; 889 | } 890 | }, this[_filter3]); 891 | } 892 | bitsAllSet(positions) { 893 | return new _Operator((d) => { 894 | if (typeof d === "number") { 895 | let mask = 0; 896 | positions.forEach((p) => mask |= 1 << p); 897 | return (d & mask) === mask; 898 | } 899 | }, this[_filter3]); 900 | } 901 | bitsAnySet(positions) { 902 | return new _Operator((d) => { 903 | if (typeof d === "number") { 904 | let mask = 0; 905 | positions.forEach((p) => mask |= 1 << p); 906 | return (d & mask) > 0; 907 | } 908 | }, this[_filter3]); 909 | } 910 | elemMatch(conditions) { 911 | if (conditions instanceof _Operator) { 912 | conditions = conditions[_filter3]; 913 | } 914 | const filter = typeof conditions === "function" ? conditions : mergeConditions(conditions); 915 | return new _Operator((d) => { 916 | if (Array.isArray(d)) { 917 | return d.some((item) => filter(item)); 918 | } 919 | }, this[_filter3]); 920 | } 921 | exists(flag) { 922 | return new _Operator((d, k, o) => k in o == flag, this[_filter3]); 923 | } 924 | type(t) { 925 | return new _Operator((d, k, o) => k in o && getType(d) === t, this[_filter3]); 926 | } 927 | not(condition) { 928 | if (condition instanceof _Operator) { 929 | condition = condition[_filter3]; 930 | } else if (typeof condition !== "function") { 931 | condition = (d) => d === condition; 932 | } 933 | return new _Operator((d, k, o) => !condition(d, k, o), this[_filter3]); 934 | } 935 | and(...conditions) { 936 | return new _Operator(mergeConditions(conditions, "and"), this[_filter3]); 937 | } 938 | or(...conditions) { 939 | return new _Operator(mergeConditions(conditions, "or"), this[_filter3]); 940 | } 941 | nor(...conditions) { 942 | return new _Operator(mergeConditions(conditions, "nor"), this[_filter3]); 943 | } 944 | inc(value) { 945 | const filter = this[_filter3]; 946 | return new _Operator((d) => { 947 | if (filter) 948 | d = filter(d); 949 | if (typeof d !== "number") { 950 | throw new Error("Cannot apply $inc to a value of non-numeric type."); 951 | } 952 | return d + value; 953 | }); 954 | } 955 | mul(value) { 956 | const filter = this[_filter3]; 957 | return new _Operator((d) => { 958 | if (filter) 959 | d = filter(d); 960 | if (typeof d !== "number") { 961 | throw new Error("Cannot apply $inc to a value of non-numeric type."); 962 | } 963 | return d * value; 964 | }); 965 | } 966 | min(value) { 967 | const filter = this[_filter3]; 968 | return new _Operator((d) => { 969 | if (filter) 970 | d = filter(d); 971 | if (typeof d !== "number") { 972 | throw new Error("Cannot apply $inc to a value of non-numeric type."); 973 | } 974 | return Math.min(d, value); 975 | }); 976 | } 977 | max(value) { 978 | const filter = this[_filter3]; 979 | return new _Operator((d) => { 980 | if (filter) 981 | d = filter(d); 982 | if (typeof d !== "number") { 983 | throw new Error("Cannot apply $inc to a value of non-numeric type."); 984 | } 985 | return Math.max(d, value); 986 | }); 987 | } 988 | rename(newKey) { 989 | return new _Operator((d, k, o) => { 990 | if (newKey !== k) { 991 | o[newKey] = o[k]; 992 | } 993 | return; 994 | }); 995 | } 996 | unset() { 997 | return new _Operator(() => { 998 | return; 999 | }); 1000 | } 1001 | currentDate() { 1002 | return new _Operator(() => /* @__PURE__ */ new Date()); 1003 | } 1004 | regex(value) { 1005 | return new _Operator((d) => { 1006 | const exp = new RegExp(value); 1007 | return d.match(exp) != null; 1008 | }); 1009 | } 1010 | }; 1011 | 1012 | // lib/db.js 1013 | var db_default = class extends Operator { 1014 | #root; 1015 | #meta; 1016 | #name; 1017 | #version; 1018 | #tables = {}; 1019 | constructor({ root = ".db", meta = ".meta", name = "okeydb", version = 1 } = {}) { 1020 | super(); 1021 | this.#root = root; 1022 | this.#meta = meta; 1023 | this.#name = name; 1024 | this.#version = version; 1025 | } 1026 | get name() { 1027 | return this.#name; 1028 | } 1029 | get version() { 1030 | return this.#version; 1031 | } 1032 | close() { 1033 | if (this.instance) 1034 | this.instance.close(); 1035 | } 1036 | table(name, { indexes } = {}) { 1037 | if (!this.#tables[name]) 1038 | this.#tables[name] = new table_default(name, { root: this.#root, meta: this.#meta, database: this, indexes }); 1039 | return this.#tables[name]; 1040 | } 1041 | }; 1042 | 1043 | // index.js 1044 | var airdb_lite_default = db_default; 1045 | // Annotate the CommonJS export names for ESM import in node: 1046 | 0 && (module.exports = { 1047 | OkeyDB 1048 | }); 1049 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import OkeyDB from "./lib/db.js"; 2 | 3 | export { OkeyDB }; 4 | export default OkeyDB; 5 | -------------------------------------------------------------------------------- /lib/db.js: -------------------------------------------------------------------------------- 1 | import Table from './table.js'; 2 | 3 | import Operator from './operator.js'; 4 | 5 | export default class extends Operator { 6 | #root; 7 | #meta; 8 | #name; 9 | #version; 10 | #tables = {}; 11 | 12 | constructor({root = '.db', meta = '.meta', name = 'okeydb', version = 1} = {}) { 13 | super(); 14 | this.#root = root; 15 | this.#meta = meta; 16 | this.#name = name; 17 | this.#version = version; 18 | } 19 | 20 | get name() { 21 | return this.#name; 22 | } 23 | 24 | get version() { 25 | return this.#version; 26 | } 27 | 28 | close() { 29 | if(this.instance) this.instance.close(); 30 | } 31 | 32 | table(name, {indexes} = {}) { 33 | if(!this.#tables[name]) 34 | this.#tables[name] = new Table(name, {root: this.#root, meta: this.#meta, database: this, indexes}); 35 | return this.#tables[name]; 36 | } 37 | } -------------------------------------------------------------------------------- /lib/operator.js: -------------------------------------------------------------------------------- 1 | import {mergeConditions, getType} from './utils.js'; 2 | 3 | const _filter = Symbol.for('okeydb-filter'); 4 | 5 | export default class Operator { 6 | constructor(filter, prev) { 7 | if(filter && prev) { 8 | this[_filter] = (this.and(prev, filter))[_filter]; 9 | } else if(filter) { 10 | this[_filter] = filter; 11 | } 12 | } 13 | 14 | gt(value) { 15 | const fn = d => d > value; 16 | const ret = new Operator(fn, this[_filter]); 17 | if(!this[_filter]) { 18 | ret._type = 'gt'; 19 | ret._value = value; 20 | } else if(this._type === 'lt' || this._type === 'lte') { 21 | ret._type = `gt${this._type}`; // ltgt & ltegt 22 | ret._value = [value, this._value]; 23 | } 24 | return ret; 25 | } 26 | 27 | greaterThan(value) { 28 | return this.gt(value); 29 | } 30 | 31 | gte(value) { 32 | const fn = d => d >= value; 33 | const ret = new Operator(fn, this[_filter]); 34 | if(!this[_filter]) { 35 | ret._type = 'gte'; 36 | ret._value = value; 37 | } else if(this._type === 'lt' || this._type === 'lte') { 38 | ret._type = `gte${this._type}`; 39 | ret._value = [value, this._value]; 40 | } 41 | return ret; 42 | } 43 | 44 | greaterThanOrEqual(value) { 45 | return this.gte(value); 46 | } 47 | 48 | lt(value) { 49 | const fn = (d) => d < value; 50 | const ret = new Operator(fn, this[_filter]); 51 | if(!this[_filter]) { 52 | ret._type = 'lt'; 53 | ret._value = value; 54 | } else if(this._type === 'gt' || this._type === 'gte') { 55 | ret._type = `${this._type}lt`; 56 | ret._value = [this._value, value]; 57 | } 58 | return ret; 59 | } 60 | 61 | lessThan(value) { 62 | return this.lt(value); 63 | } 64 | 65 | lte(value) { 66 | const fn = d => d <= value; 67 | const ret = new Operator(fn, this[_filter]); 68 | if(!this[_filter]) { 69 | ret._type = 'lte'; 70 | ret._value = value; 71 | } else if(this._type === 'gt' || this._type === 'gte') { 72 | ret._type = `${this._type}lte`; 73 | ret._value = [this._value, value]; 74 | } 75 | return ret; 76 | } 77 | 78 | lessThanOrEqual(value) { 79 | return this.lte(value); 80 | } 81 | 82 | eq(value) { 83 | return new Operator(d => d == value, this[_filter]); 84 | } 85 | 86 | equal(value) { 87 | return new Operator(d => d == value, this[_filter]); 88 | } 89 | 90 | ne(value) { 91 | return new Operator(d => d != value, this[_filter]); 92 | } 93 | 94 | notEqual(value) { 95 | return new Operator(d => d != value, this[_filter]); 96 | } 97 | 98 | mod(divisor, remainder) { 99 | return new Operator(d => d % divisor === remainder, this[_filter]); 100 | } 101 | 102 | in(list) { 103 | return new Operator(d => { 104 | if(Array.isArray(d)) { 105 | return d.some(item => list.includes(item)); 106 | } 107 | return list.includes(d); 108 | }, this[_filter]); 109 | } 110 | 111 | nin(list) { 112 | return new Operator(d => { 113 | if(Array.isArray(d)) { 114 | return !d.some(item => list.includes(item)); 115 | } 116 | return !list.includes(d); 117 | }, this[_filter]); 118 | } 119 | 120 | all(list) { 121 | return new Operator(d => { 122 | if(Array.isArray(d)) { 123 | return d.every(item => list.includes(item)); 124 | } 125 | }, this[_filter]); 126 | } 127 | 128 | size(len) { 129 | return new Operator(d => { 130 | if(Array.isArray(d)) { 131 | return d.length === len; 132 | } 133 | }, this[_filter]); 134 | } 135 | 136 | bitsAllClear(positions) { 137 | return new Operator(d => { 138 | if(typeof d === 'number') { 139 | let mask = 0; 140 | positions.forEach(p => mask |= (1 << p)); 141 | return (d & mask) === 0; 142 | } 143 | }, this[_filter]); 144 | } 145 | 146 | bitsAnyClear(positions) { 147 | return new Operator(d => { 148 | if(typeof d === 'number') { 149 | let mask = 0; 150 | positions.forEach(p => mask |= (1 << p)); 151 | return (d & mask) < mask; 152 | } 153 | }, this[_filter]); 154 | } 155 | 156 | bitsAllSet(positions) { 157 | return new Operator(d => { 158 | if(typeof d === 'number') { 159 | let mask = 0; 160 | positions.forEach(p => mask |= (1 << p)); 161 | return (d & mask) === mask; 162 | } 163 | }, this[_filter]); 164 | } 165 | 166 | bitsAnySet(positions) { 167 | return new Operator(d => { 168 | if(typeof d === 'number') { 169 | let mask = 0; 170 | positions.forEach(p => mask |= (1 << p)); 171 | return (d & mask) > 0; 172 | } 173 | }, this[_filter]); 174 | } 175 | 176 | elemMatch(conditions) { 177 | if(conditions instanceof Operator) { 178 | conditions = conditions[_filter]; 179 | } 180 | const filter = typeof conditions === 'function' 181 | ? conditions : mergeConditions(conditions); 182 | 183 | return new Operator(d => { 184 | if(Array.isArray(d)) { 185 | return d.some(item => filter(item)); 186 | } 187 | }, this[_filter]); 188 | } 189 | 190 | exists(flag) { 191 | return new Operator((d, k, o) => k in o == flag, this[_filter]); 192 | } 193 | 194 | type(t) { 195 | return new Operator((d, k, o) => k in o && getType(d) === t, this[_filter]); 196 | } 197 | 198 | not(condition) { 199 | if(condition instanceof Operator) { 200 | condition = condition[_filter]; 201 | } else if(typeof condition !== 'function') { 202 | condition = (d) => d === condition; 203 | } 204 | return new Operator((d, k, o) => !condition(d, k, o), this[_filter]); 205 | } 206 | 207 | and(...conditions) { 208 | return new Operator(mergeConditions(conditions, 'and'), this[_filter]); 209 | } 210 | 211 | or(...conditions) { 212 | return new Operator(mergeConditions(conditions, 'or'), this[_filter]); 213 | } 214 | 215 | nor(...conditions) { 216 | return new Operator(mergeConditions(conditions, 'nor'), this[_filter]); 217 | } 218 | 219 | inc(value) { 220 | const filter = this[_filter]; 221 | return new Operator((d) => { 222 | if(filter) d = filter(d); 223 | if(typeof d !== 'number') { 224 | throw new Error('Cannot apply $inc to a value of non-numeric type.'); 225 | } 226 | return d + value; 227 | }); 228 | } 229 | 230 | mul(value) { 231 | const filter = this[_filter]; 232 | return new Operator((d) => { 233 | if(filter) d = filter(d); 234 | if(typeof d !== 'number') { 235 | throw new Error('Cannot apply $inc to a value of non-numeric type.'); 236 | } 237 | return d * value; 238 | }); 239 | } 240 | 241 | min(value) { 242 | const filter = this[_filter]; 243 | return new Operator((d) => { 244 | if(filter) d = filter(d); 245 | if(typeof d !== 'number') { 246 | throw new Error('Cannot apply $inc to a value of non-numeric type.'); 247 | } 248 | return Math.min(d, value); 249 | }); 250 | } 251 | 252 | max(value) { 253 | const filter = this[_filter]; 254 | return new Operator((d) => { 255 | if(filter) d = filter(d); 256 | if(typeof d !== 'number') { 257 | throw new Error('Cannot apply $inc to a value of non-numeric type.'); 258 | } 259 | return Math.max(d, value); 260 | }); 261 | } 262 | 263 | rename(newKey) { 264 | return new Operator((d, k, o) => { 265 | if(newKey !== k) { 266 | o[newKey] = o[k]; 267 | } 268 | return; 269 | }); 270 | } 271 | 272 | unset() { 273 | return new Operator(() => { 274 | return; 275 | }); 276 | } 277 | 278 | currentDate() { 279 | return new Operator(() => new Date()); 280 | } 281 | 282 | regex(value) { 283 | return new Operator((d) => { 284 | const exp = new RegExp(value); 285 | return d.match(exp) != null; 286 | }); 287 | } 288 | } -------------------------------------------------------------------------------- /lib/platform/browser/index.js: -------------------------------------------------------------------------------- 1 | import { Storage } from './storage.js'; 2 | 3 | const dbInstances = {}; 4 | const _filter = Symbol.for('okeydb-filter'); 5 | const _notIndexFilter = Symbol.for('not-index-filter'); 6 | 7 | function upgradeDB(metaDB) { 8 | return new Promise((resolve, reject) => { 9 | const transaction = metaDB.transaction(['version'], 'readwrite'); 10 | const objectStore = transaction.objectStore('version'); 11 | const request = objectStore.get(1); 12 | request.onerror = function() { 13 | reject(new Error(request)); 14 | }; 15 | request.onsuccess = function() { 16 | const req = objectStore.put({id: 1, version: request.result.version + 1}); 17 | req.onerror = function() { 18 | reject(new Error(req)); 19 | }; 20 | req.onsuccess = function() { 21 | resolve(request.result.version + 1); 22 | }; 23 | }; 24 | }); 25 | } 26 | 27 | let version = 0; 28 | export async function createTable(table) { 29 | const dbName = table.database.name; 30 | const meta = `${dbName}.__meta__`; 31 | const tableName = table.name; 32 | 33 | if(!dbInstances[tableName]) { 34 | const metaDB = await new Promise((resolve, reject) => { 35 | const request = window.indexedDB.open(meta); 36 | request.onerror = function() { 37 | reject(new Error(request)); 38 | }; 39 | request.onsuccess = function() { 40 | const db = request.result; 41 | resolve(db); 42 | }; 43 | request.onupgradeneeded = function() { 44 | const db = request.result; 45 | db.createObjectStore('version', {keyPath: 'id'}); 46 | db.createObjectStore('tables', { keyPath: 'name' }); 47 | }; 48 | }); 49 | 50 | if(!version) 51 | version = await new Promise((resolve, reject) => { 52 | const transaction = metaDB.transaction(['version'], 'readwrite'); 53 | const objectStore = transaction.objectStore('version'); 54 | const request = objectStore.get(1); 55 | request.onerror = function() { 56 | reject(new Error(request)); 57 | }; 58 | request.onsuccess = function() { 59 | if(!request.result) { 60 | const req = objectStore.add({id: 1, version: 0}); 61 | req.onerror = function() { 62 | reject(new Error(req)); 63 | }; 64 | req.onsuccess = function() { 65 | resolve(0); 66 | }; 67 | } else { 68 | resolve(request.result.version); 69 | } 70 | }; 71 | }); 72 | 73 | const tableData = await new Promise((resolve, reject) => { 74 | const transaction = metaDB.transaction(['tables'], 'readwrite'); 75 | const objectStore = transaction.objectStore('tables'); 76 | const request = objectStore.get(tableName); 77 | request.onerror = function() { 78 | reject(new Error(request)); 79 | }; 80 | request.onsuccess = function() { 81 | resolve(request.result); 82 | }; 83 | }); 84 | 85 | if(!tableData) { 86 | await new Promise((resolve, reject) => { 87 | const transaction = metaDB.transaction(['tables'], 'readwrite'); 88 | const objectStore = transaction.objectStore('tables'); 89 | const request = objectStore.add({name: tableName, indexes: table.indexes}); 90 | request.onerror = function() { 91 | reject(new Error(request)); 92 | }; 93 | request.onsuccess = function() { 94 | resolve(request.result); 95 | }; 96 | }); 97 | version = await upgradeDB(metaDB); 98 | } else { 99 | const needsUpdate = await new Promise((resolve, reject) => { 100 | const transaction = metaDB.transaction(['tables'], 'readwrite'); 101 | const objectStore = transaction.objectStore('tables'); 102 | const request = objectStore.get(tableName); 103 | request.onerror = function() { 104 | reject(new Error(request)); 105 | }; 106 | request.onsuccess = function() { 107 | if(JSON.stringify(request.result.indexes) === JSON.stringify(table.indexes)) { 108 | resolve(false); 109 | } else { 110 | const req = objectStore.put({name: tableName, indexes: table.indexes}); 111 | req.onerror = function() { 112 | reject(new Error(req)); 113 | }; 114 | req.onsuccess = function() { 115 | resolve(true); 116 | }; 117 | } 118 | }; 119 | }); 120 | if(needsUpdate) { 121 | version = await upgradeDB(metaDB); 122 | } 123 | } 124 | 125 | dbInstances[tableName] = await new Promise((resolve, reject) => { 126 | // console.log(dbName, version, tableName); 127 | const request = window.indexedDB.open(dbName, version); 128 | request.onerror = function () { 129 | reject(new Error(request)); 130 | }; 131 | request.onsuccess = function () { 132 | resolve(request.result); 133 | }; 134 | request.onupgradeneeded = function() { 135 | const db = request.result; 136 | const upgradeTransaction = request.transaction; 137 | let objectStore; 138 | if (!db.objectStoreNames.contains(tableName)) { 139 | objectStore = db.createObjectStore(tableName, { keyPath: '_id' }); 140 | } else { 141 | objectStore = upgradeTransaction.objectStore(tableName); 142 | } 143 | const indexes = table.indexes; 144 | const len = objectStore.indexNames.length; 145 | for(let i = len - 1; i >= 0; i--) { 146 | objectStore.deleteIndex(objectStore.indexNames[i]); 147 | } 148 | for(const [k, v] of Object.entries(indexes)) { 149 | if(k !== '_id') { 150 | if (!objectStore.indexNames.contains(k)) { 151 | objectStore.createIndex(k, k, { unique: v }); 152 | } 153 | } 154 | } 155 | }; 156 | }); 157 | } 158 | const db = dbInstances[tableName]; 159 | table.database.instance = db; 160 | return new Storage({db, tableName}); 161 | } 162 | 163 | export async function fileSync() { 164 | // keep empty 165 | } 166 | 167 | export async function flushData() { 168 | // keep empty 169 | } 170 | 171 | // eslint-disable-next-line complexity 172 | export async function getRecords(table, {filter, sorter, skip, limit, filterIndexes, rawSorter} = {}) { 173 | const objectStore = table._storage.transaction(); 174 | const notIndexFilter = table[_notIndexFilter]; 175 | 176 | if(filterIndexes) { 177 | const records = []; 178 | const indexes = Object.keys(filterIndexes); 179 | let singleIndex = false; 180 | // console.log(indexes, filterIndexes); 181 | for(let i = 0; i < indexes.length; i++) { 182 | const indexName = indexes[i]; 183 | const isUnique = table.indexes[indexName]; 184 | const indexValues = [...filterIndexes[indexName]]; 185 | // console.log(indexName, isUnique, indexValues); 186 | // eslint-disable-next-line complexity 187 | const ret = await Promise.all(indexValues.map(async (value) => { 188 | if(indexName === '_id') { 189 | return new Promise((resolve, reject) => { 190 | const request = objectStore.get(value); 191 | request.onerror = function() { 192 | reject(new Error(request)); 193 | }; 194 | request.onsuccess = function() { 195 | resolve(request.result); 196 | }; 197 | }); 198 | } else if(isUnique && value && typeof value !== 'function' 199 | && typeof value[_filter] !== 'function' && !(value instanceof RegExp)) { 200 | return new Promise((resolve, reject) => { 201 | const request = objectStore.index(indexName).get(value); 202 | request.onerror = function() { 203 | reject(new Error(request)); 204 | }; 205 | request.onsuccess = function() { 206 | resolve(request.result); 207 | }; 208 | }); 209 | } else if(value && typeof value !== 'function' 210 | && typeof value[_filter] !== 'function' 211 | && !(value instanceof RegExp)) { 212 | return new Promise((resolve, reject) => { 213 | const request = objectStore.index(indexName).openCursor(IDBKeyRange.only(value)); 214 | const records = []; 215 | request.onerror = function() { 216 | reject(new Error(request)); 217 | }; 218 | request.onsuccess = function() { 219 | const cursor = request.result; 220 | if(cursor) { 221 | records.push(cursor.value); 222 | cursor.continue(); 223 | } else { 224 | resolve(records); 225 | } 226 | }; 227 | }); 228 | } else { 229 | const type = value._type; 230 | let range = null; 231 | if(type === 'gt') { 232 | range = IDBKeyRange.lowerBound(value._value, true); 233 | } else if(type === 'gte') { 234 | range = IDBKeyRange.lowerBound(value._value); 235 | } else if(type === 'lt') { 236 | range = IDBKeyRange.upperBound(value._value, true); 237 | } else if(type === 'lte') { 238 | range = IDBKeyRange.upperBound(value._value); 239 | } else if(type === 'gtlt') { 240 | range = IDBKeyRange.bound(...value._value, true, true); 241 | } else if(type === 'gtlte') { 242 | range = IDBKeyRange.bound(...value._value, true, false); 243 | } else if(type === 'gtelt') { 244 | range = IDBKeyRange.bound(...value._value, false, true); 245 | } else if(type === 'gtelte') { 246 | range = IDBKeyRange.bound(...value._value, false, false); 247 | } 248 | let direction = 'next'; 249 | if(rawSorter) { 250 | const order = rawSorter[indexName]; 251 | if(order === -1 || order === 'desc') direction = 'prev'; 252 | // the only one sorter is the only one index 253 | if(indexes.length === 1 && indexValues.length === 1) { 254 | singleIndex = true; 255 | const keys = Object.keys(rawSorter); 256 | if(keys.length === 1 && keys[0] === indexName) { 257 | sorter = null; 258 | } 259 | } 260 | } 261 | return new Promise((resolve, reject) => { 262 | const request = objectStore.index(indexName).openCursor(range, direction); 263 | const records = []; 264 | request.onerror = function() { 265 | reject(new Error(request)); 266 | }; 267 | request.onsuccess = function() { 268 | const cursor = request.result; 269 | if(cursor) { 270 | if(singleIndex && !notIndexFilter && !sorter) { 271 | if(skip > 0) { 272 | cursor.advance(skip); 273 | skip = 0; 274 | } else { 275 | records.push(cursor.value); 276 | if(records.length === limit) { 277 | resolve(records); 278 | } else { 279 | cursor.continue(); 280 | } 281 | } 282 | } else { 283 | if(filter(cursor.value)) { 284 | records.push(cursor.value); 285 | } 286 | if(singleIndex && !sorter && records.length === skip + limit) { 287 | resolve(records); 288 | } else { 289 | cursor.continue(); 290 | } 291 | } 292 | } else { 293 | resolve(records); 294 | } 295 | }; 296 | }); 297 | } 298 | })); 299 | records.push(...ret.flat()); 300 | } 301 | if(singleIndex) { 302 | if(sorter) records.sort(sorter); 303 | return records.slice(skip, skip + limit); 304 | } 305 | // console.log(records); 306 | const ret = []; // filter duplication 307 | const ids = new Set(); 308 | for(let i = 0; i < records.length; i++) { 309 | const record = records[i]; 310 | if(!record || ids.has(record._id) || !filter(record)) continue; 311 | ids.add(record._id); 312 | ret.push(record); 313 | if(!sorter && (skip > 0 || Number.isFinite(limit)) && ret.length >= skip + limit) { 314 | return ret.slice(skip, skip + limit); 315 | } 316 | } 317 | if(sorter) ret.sort(sorter); 318 | if(skip > 0 || Number.isFinite(limit)) { 319 | return ret.slice(skip, skip + limit); 320 | } 321 | return ret; 322 | } else { 323 | if(rawSorter) { 324 | const keys = Object.keys(rawSorter); 325 | if(keys.length === 1) { // only one sorter and no filter-index 326 | const key = keys[0]; 327 | const order = rawSorter[key]; 328 | if(table.indexes[key] != null) { 329 | let direction = 'next'; 330 | if(order === -1 || order === 'desc') direction = 'prev'; 331 | const records = await new Promise((resolve, reject) => { 332 | const request = objectStore.index(key).openCursor(null, direction); 333 | const records = []; 334 | request.onerror = function() { 335 | reject(new Error(request)); 336 | }; 337 | request.onsuccess = function() { 338 | const cursor = request.result; 339 | if(cursor) { 340 | if(!notIndexFilter) { 341 | if(skip > 0) { 342 | cursor.advance(skip); 343 | skip = 0; 344 | } else { 345 | records.push(cursor.value); 346 | if(records.length === limit) { 347 | resolve(records); 348 | } else { 349 | cursor.continue(); 350 | } 351 | } 352 | } else { 353 | if(filter(cursor.value)) { 354 | records.push(cursor.value); 355 | } 356 | if(records.length === skip + limit) { 357 | resolve(records); 358 | } else { 359 | cursor.continue(); 360 | } 361 | } 362 | } else { 363 | resolve(records); 364 | } 365 | }; 366 | }); 367 | return records.slice(skip, skip + limit); 368 | } 369 | } 370 | } 371 | const records = await new Promise((resolve, reject) => { 372 | const request = objectStore.index('createdAt').getAll(); 373 | request.onerror = function() { 374 | reject(new Error(request)); 375 | }; 376 | request.onsuccess = function() { 377 | resolve(request.result); 378 | }; 379 | }); 380 | let filtedRecords; 381 | if(!sorter && skip === 0 && limit === 1) { 382 | filtedRecords = records.find(filter); 383 | if(filtedRecords) return [filtedRecords]; 384 | return []; 385 | } else { 386 | filtedRecords = records.filter(filter); 387 | } 388 | if(sorter) filtedRecords.sort(sorter); 389 | if(skip > 0 || Number.isFinite(limit)) { 390 | filtedRecords = filtedRecords.slice(skip, skip + limit); 391 | } 392 | return filtedRecords; 393 | } 394 | } -------------------------------------------------------------------------------- /lib/platform/browser/storage.js: -------------------------------------------------------------------------------- 1 | export class Storage { 2 | #storage; 3 | 4 | constructor(storage) { 5 | this.#storage = storage; 6 | } 7 | 8 | transaction(type = 'readonly') { 9 | const name = this.#storage.tableName; 10 | return this.#storage.db.transaction([name], type) 11 | .objectStore(name); 12 | } 13 | 14 | add(records) { 15 | const promises = records.map((record) => { 16 | return new Promise((resolve, reject) => { 17 | const request = this.transaction('readwrite').add(record); 18 | 19 | request.onsuccess = function () { 20 | resolve(request.result); 21 | }; 22 | 23 | request.onerror = function () { 24 | reject(new Error('Datebase error.')); 25 | }; 26 | }); 27 | }); 28 | 29 | return Promise.all(promises); 30 | } 31 | 32 | getItemIndex(id) { 33 | return id; 34 | } 35 | 36 | put(idx, record) { 37 | return new Promise((resolve, reject) => { 38 | const request = this.transition('readwrite').put(record); 39 | 40 | request.onsuccess = function () { 41 | resolve(request.result); 42 | }; 43 | 44 | request.onerror = function () { 45 | reject(new Error('Datebase error.')); 46 | }; 47 | }); 48 | } 49 | 50 | delete(deleteMap) { 51 | const promises = []; 52 | for(const id of Object.keys(deleteMap)) { 53 | promises.push(new Promise((resolve, reject) => { 54 | const request = this.transaction('readwrite').delete(id); 55 | request.onsuccess = function () { 56 | resolve(request.result); 57 | }; 58 | 59 | request.onerror = function () { 60 | reject(new Error('Datebase error.')); 61 | }; 62 | })); 63 | } 64 | return Promise.all(promises); 65 | } 66 | } -------------------------------------------------------------------------------- /lib/platform/index.js: -------------------------------------------------------------------------------- 1 | export { fileSync, getRecords, flushData, createTable } from './node'; -------------------------------------------------------------------------------- /lib/platform/node/index.js: -------------------------------------------------------------------------------- 1 | import { sleep } from '../../utils.js'; 2 | 3 | import { Storage } from './storage.js'; 4 | 5 | import path from 'node:path'; 6 | import { existsSync } from 'node:fs'; 7 | import { writeFile, readFile, unlink, mkdir } from 'node:fs/promises'; 8 | 9 | import { v4 as uuidv4 } from 'uuid'; 10 | 11 | async function getRecordsFromFile(filepath) { 12 | await _fileLock(filepath); 13 | let records = await readFile(filepath, {charset: 'utf8'}); 14 | await _fileUnlock(filepath); 15 | records = JSON.parse(records); 16 | records.records = records.records.map((r, i) => { 17 | const schema = records._schema[i]; 18 | for(const [k, v] of Object.entries(schema)) { 19 | if(v === 'date') { 20 | r[k] = new Date(r[k]); 21 | } else if(v === 'regexp') { 22 | r[k] = new RegExp(r[k].source, r[k].flags); 23 | } 24 | } 25 | return r; 26 | }); 27 | delete records._schema; 28 | return new Storage(records); 29 | } 30 | 31 | async function _fileLock(filepath, unlock = false) { 32 | const locker = `${filepath}.lck`; 33 | while(existsSync(locker)) { 34 | await sleep(10); 35 | } 36 | if(!unlock) await writeFile(locker, ''); 37 | } 38 | 39 | async function _fileUnlock(filepath) { 40 | const locker = `${filepath}.lck`; 41 | await unlink(locker); 42 | } 43 | 44 | async function _updateMeta(metafile, version) { 45 | await _fileLock(metafile); 46 | const metadata = JSON.parse(await readFile(metafile, {charset: 'utf8'})); 47 | metadata.version = version; 48 | await writeFile(metafile, JSON.stringify(metadata), {charset: 'utf8'}); 49 | await _fileUnlock(metafile); 50 | } 51 | 52 | const _root = Symbol('root'); 53 | const _meta = Symbol('meta'); 54 | 55 | 56 | function metapath(table) { 57 | return path.join(table[_meta], `${table.name}.meta`); 58 | } 59 | 60 | function filepath(table) { 61 | return path.join(table[_root], table.name); 62 | } 63 | 64 | export async function createTable(table, root, meta) { 65 | table[_root] = root; 66 | table[_meta] = meta; 67 | 68 | if(!(existsSync(table[_root]))) { 69 | await mkdir(table[_root]); 70 | } 71 | 72 | if(!(existsSync(table[_meta]))) { 73 | await mkdir(table[_meta]); 74 | } 75 | 76 | if(!(existsSync(metapath(table)))) { 77 | table._version = uuidv4(); 78 | await writeFile(metapath(table), JSON.stringify({version: table._version}), {charset: 'utf8'}); 79 | } else { 80 | const {version} = JSON.parse(await readFile(metapath(table), {charset: 'utf8'})); 81 | table._version = version; 82 | } 83 | 84 | if(!(existsSync(filepath(table)))) { 85 | const records = { 86 | _ids: {}, 87 | records: [], 88 | }; 89 | await writeFile(filepath(table), JSON.stringify(records), {charset: 'utf8'}); 90 | return new Storage(records); 91 | } 92 | 93 | return null; 94 | } 95 | 96 | export async function fileSync(table) { 97 | await _fileLock(filepath(table)); 98 | await writeFile(filepath(table), JSON.stringify(table._storage), {charset: 'utf8'}); 99 | const version = uuidv4(); 100 | await _updateMeta(metapath(table), version); 101 | table._version = version; 102 | await _fileUnlock(filepath(table)); 103 | } 104 | 105 | export async function flushData(table) { 106 | await _fileLock(metapath(table)); 107 | const {version} = JSON.parse(await readFile(metapath(table), {charset: 'utf8'})); 108 | if(!table._storage || table._version !== version) { 109 | table._storage = await getRecordsFromFile(filepath(table)); 110 | } 111 | table._version = version; 112 | await _fileUnlock(metapath(table)); 113 | } 114 | 115 | export async function getRecords(table, {filter, sorter, skip, limit} = {}) { 116 | await flushData(table); 117 | const records = table._storage.records; 118 | let filtedRecords; 119 | if(!sorter && skip === 0 && limit === 1) { 120 | filtedRecords = records.find(filter); 121 | if(filtedRecords) return [filtedRecords]; 122 | return []; 123 | } else { 124 | filtedRecords = records.filter(filter); 125 | } 126 | if(sorter) filtedRecords.sort(sorter); 127 | if(skip > 0 || Number.isFinite(limit)) { 128 | filtedRecords = filtedRecords.slice(skip, skip + limit); 129 | } 130 | return filtedRecords; 131 | } 132 | -------------------------------------------------------------------------------- /lib/platform/node/storage.js: -------------------------------------------------------------------------------- 1 | import { getType } from '../../utils.js'; 2 | 3 | export class Storage { 4 | #storage; 5 | 6 | constructor(storage) { 7 | this.#storage = storage; 8 | } 9 | 10 | toJSON() { 11 | const _schema = this.#storage.records.map(d => { 12 | const s = {}; 13 | for(const [k, v] of Object.entries(d)) { 14 | s[k] = getType(v); 15 | } 16 | return s; 17 | }); 18 | return { 19 | _ids: this.#storage._ids, 20 | records: this.#storage.records, 21 | _schema, 22 | }; 23 | } 24 | 25 | get records() { 26 | return this.#storage.records; 27 | } 28 | 29 | add(records) { 30 | const start = this.#storage.records.length; 31 | for(let i = 0; i < records.length; i++) { 32 | const id = records[i]._id; 33 | this.#storage._ids[id] = start + i; 34 | } 35 | this.#storage.records = [...this.#storage.records, ...records]; 36 | } 37 | 38 | getItemIndex(id) { 39 | return this.#storage._ids[id]; 40 | } 41 | 42 | put(idx, record) { 43 | this.#storage.records[idx] = record; 44 | } 45 | 46 | delete(deleteMap) { 47 | this.#storage.records = this.#storage.records.filter((_, idx) => !deleteMap[idx]); 48 | this.#storage._ids = {}; 49 | for(let i = 0; i < this.#storage.records.length; i++) { 50 | const record = this.#storage.records[i]; 51 | this.#storage._ids[record._id] = i; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/query.js: -------------------------------------------------------------------------------- 1 | import {mergeConditions} from './utils.js'; 2 | 3 | const _notIndexFilter = Symbol.for('not-index-filter'); 4 | function updateFilterIndex(query, conditions, filterIndexes = {} ,phase = 'and') { 5 | const indexes = query.table.indexes; 6 | let notIndexFilter = false; 7 | for(let i = 0; i < conditions.length; i++) { 8 | const condition = conditions[i]; 9 | let hasIndex = false; 10 | for(const [k, v] of Object.entries(condition)) { 11 | if(k in indexes) { 12 | hasIndex = true; 13 | filterIndexes[k] = filterIndexes[k] || new Set(); 14 | filterIndexes[k].add(v); 15 | if(phase === 'and' && filterIndexes[k].size > 1) filterIndexes[k].clear(); 16 | } else { 17 | notIndexFilter = true; 18 | } 19 | } 20 | if(!hasIndex && phase === 'or') { 21 | query.table[_notIndexFilter] = notIndexFilter; 22 | return null; 23 | } 24 | } 25 | query.table[_notIndexFilter] = notIndexFilter; 26 | return filterIndexes; 27 | } 28 | 29 | 30 | const _filter = Symbol.for('okeydb-filter'); 31 | 32 | export default class { 33 | #table; 34 | #filter; 35 | #records; 36 | #sorter = null; 37 | #rawSorter; 38 | #skip = 0; 39 | #limit = Infinity; 40 | #projection = null; 41 | #updateFields = null; 42 | #insertFields = {}; 43 | #setOnInsertFields = null; 44 | #upsert = false; 45 | #filterIndexes = {}; 46 | 47 | constructor(condition, table) { 48 | this.#table = table; 49 | if(condition) { 50 | this.#filter = mergeConditions([condition]); 51 | this.#insertFields = {...condition}; 52 | this.#filterIndexes = updateFilterIndex(this, [condition], {}, 'and'); 53 | } 54 | } 55 | 56 | and(...conditions) { 57 | const left = this.#filter; 58 | const right = mergeConditions(conditions); 59 | if(left) { 60 | this.#filter = record => left(record) && right(record); 61 | } else { 62 | this.#filter = right; 63 | } 64 | for(let i = 0; i < conditions.length; i++) { 65 | Object.assign(this.#insertFields, conditions[i]); 66 | } 67 | if(this.#filterIndexes) this.#filterIndexes = updateFilterIndex(this, conditions, this.#filterIndexes, 'and'); 68 | return this; 69 | } 70 | 71 | or(...conditions) { 72 | const left = this.#filter; 73 | const right = mergeConditions(conditions, 'or'); 74 | if(left) { 75 | this.#filter = record => left(record) || right(record); 76 | } else { 77 | this.#filter = right; 78 | } 79 | this.#insertFields = {}; 80 | if(this.#filterIndexes) this.#filterIndexes = updateFilterIndex(this, conditions, this.#filterIndexes, 'or'); 81 | return this; 82 | } 83 | 84 | nor(...conditions) { 85 | const left = this.#filter; 86 | const right = mergeConditions(conditions, 'or'); 87 | if(left) { 88 | this.#filter = record => !(left(record) || right(record)); 89 | } else { 90 | this.#filter = record => !right(record); 91 | } 92 | this.#insertFields = {}; 93 | this.#filterIndexes = null; 94 | this.table[_notIndexFilter] = true; 95 | return this; 96 | } 97 | 98 | async find() { 99 | let filtedRecords = await this.#table.getRecords({ 100 | filter: this.#filter || function() {return true;}, 101 | sorter: this.#sorter, 102 | rawSorter: this.#rawSorter, 103 | skip: this.#skip, 104 | limit: this.#limit, 105 | filterIndexes: this.filterIndexes, 106 | }); 107 | 108 | if(this.#projection) { 109 | const {type, fields} = this.#projection; 110 | if(type === 'inclusion') { 111 | filtedRecords = filtedRecords.map(r => { 112 | const ret = {}; 113 | fields.forEach(f => ret[f] = r[f]); 114 | return ret; 115 | }); 116 | } else if(type === 'exclusion') { 117 | filtedRecords = filtedRecords.map(r => { 118 | const ret = {...r}; 119 | fields.forEach(f => delete ret[f]); 120 | return ret; 121 | }); 122 | } 123 | } 124 | this.#records = filtedRecords; 125 | return filtedRecords; 126 | } 127 | 128 | async findOne() { 129 | const records = await this.#table.getRecords({ 130 | filter: this.#filter || function() {return true;}, 131 | sorter: this.#sorter, 132 | rawSorter: this.#rawSorter, 133 | skip: this.#skip, 134 | limit: 1, 135 | filterIndexes: this.filterIndexes, 136 | }); 137 | const record = records[0]; 138 | // const record = records.find(this.#filter) || null; 139 | if(this.#projection) { 140 | const {type, fields} = this.#projection; 141 | const ret = {}; 142 | if(type === 'inclusion') { 143 | fields.forEach(f => ret[f] = record[f]); 144 | } else if(type === 'exclusion') { 145 | Object.assign(ret, record); 146 | fields.forEach(f => delete ret[f]); 147 | } 148 | return ret; 149 | } 150 | return record; 151 | } 152 | 153 | async count() { 154 | if(this.#records) return this.#records.length; 155 | return await this.find().length; 156 | } 157 | 158 | set(fields) { 159 | this.#updateFields = fields; 160 | return this; 161 | } 162 | 163 | setOnInsert(fields) { 164 | this.#setOnInsertFields = fields; 165 | return this; 166 | } 167 | 168 | upsert(flag) { 169 | this.#upsert = flag; 170 | return this; 171 | } 172 | 173 | async save() { 174 | if(this.#updateFields || this.#upsert) { 175 | let records = this.#records; 176 | if(!records) records = await this.find(); 177 | if(records.length <= 0 && this.#upsert) { 178 | // insert 179 | records = Object.assign({}, this.#insertFields, this.#setOnInsertFields); 180 | for(let [k, v] of Object.entries(records)) { 181 | if(v && typeof v[_filter] === 'function') { 182 | v = v[_filter]; 183 | } 184 | if(typeof v === 'function') { 185 | records[k] = v(records[k], k, records); 186 | } 187 | } 188 | if(this.#updateFields) { 189 | const updateFields = this.#updateFields; 190 | for(let [k, v] of Object.entries(updateFields)) { 191 | if(v && typeof v[_filter] === 'function') { 192 | v = v[_filter]; 193 | } 194 | if(typeof v !== 'function') { 195 | records[k] = v; 196 | } else { 197 | records[k] = v(records[k], k, records); 198 | if(records[k] === undefined) delete records[k]; 199 | } 200 | } 201 | } 202 | } else if(this.#updateFields) { 203 | const updateFields = this.#updateFields; 204 | records = records.map(record => { 205 | const ret = {...record}; 206 | for(let [k, v] of Object.entries(updateFields)) { 207 | if(v && typeof v[_filter] === 'function') { 208 | v = v[_filter]; 209 | } 210 | if(typeof v !== 'function') { 211 | ret[k] = v; 212 | } else { 213 | ret[k] = v(ret[k], k, ret); 214 | if(ret[k] === undefined) delete ret[k]; 215 | } 216 | } 217 | return ret; 218 | }); 219 | } else { 220 | return await this.#table.save([], true); 221 | } 222 | return await this.#table.save(records, true); 223 | } 224 | throw new Error('Must use set or upsert at least once'); 225 | } 226 | 227 | async delete() { 228 | let records = this.#records; 229 | if(!records) records = await this.find(); 230 | return await this.#table.delete(records); 231 | } 232 | 233 | sort(conditions) { 234 | const conds = Object.entries(conditions); 235 | this.#rawSorter = conditions; 236 | this.#sorter = (a, b) => { 237 | for(let [k, v] of conds) { 238 | if(typeof v === 'string') { 239 | if(v.toLowerCase() === 'asc') { 240 | v = 1; 241 | } else if(v.toLowerCase() === 'desc') { 242 | v = -1; 243 | } 244 | } 245 | if(v !== 1 && v !== -1) throw new Error(`Invalid sort condition: ${k} ${v}`); 246 | if(a[k] != b[k]) { 247 | return a[k] > b[k] ? v * 1 : v * -1; 248 | } 249 | } 250 | return 0; 251 | }; 252 | return this; 253 | } 254 | 255 | skip(n) { 256 | this.#skip = n; 257 | return this; 258 | } 259 | 260 | limit(n) { 261 | this.#limit = n; 262 | return this; 263 | } 264 | 265 | projection(conditions) { 266 | let type = null; 267 | const fields = []; 268 | let ignoreId = false; 269 | for(const [k, v] of Object.entries(conditions)) { 270 | if(k === '_id') { 271 | ignoreId = !v; 272 | continue; 273 | } 274 | if(!type && v) type = 'inclusion'; 275 | else if(!type && !v) type = 'exclusion'; 276 | else if(type === 'inclusion' && !v || type === 'exclusion' && v) 277 | throw new Error('Projection cannot have a mix of inclusion and exclusion.'); 278 | fields.push(k); 279 | } 280 | if(type === 'exclusion' && ignoreId || type === 'inclusion' && !ignoreId) { 281 | fields.push('_id'); 282 | } 283 | if(type === 'exclusion' && !ignoreId) { 284 | throw new Error('Projection cannot have a mix of inclusion and exclusion.'); 285 | } 286 | this.#projection = {type: type || 'inclusion', fields}; 287 | return this; 288 | } 289 | 290 | get table() { 291 | return this.#table; 292 | } 293 | 294 | get filterIndexes() { 295 | const filterIndexes = this.#filterIndexes || {}; 296 | if(Object.keys(filterIndexes).length) return filterIndexes; 297 | return null; 298 | } 299 | } -------------------------------------------------------------------------------- /lib/table.js: -------------------------------------------------------------------------------- 1 | import Query from './query.js'; 2 | import { v4 as uuidv4 } from 'uuid'; 3 | 4 | const Table = (() => { 5 | let platform; 6 | 7 | // eslint-disable-next-line no-undef 8 | if(typeof ESB_PLATFORM === 'string' && ESB_PLATFORM === 'browser') { 9 | platform = import('./platform/browser/index.js'); 10 | } else { 11 | platform = import('./platform/node/index.js'); 12 | } 13 | 14 | RegExp.prototype.toJSON = function() { 15 | return {type: 'RegExp', source: this.source, flags: this.flags}; 16 | }; 17 | 18 | return class { 19 | #name; 20 | #db; 21 | #ready; 22 | #indexes; 23 | 24 | constructor(name, {root = '.db', meta = '.meta', database, indexes} = {}) { 25 | if(name.startsWith('.')) { 26 | throw new TypeError('The table name cannot starts with \'.\'.'); 27 | } 28 | 29 | this.#name = name; 30 | this.#db = database; 31 | this.#indexes = { 32 | _id: true, // indent 33 | createdAt: false, 34 | updatedAt: false, 35 | ...indexes, 36 | }; 37 | 38 | this.#ready = platform.then(({createTable}) => { 39 | return createTable(this, root, meta); 40 | }).then((res) => { 41 | this._storage = res; 42 | }); 43 | } 44 | 45 | get indexes() { 46 | return this.#indexes; 47 | } 48 | 49 | get database() { 50 | return this.#db; 51 | } 52 | 53 | get name() { 54 | return this.#name; 55 | } 56 | 57 | async getRecords({filter, sorter, skip, limit, filterIndexes, rawSorter} = {}) { 58 | await this.#ready; 59 | const {getRecords} = await platform; 60 | return getRecords(this, {filter, sorter, skip, limit, filterIndexes, rawSorter}); 61 | } 62 | 63 | async save(records = [], countResult = false) { 64 | await this.#ready; 65 | const originalRecords = records; 66 | if(!Array.isArray(records)) { 67 | records = [records]; 68 | } 69 | 70 | const {flushData} = await platform; 71 | await flushData(this); 72 | 73 | const insertRecords = []; 74 | const datetime = new Date(); 75 | 76 | for(let i = 0; i < records.length; i++) { 77 | const record = records[i]; 78 | record.createdAt = record.createdAt || datetime; 79 | record.updatedAt = datetime; 80 | 81 | if(record._id != null) { 82 | const idx = this._storage.getItemIndex(record._id); 83 | if(idx >= 0) { 84 | await this._storage.put(idx, record); 85 | } 86 | } else { 87 | record._id = record._id || uuidv4(); 88 | insertRecords.push(record); 89 | } 90 | } 91 | 92 | const upsertedCount = insertRecords.length; 93 | const modifiedCount = records.length - upsertedCount; 94 | 95 | await this._storage.add(insertRecords); 96 | 97 | const {fileSync} = await platform; 98 | await fileSync(this); 99 | 100 | if(countResult) return {modifiedCount, upsertedCount}; 101 | 102 | return originalRecords; 103 | } 104 | 105 | async delete(records = []) { 106 | await this.#ready; 107 | if(!Array.isArray(records)) records = [records]; 108 | 109 | const {flushData} = await platform; 110 | await flushData(this); 111 | 112 | let deletedCount = 0; 113 | 114 | const filterMap = {}; 115 | 116 | for(let i = 0; i < records.length; i++) { 117 | const record = records[i]; 118 | const idx = this._storage.getItemIndex(record._id); 119 | if(idx >= 0) deletedCount++; 120 | filterMap[idx] = true; 121 | } 122 | 123 | await this._storage.delete(filterMap); 124 | 125 | const {fileSync} = await platform; 126 | await fileSync(this); 127 | 128 | return {deletedCount}; 129 | } 130 | 131 | where(condition = null) { 132 | const query = new Query(condition, this); 133 | return query; 134 | } 135 | }; 136 | })(); 137 | 138 | export default Table; -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | const _filter = Symbol.for('okeydb-filter'); 2 | 3 | function parseCondition(condition = {}) { 4 | if(typeof condition === 'function') return condition; 5 | if(condition[_filter]) return condition[_filter]; 6 | 7 | const filters = []; 8 | 9 | for(const [k, v] of Object.entries(condition)) { 10 | if(typeof v === 'function') { 11 | filters.push((d) => v(d[k], k, d)); 12 | } else if (v && typeof v[_filter] === 'function') { 13 | const f = v[_filter]; 14 | filters.push((d) => f(d[k], k, d)); 15 | } else if(v instanceof RegExp) { 16 | filters.push((d) => d[k] && typeof d[k].match === 'function' && d[k].match(v) != null); 17 | } else { 18 | filters.push((d) => d[k] === v); 19 | } 20 | } 21 | return record => filters.every(f => f(record)); 22 | } 23 | 24 | export function mergeConditions(conditions, type = 'and') { 25 | const filters = []; 26 | for(let i = 0; i < conditions.length; i++) { 27 | filters.push(parseCondition(conditions[i])); 28 | } 29 | 30 | if(type === 'and') { 31 | return record => filters.every(f => f(record)); 32 | } else if(type === 'or') { 33 | return record => filters.some(f => f(record)); 34 | } else if(type === 'nor') { 35 | return record => !filters.some(f => f(record)); 36 | } 37 | } 38 | 39 | export function getType(value) { 40 | let type = typeof value; 41 | if(type === 'object' && Array.isArray(value)) { 42 | type = 'array'; 43 | } else if(type === 'object' && value instanceof Date) { 44 | type = 'date'; 45 | } else if(type === 'object' && value instanceof RegExp) { 46 | type = 'regexp'; 47 | } else if(value == null) { 48 | type = 'null'; 49 | } 50 | return type; 51 | } 52 | 53 | export function sleep(ms) { 54 | return new Promise(resolve => setTimeout(resolve, ms)); 55 | } 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "okeydb", 3 | "version": "0.1.2", 4 | "description": "", 5 | "type": "module", 6 | "main": "dist/okeydb.cjs", 7 | "module": "index.js", 8 | "browser": "dist/okeydb.browser.js", 9 | "scripts": { 10 | "dev": "node build.js", 11 | "build": "mode=production node build.js", 12 | "prepublishOnly": "npm run build", 13 | "test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest", 14 | "test:browser": "npm run build && cp ./dist/okeydb.browser.mjs ./test/dist/okeydb.browser.mjs" 15 | }, 16 | "keywords": [], 17 | "author": "", 18 | "license": "MIT", 19 | "dependencies": { 20 | "uuid": "^9.0.0" 21 | }, 22 | "devDependencies": { 23 | "cross-env": "^7.0.3", 24 | "esbuild": "^0.18.14", 25 | "eslint": "^8.47.0", 26 | "jest": "^29.5.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/dist/okeydb.browser.mjs: -------------------------------------------------------------------------------- 1 | var __defProp = Object.defineProperty; 2 | var __getOwnPropNames = Object.getOwnPropertyNames; 3 | var __esm = (fn, res) => function __init() { 4 | return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; 5 | }; 6 | var __export = (target, all) => { 7 | for (var name in all) 8 | __defProp(target, name, { get: all[name], enumerable: true }); 9 | }; 10 | 11 | // lib/platform/browser/storage.js 12 | var Storage; 13 | var init_storage = __esm({ 14 | "lib/platform/browser/storage.js"() { 15 | Storage = class { 16 | #storage; 17 | constructor(storage) { 18 | this.#storage = storage; 19 | } 20 | transaction(type = "readonly") { 21 | const name = this.#storage.tableName; 22 | return this.#storage.db.transaction([name], type).objectStore(name); 23 | } 24 | add(records) { 25 | const promises = records.map((record) => { 26 | return new Promise((resolve, reject) => { 27 | const request = this.transaction("readwrite").add(record); 28 | request.onsuccess = function() { 29 | resolve(request.result); 30 | }; 31 | request.onerror = function() { 32 | reject(new Error("Datebase error.")); 33 | }; 34 | }); 35 | }); 36 | return Promise.all(promises); 37 | } 38 | getItemIndex(id) { 39 | return id; 40 | } 41 | put(idx, record) { 42 | return new Promise((resolve, reject) => { 43 | const request = this.transition("readwrite").put(record); 44 | request.onsuccess = function() { 45 | resolve(request.result); 46 | }; 47 | request.onerror = function() { 48 | reject(new Error("Datebase error.")); 49 | }; 50 | }); 51 | } 52 | delete(deleteMap) { 53 | const promises = []; 54 | for (const id of Object.keys(deleteMap)) { 55 | promises.push(new Promise((resolve, reject) => { 56 | const request = this.transaction("readwrite").delete(id); 57 | request.onsuccess = function() { 58 | resolve(request.result); 59 | }; 60 | request.onerror = function() { 61 | reject(new Error("Datebase error.")); 62 | }; 63 | })); 64 | } 65 | return Promise.all(promises); 66 | } 67 | }; 68 | } 69 | }); 70 | 71 | // lib/platform/browser/index.js 72 | var browser_exports = {}; 73 | __export(browser_exports, { 74 | createTable: () => createTable, 75 | fileSync: () => fileSync, 76 | flushData: () => flushData, 77 | getRecords: () => getRecords 78 | }); 79 | function upgradeDB(metaDB) { 80 | return new Promise((resolve, reject) => { 81 | const transaction = metaDB.transaction(["version"], "readwrite"); 82 | const objectStore = transaction.objectStore("version"); 83 | const request = objectStore.get(1); 84 | request.onerror = function() { 85 | reject(new Error(request)); 86 | }; 87 | request.onsuccess = function() { 88 | const req = objectStore.put({ id: 1, version: request.result.version + 1 }); 89 | req.onerror = function() { 90 | reject(new Error(req)); 91 | }; 92 | req.onsuccess = function() { 93 | resolve(request.result.version + 1); 94 | }; 95 | }; 96 | }); 97 | } 98 | async function createTable(table) { 99 | const dbName = table.database.name; 100 | const meta = `${dbName}.__meta__`; 101 | const tableName = table.name; 102 | if (!dbInstances[tableName]) { 103 | const metaDB = await new Promise((resolve, reject) => { 104 | const request = window.indexedDB.open(meta); 105 | request.onerror = function() { 106 | reject(new Error(request)); 107 | }; 108 | request.onsuccess = function() { 109 | const db2 = request.result; 110 | resolve(db2); 111 | }; 112 | request.onupgradeneeded = function() { 113 | const db2 = request.result; 114 | db2.createObjectStore("version", { keyPath: "id" }); 115 | db2.createObjectStore("tables", { keyPath: "name" }); 116 | }; 117 | }); 118 | if (!version) 119 | version = await new Promise((resolve, reject) => { 120 | const transaction = metaDB.transaction(["version"], "readwrite"); 121 | const objectStore = transaction.objectStore("version"); 122 | const request = objectStore.get(1); 123 | request.onerror = function() { 124 | reject(new Error(request)); 125 | }; 126 | request.onsuccess = function() { 127 | if (!request.result) { 128 | const req = objectStore.add({ id: 1, version: 0 }); 129 | req.onerror = function() { 130 | reject(new Error(req)); 131 | }; 132 | req.onsuccess = function() { 133 | resolve(0); 134 | }; 135 | } else { 136 | resolve(request.result.version); 137 | } 138 | }; 139 | }); 140 | const tableData = await new Promise((resolve, reject) => { 141 | const transaction = metaDB.transaction(["tables"], "readwrite"); 142 | const objectStore = transaction.objectStore("tables"); 143 | const request = objectStore.get(tableName); 144 | request.onerror = function() { 145 | reject(new Error(request)); 146 | }; 147 | request.onsuccess = function() { 148 | resolve(request.result); 149 | }; 150 | }); 151 | if (!tableData) { 152 | await new Promise((resolve, reject) => { 153 | const transaction = metaDB.transaction(["tables"], "readwrite"); 154 | const objectStore = transaction.objectStore("tables"); 155 | const request = objectStore.add({ name: tableName, indexes: table.indexes }); 156 | request.onerror = function() { 157 | reject(new Error(request)); 158 | }; 159 | request.onsuccess = function() { 160 | resolve(request.result); 161 | }; 162 | }); 163 | version = await upgradeDB(metaDB); 164 | } else { 165 | const needsUpdate = await new Promise((resolve, reject) => { 166 | const transaction = metaDB.transaction(["tables"], "readwrite"); 167 | const objectStore = transaction.objectStore("tables"); 168 | const request = objectStore.get(tableName); 169 | request.onerror = function() { 170 | reject(new Error(request)); 171 | }; 172 | request.onsuccess = function() { 173 | if (JSON.stringify(request.result.indexes) === JSON.stringify(table.indexes)) { 174 | resolve(false); 175 | } else { 176 | const req = objectStore.put({ name: tableName, indexes: table.indexes }); 177 | req.onerror = function() { 178 | reject(new Error(req)); 179 | }; 180 | req.onsuccess = function() { 181 | resolve(true); 182 | }; 183 | } 184 | }; 185 | }); 186 | if (needsUpdate) { 187 | version = await upgradeDB(metaDB); 188 | } 189 | } 190 | dbInstances[tableName] = await new Promise((resolve, reject) => { 191 | const request = window.indexedDB.open(dbName, version); 192 | request.onerror = function() { 193 | reject(new Error(request)); 194 | }; 195 | request.onsuccess = function() { 196 | resolve(request.result); 197 | }; 198 | request.onupgradeneeded = function() { 199 | const db2 = request.result; 200 | const upgradeTransaction = request.transaction; 201 | let objectStore; 202 | if (!db2.objectStoreNames.contains(tableName)) { 203 | objectStore = db2.createObjectStore(tableName, { keyPath: "_id" }); 204 | } else { 205 | objectStore = upgradeTransaction.objectStore(tableName); 206 | } 207 | const indexes = table.indexes; 208 | const len = objectStore.indexNames.length; 209 | for (let i = len - 1; i >= 0; i--) { 210 | objectStore.deleteIndex(objectStore.indexNames[i]); 211 | } 212 | for (const [k, v] of Object.entries(indexes)) { 213 | if (k !== "_id") { 214 | if (!objectStore.indexNames.contains(k)) { 215 | objectStore.createIndex(k, k, { unique: v }); 216 | } 217 | } 218 | } 219 | }; 220 | }); 221 | } 222 | const db = dbInstances[tableName]; 223 | table.database.instance = db; 224 | return new Storage({ db, tableName }); 225 | } 226 | async function fileSync() { 227 | } 228 | async function flushData() { 229 | } 230 | async function getRecords(table, { filter, sorter, skip, limit, filterIndexes, rawSorter } = {}) { 231 | const objectStore = table._storage.transaction(); 232 | const notIndexFilter = table[_notIndexFilter2]; 233 | if (filterIndexes) { 234 | const records = []; 235 | const indexes = Object.keys(filterIndexes); 236 | let singleIndex = false; 237 | for (let i = 0; i < indexes.length; i++) { 238 | const indexName = indexes[i]; 239 | const isUnique = table.indexes[indexName]; 240 | const indexValues = [...filterIndexes[indexName]]; 241 | const ret2 = await Promise.all(indexValues.map(async (value) => { 242 | if (indexName === "_id") { 243 | return new Promise((resolve, reject) => { 244 | const request = objectStore.get(value); 245 | request.onerror = function() { 246 | reject(new Error(request)); 247 | }; 248 | request.onsuccess = function() { 249 | resolve(request.result); 250 | }; 251 | }); 252 | } else if (isUnique && value && typeof value !== "function" && typeof value[_filter3] !== "function" && !(value instanceof RegExp)) { 253 | return new Promise((resolve, reject) => { 254 | const request = objectStore.index(indexName).get(value); 255 | request.onerror = function() { 256 | reject(new Error(request)); 257 | }; 258 | request.onsuccess = function() { 259 | resolve(request.result); 260 | }; 261 | }); 262 | } else if (value && typeof value !== "function" && typeof value[_filter3] !== "function" && !(value instanceof RegExp)) { 263 | return new Promise((resolve, reject) => { 264 | const request = objectStore.index(indexName).openCursor(IDBKeyRange.only(value)); 265 | const records2 = []; 266 | request.onerror = function() { 267 | reject(new Error(request)); 268 | }; 269 | request.onsuccess = function() { 270 | const cursor = request.result; 271 | if (cursor) { 272 | records2.push(cursor.value); 273 | cursor.continue(); 274 | } else { 275 | resolve(records2); 276 | } 277 | }; 278 | }); 279 | } else { 280 | const type = value._type; 281 | let range = null; 282 | if (type === "gt") { 283 | range = IDBKeyRange.lowerBound(value._value, true); 284 | } else if (type === "gte") { 285 | range = IDBKeyRange.lowerBound(value._value); 286 | } else if (type === "lt") { 287 | range = IDBKeyRange.upperBound(value._value, true); 288 | } else if (type === "lte") { 289 | range = IDBKeyRange.upperBound(value._value); 290 | } else if (type === "gtlt") { 291 | range = IDBKeyRange.bound(...value._value, true, true); 292 | } else if (type === "gtlte") { 293 | range = IDBKeyRange.bound(...value._value, true, false); 294 | } else if (type === "gtelt") { 295 | range = IDBKeyRange.bound(...value._value, false, true); 296 | } else if (type === "gtelte") { 297 | range = IDBKeyRange.bound(...value._value, false, false); 298 | } 299 | let direction = "next"; 300 | if (rawSorter) { 301 | const order = rawSorter[indexName]; 302 | if (order === -1 || order === "desc") 303 | direction = "prev"; 304 | if (indexes.length === 1 && indexValues.length === 1) { 305 | singleIndex = true; 306 | const keys = Object.keys(rawSorter); 307 | if (keys.length === 1 && keys[0] === indexName) { 308 | sorter = null; 309 | } 310 | } 311 | } 312 | return new Promise((resolve, reject) => { 313 | const request = objectStore.index(indexName).openCursor(range, direction); 314 | const records2 = []; 315 | request.onerror = function() { 316 | reject(new Error(request)); 317 | }; 318 | request.onsuccess = function() { 319 | const cursor = request.result; 320 | if (cursor) { 321 | if (singleIndex && !notIndexFilter && !sorter) { 322 | if (skip > 0) { 323 | cursor.advance(skip); 324 | skip = 0; 325 | } else { 326 | records2.push(cursor.value); 327 | if (records2.length === limit) { 328 | resolve(records2); 329 | } else { 330 | cursor.continue(); 331 | } 332 | } 333 | } else { 334 | if (filter(cursor.value)) { 335 | records2.push(cursor.value); 336 | } 337 | if (singleIndex && !sorter && records2.length === skip + limit) { 338 | resolve(records2); 339 | } else { 340 | cursor.continue(); 341 | } 342 | } 343 | } else { 344 | resolve(records2); 345 | } 346 | }; 347 | }); 348 | } 349 | })); 350 | records.push(...ret2.flat()); 351 | } 352 | if (singleIndex) { 353 | if (sorter) 354 | records.sort(sorter); 355 | return records.slice(skip, skip + limit); 356 | } 357 | const ret = []; 358 | const ids = /* @__PURE__ */ new Set(); 359 | for (let i = 0; i < records.length; i++) { 360 | const record = records[i]; 361 | if (!record || ids.has(record._id) || !filter(record)) 362 | continue; 363 | ids.add(record._id); 364 | ret.push(record); 365 | if (!sorter && (skip > 0 || Number.isFinite(limit)) && ret.length >= skip + limit) { 366 | return ret.slice(skip, skip + limit); 367 | } 368 | } 369 | if (sorter) 370 | ret.sort(sorter); 371 | if (skip > 0 || Number.isFinite(limit)) { 372 | return ret.slice(skip, skip + limit); 373 | } 374 | return ret; 375 | } else { 376 | if (rawSorter) { 377 | const keys = Object.keys(rawSorter); 378 | if (keys.length === 1) { 379 | const key = keys[0]; 380 | const order = rawSorter[key]; 381 | if (table.indexes[key] != null) { 382 | let direction = "next"; 383 | if (order === -1 || order === "desc") 384 | direction = "prev"; 385 | const records2 = await new Promise((resolve, reject) => { 386 | const request = objectStore.index(key).openCursor(null, direction); 387 | const records3 = []; 388 | request.onerror = function() { 389 | reject(new Error(request)); 390 | }; 391 | request.onsuccess = function() { 392 | const cursor = request.result; 393 | if (cursor) { 394 | if (!notIndexFilter) { 395 | if (skip > 0) { 396 | cursor.advance(skip); 397 | skip = 0; 398 | } else { 399 | records3.push(cursor.value); 400 | if (records3.length === limit) { 401 | resolve(records3); 402 | } else { 403 | cursor.continue(); 404 | } 405 | } 406 | } else { 407 | if (filter(cursor.value)) { 408 | records3.push(cursor.value); 409 | } 410 | if (records3.length === skip + limit) { 411 | resolve(records3); 412 | } else { 413 | cursor.continue(); 414 | } 415 | } 416 | } else { 417 | resolve(records3); 418 | } 419 | }; 420 | }); 421 | return records2.slice(skip, skip + limit); 422 | } 423 | } 424 | } 425 | const records = await new Promise((resolve, reject) => { 426 | const request = objectStore.index("createdAt").getAll(); 427 | request.onerror = function() { 428 | reject(new Error(request)); 429 | }; 430 | request.onsuccess = function() { 431 | resolve(request.result); 432 | }; 433 | }); 434 | let filtedRecords; 435 | if (!sorter && skip === 0 && limit === 1) { 436 | filtedRecords = records.find(filter); 437 | if (filtedRecords) 438 | return [filtedRecords]; 439 | return []; 440 | } else { 441 | filtedRecords = records.filter(filter); 442 | } 443 | if (sorter) 444 | filtedRecords.sort(sorter); 445 | if (skip > 0 || Number.isFinite(limit)) { 446 | filtedRecords = filtedRecords.slice(skip, skip + limit); 447 | } 448 | return filtedRecords; 449 | } 450 | } 451 | var dbInstances, _filter3, _notIndexFilter2, version; 452 | var init_browser = __esm({ 453 | "lib/platform/browser/index.js"() { 454 | init_storage(); 455 | dbInstances = {}; 456 | _filter3 = Symbol.for("okeydb-filter"); 457 | _notIndexFilter2 = Symbol.for("not-index-filter"); 458 | version = 0; 459 | } 460 | }); 461 | 462 | // lib/utils.js 463 | var _filter = Symbol.for("okeydb-filter"); 464 | function parseCondition(condition = {}) { 465 | if (typeof condition === "function") 466 | return condition; 467 | if (condition[_filter]) 468 | return condition[_filter]; 469 | const filters = []; 470 | for (const [k, v] of Object.entries(condition)) { 471 | if (typeof v === "function") { 472 | filters.push((d) => v(d[k], k, d)); 473 | } else if (v && typeof v[_filter] === "function") { 474 | const f = v[_filter]; 475 | filters.push((d) => f(d[k], k, d)); 476 | } else if (v instanceof RegExp) { 477 | filters.push((d) => d[k] && typeof d[k].match === "function" && d[k].match(v) != null); 478 | } else { 479 | filters.push((d) => d[k] === v); 480 | } 481 | } 482 | return (record) => filters.every((f) => f(record)); 483 | } 484 | function mergeConditions(conditions, type = "and") { 485 | const filters = []; 486 | for (let i = 0; i < conditions.length; i++) { 487 | filters.push(parseCondition(conditions[i])); 488 | } 489 | if (type === "and") { 490 | return (record) => filters.every((f) => f(record)); 491 | } else if (type === "or") { 492 | return (record) => filters.some((f) => f(record)); 493 | } else if (type === "nor") { 494 | return (record) => !filters.some((f) => f(record)); 495 | } 496 | } 497 | function getType(value) { 498 | let type = typeof value; 499 | if (type === "object" && Array.isArray(value)) { 500 | type = "array"; 501 | } else if (type === "object" && value instanceof Date) { 502 | type = "date"; 503 | } else if (type === "object" && value instanceof RegExp) { 504 | type = "regexp"; 505 | } else if (value == null) { 506 | type = "null"; 507 | } 508 | return type; 509 | } 510 | 511 | // lib/query.js 512 | var _notIndexFilter = Symbol.for("not-index-filter"); 513 | function updateFilterIndex(query, conditions, filterIndexes = {}, phase = "and") { 514 | const indexes = query.table.indexes; 515 | let notIndexFilter = false; 516 | for (let i = 0; i < conditions.length; i++) { 517 | const condition = conditions[i]; 518 | let hasIndex = false; 519 | for (const [k, v] of Object.entries(condition)) { 520 | if (k in indexes) { 521 | hasIndex = true; 522 | filterIndexes[k] = filterIndexes[k] || /* @__PURE__ */ new Set(); 523 | filterIndexes[k].add(v); 524 | if (phase === "and" && filterIndexes[k].size > 1) 525 | filterIndexes[k].clear(); 526 | } else { 527 | notIndexFilter = true; 528 | } 529 | } 530 | if (!hasIndex && phase === "or") { 531 | query.table[_notIndexFilter] = notIndexFilter; 532 | return null; 533 | } 534 | } 535 | query.table[_notIndexFilter] = notIndexFilter; 536 | return filterIndexes; 537 | } 538 | var _filter2 = Symbol.for("okeydb-filter"); 539 | var query_default = class { 540 | #table; 541 | #filter; 542 | #records; 543 | #sorter = null; 544 | #rawSorter; 545 | #skip = 0; 546 | #limit = Infinity; 547 | #projection = null; 548 | #updateFields = null; 549 | #insertFields = {}; 550 | #setOnInsertFields = null; 551 | #upsert = false; 552 | #filterIndexes = {}; 553 | constructor(condition, table) { 554 | this.#table = table; 555 | if (condition) { 556 | this.#filter = mergeConditions([condition]); 557 | this.#insertFields = { ...condition }; 558 | this.#filterIndexes = updateFilterIndex(this, [condition], {}, "and"); 559 | } 560 | } 561 | and(...conditions) { 562 | const left = this.#filter; 563 | const right = mergeConditions(conditions); 564 | if (left) { 565 | this.#filter = (record) => left(record) && right(record); 566 | } else { 567 | this.#filter = right; 568 | } 569 | for (let i = 0; i < conditions.length; i++) { 570 | Object.assign(this.#insertFields, conditions[i]); 571 | } 572 | if (this.#filterIndexes) 573 | this.#filterIndexes = updateFilterIndex(this, conditions, this.#filterIndexes, "and"); 574 | return this; 575 | } 576 | or(...conditions) { 577 | const left = this.#filter; 578 | const right = mergeConditions(conditions, "or"); 579 | if (left) { 580 | this.#filter = (record) => left(record) || right(record); 581 | } else { 582 | this.#filter = right; 583 | } 584 | this.#insertFields = {}; 585 | if (this.#filterIndexes) 586 | this.#filterIndexes = updateFilterIndex(this, conditions, this.#filterIndexes, "or"); 587 | return this; 588 | } 589 | nor(...conditions) { 590 | const left = this.#filter; 591 | const right = mergeConditions(conditions, "or"); 592 | if (left) { 593 | this.#filter = (record) => !(left(record) || right(record)); 594 | } else { 595 | this.#filter = (record) => !right(record); 596 | } 597 | this.#insertFields = {}; 598 | this.#filterIndexes = null; 599 | this.table[_notIndexFilter] = true; 600 | return this; 601 | } 602 | async find() { 603 | let filtedRecords = await this.#table.getRecords({ 604 | filter: this.#filter || function() { 605 | return true; 606 | }, 607 | sorter: this.#sorter, 608 | rawSorter: this.#rawSorter, 609 | skip: this.#skip, 610 | limit: this.#limit, 611 | filterIndexes: this.filterIndexes 612 | }); 613 | if (this.#projection) { 614 | const { type, fields } = this.#projection; 615 | if (type === "inclusion") { 616 | filtedRecords = filtedRecords.map((r) => { 617 | const ret = {}; 618 | fields.forEach((f) => ret[f] = r[f]); 619 | ret._id = r._id; 620 | return ret; 621 | }); 622 | } else if (type === "exclusion") { 623 | filtedRecords = filtedRecords.map((r) => { 624 | const ret = { ...r }; 625 | fields.forEach((f) => delete ret[f]); 626 | return ret; 627 | }); 628 | } 629 | } 630 | this.#records = filtedRecords; 631 | return filtedRecords; 632 | } 633 | async findOne() { 634 | const records = await this.#table.getRecords({ 635 | filter: this.#filter || function() { 636 | return true; 637 | }, 638 | sorter: this.#sorter, 639 | rawSorter: this.#rawSorter, 640 | skip: this.#skip, 641 | limit: 1, 642 | filterIndexes: this.filterIndexes 643 | }); 644 | const record = records[0]; 645 | if (this.#projection) { 646 | const { type, fields } = this.#projection; 647 | const ret = {}; 648 | if (type === "inclusion") { 649 | fields.forEach((f) => ret[f] = record[f]); 650 | } else if (type === "exclusion") { 651 | Object.assign(ret, record); 652 | fields.forEach((f) => delete ret[f]); 653 | } 654 | return ret; 655 | } 656 | return record; 657 | } 658 | async count() { 659 | if (this.#records) 660 | return this.#records.length; 661 | return await this.find().length; 662 | } 663 | set(fields) { 664 | this.#updateFields = fields; 665 | return this; 666 | } 667 | setOnInsert(fields) { 668 | this.#setOnInsertFields = fields; 669 | return this; 670 | } 671 | upsert(flag) { 672 | this.#upsert = flag; 673 | return this; 674 | } 675 | async save() { 676 | if (this.#updateFields || this.#upsert) { 677 | let records = this.#records; 678 | if (!records) 679 | records = await this.find(); 680 | if (records.length <= 0 && this.#upsert) { 681 | records = Object.assign({}, this.#insertFields, this.#setOnInsertFields); 682 | for (let [k, v] of Object.entries(records)) { 683 | if (v && typeof v[_filter2] === "function") { 684 | v = v[_filter2]; 685 | } 686 | if (typeof v === "function") { 687 | records[k] = v(records[k], k, records); 688 | } 689 | } 690 | if (this.#updateFields) { 691 | const updateFields = this.#updateFields; 692 | for (let [k, v] of Object.entries(updateFields)) { 693 | if (v && typeof v[_filter2] === "function") { 694 | v = v[_filter2]; 695 | } 696 | if (typeof v !== "function") { 697 | records[k] = v; 698 | } else { 699 | records[k] = v(records[k], k, records); 700 | if (records[k] === void 0) 701 | delete records[k]; 702 | } 703 | } 704 | } 705 | } else if (this.#updateFields) { 706 | const updateFields = this.#updateFields; 707 | records = records.map((record) => { 708 | const ret = { ...record }; 709 | for (let [k, v] of Object.entries(updateFields)) { 710 | if (v && typeof v[_filter2] === "function") { 711 | v = v[_filter2]; 712 | } 713 | if (typeof v !== "function") { 714 | ret[k] = v; 715 | } else { 716 | ret[k] = v(ret[k], k, ret); 717 | if (ret[k] === void 0) 718 | delete ret[k]; 719 | } 720 | } 721 | return ret; 722 | }); 723 | } else { 724 | return await this.#table.save([], true); 725 | } 726 | return await this.#table.save(records, true); 727 | } 728 | throw new Error("Must use set or upsert at least once"); 729 | } 730 | async delete() { 731 | let records = this.#records; 732 | if (!records) 733 | records = await this.find(); 734 | return await this.#table.delete(records); 735 | } 736 | sort(conditions) { 737 | const conds = Object.entries(conditions); 738 | this.#rawSorter = conditions; 739 | this.#sorter = (a, b) => { 740 | for (let [k, v] of conds) { 741 | if (typeof v === "string") { 742 | if (v.toLowerCase() === "asc") { 743 | v = 1; 744 | } else if (v.toLowerCase() === "desc") { 745 | v = -1; 746 | } 747 | } 748 | if (v !== 1 && v !== -1) 749 | throw new Error(`Invalid sort condition: ${k} ${v}`); 750 | if (a[k] != b[k]) { 751 | return a[k] > b[k] ? v * 1 : v * -1; 752 | } 753 | } 754 | return 0; 755 | }; 756 | return this; 757 | } 758 | skip(n) { 759 | this.#skip = n; 760 | return this; 761 | } 762 | limit(n) { 763 | this.#limit = n; 764 | return this; 765 | } 766 | projection(conditions) { 767 | let type = null; 768 | const fields = []; 769 | for (const [k, v] of Object.entries(conditions)) { 770 | if (!type && v === 1) 771 | type = "inclusion"; 772 | else if (!type && v === 0) 773 | type = "exclusion"; 774 | else if (type === "inclusion" && v === 0 || type === "exclusion" && v === 1) 775 | throw new Error("Projection cannot have a mix of inclusion and exclusion."); 776 | fields.push(k); 777 | } 778 | this.#projection = { type, fields }; 779 | return this; 780 | } 781 | get table() { 782 | return this.#table; 783 | } 784 | get filterIndexes() { 785 | const filterIndexes = this.#filterIndexes || {}; 786 | if (Object.keys(filterIndexes).length) 787 | return filterIndexes; 788 | return null; 789 | } 790 | }; 791 | 792 | // node_modules/uuid/dist/esm-browser/rng.js 793 | var getRandomValues; 794 | var rnds8 = new Uint8Array(16); 795 | function rng() { 796 | if (!getRandomValues) { 797 | getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto); 798 | if (!getRandomValues) { 799 | throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported"); 800 | } 801 | } 802 | return getRandomValues(rnds8); 803 | } 804 | 805 | // node_modules/uuid/dist/esm-browser/stringify.js 806 | var byteToHex = []; 807 | for (let i = 0; i < 256; ++i) { 808 | byteToHex.push((i + 256).toString(16).slice(1)); 809 | } 810 | function unsafeStringify(arr, offset = 0) { 811 | return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); 812 | } 813 | 814 | // node_modules/uuid/dist/esm-browser/native.js 815 | var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto); 816 | var native_default = { 817 | randomUUID 818 | }; 819 | 820 | // node_modules/uuid/dist/esm-browser/v4.js 821 | function v4(options, buf, offset) { 822 | if (native_default.randomUUID && !buf && !options) { 823 | return native_default.randomUUID(); 824 | } 825 | options = options || {}; 826 | const rnds = options.random || (options.rng || rng)(); 827 | rnds[6] = rnds[6] & 15 | 64; 828 | rnds[8] = rnds[8] & 63 | 128; 829 | if (buf) { 830 | offset = offset || 0; 831 | for (let i = 0; i < 16; ++i) { 832 | buf[offset + i] = rnds[i]; 833 | } 834 | return buf; 835 | } 836 | return unsafeStringify(rnds); 837 | } 838 | var v4_default = v4; 839 | 840 | // lib/table.js 841 | var Table = (() => { 842 | let platform; 843 | if (true) { 844 | platform = Promise.resolve().then(() => (init_browser(), browser_exports)); 845 | } else { 846 | platform = null; 847 | } 848 | RegExp.prototype.toJSON = function() { 849 | return { type: "RegExp", source: this.source, flags: this.flags }; 850 | }; 851 | return class { 852 | #name; 853 | #db; 854 | #ready; 855 | #indexes; 856 | constructor(name, { root = ".db", meta = ".meta", database, indexes } = {}) { 857 | if (name.startsWith(".")) { 858 | throw new TypeError("The table name cannot starts with '.'."); 859 | } 860 | this.#name = name; 861 | this.#db = database; 862 | this.#indexes = { 863 | _id: true, 864 | // indent 865 | createdAt: false, 866 | updatedAt: false, 867 | ...indexes 868 | }; 869 | this.#ready = platform.then(({ createTable: createTable2 }) => { 870 | return createTable2(this, root, meta); 871 | }).then((res) => { 872 | this._storage = res; 873 | }); 874 | } 875 | get indexes() { 876 | return this.#indexes; 877 | } 878 | get database() { 879 | return this.#db; 880 | } 881 | get name() { 882 | return this.#name; 883 | } 884 | async getRecords({ filter, sorter, skip, limit, filterIndexes, rawSorter } = {}) { 885 | await this.#ready; 886 | const { getRecords: getRecords2 } = await platform; 887 | return getRecords2(this, { filter, sorter, skip, limit, filterIndexes, rawSorter }); 888 | } 889 | async save(records = [], countResult = false) { 890 | await this.#ready; 891 | const originalRecords = records; 892 | if (!Array.isArray(records)) { 893 | records = [records]; 894 | } 895 | const { flushData: flushData2 } = await platform; 896 | await flushData2(this); 897 | const insertRecords = []; 898 | const datetime = /* @__PURE__ */ new Date(); 899 | for (let i = 0; i < records.length; i++) { 900 | const record = records[i]; 901 | record.createdAt = record.createdAt || datetime; 902 | record.updatedAt = datetime; 903 | if (record._id != null) { 904 | const idx = this._storage.getItemIndex(record._id); 905 | if (idx >= 0) { 906 | await this._storage.put(idx, record); 907 | } 908 | } else { 909 | record._id = record._id || v4_default(); 910 | insertRecords.push(record); 911 | } 912 | } 913 | const upsertedCount = insertRecords.length; 914 | const modifiedCount = records.length - upsertedCount; 915 | await this._storage.add(insertRecords); 916 | const { fileSync: fileSync2 } = await platform; 917 | await fileSync2(this); 918 | if (countResult) 919 | return { modifiedCount, upsertedCount }; 920 | return originalRecords; 921 | } 922 | async delete(records = []) { 923 | await this.#ready; 924 | if (!Array.isArray(records)) 925 | records = [records]; 926 | const { flushData: flushData2 } = await platform; 927 | await flushData2(this); 928 | let deletedCount = 0; 929 | const filterMap = {}; 930 | for (let i = 0; i < records.length; i++) { 931 | const record = records[i]; 932 | const idx = this._storage.getItemIndex(record._id); 933 | if (idx >= 0) 934 | deletedCount++; 935 | filterMap[idx] = true; 936 | } 937 | await this._storage.delete(filterMap); 938 | const { fileSync: fileSync2 } = await platform; 939 | await fileSync2(this); 940 | return { deletedCount }; 941 | } 942 | where(condition = null) { 943 | const query = new query_default(condition, this); 944 | return query; 945 | } 946 | }; 947 | })(); 948 | var table_default = Table; 949 | 950 | // lib/operator.js 951 | var _filter4 = Symbol.for("okeydb-filter"); 952 | var Operator = class _Operator { 953 | constructor(filter, prev) { 954 | if (filter && prev) { 955 | this[_filter4] = this.and(prev, filter)[_filter4]; 956 | } else if (filter) { 957 | this[_filter4] = filter; 958 | } 959 | } 960 | gt(value) { 961 | const fn = (d) => d > value; 962 | const ret = new _Operator(fn, this[_filter4]); 963 | if (!this[_filter4]) { 964 | ret._type = "gt"; 965 | ret._value = value; 966 | } else if (this._type === "lt" || this._type === "lte") { 967 | ret._type = `gt${this._type}`; 968 | ret._value = [value, this._value]; 969 | } 970 | return ret; 971 | } 972 | greaterThan(value) { 973 | return this.gt(value); 974 | } 975 | gte(value) { 976 | const fn = (d) => d >= value; 977 | const ret = new _Operator(fn, this[_filter4]); 978 | if (!this[_filter4]) { 979 | ret._type = "gte"; 980 | ret._value = value; 981 | } else if (this._type === "lt" || this._type === "lte") { 982 | ret._type = `gte${this._type}`; 983 | ret._value = [value, this._value]; 984 | } 985 | return ret; 986 | } 987 | greaterThanOrEqual(value) { 988 | return this.gte(value); 989 | } 990 | lt(value) { 991 | const fn = (d) => d < value; 992 | const ret = new _Operator(fn, this[_filter4]); 993 | if (!this[_filter4]) { 994 | ret._type = "lt"; 995 | ret._value = value; 996 | } else if (this._type === "gt" || this._type === "gte") { 997 | ret._type = `${this._type}lt`; 998 | ret._value = [this._value, value]; 999 | } 1000 | return ret; 1001 | } 1002 | lessThan(value) { 1003 | return this.lt(value); 1004 | } 1005 | lte(value) { 1006 | const fn = (d) => d <= value; 1007 | const ret = new _Operator(fn, this[_filter4]); 1008 | if (!this[_filter4]) { 1009 | ret._type = "lte"; 1010 | ret._value = value; 1011 | } else if (this._type === "gt" || this._type === "gte") { 1012 | ret._type = `${this._type}lte`; 1013 | ret._value = [this._value, value]; 1014 | } 1015 | return ret; 1016 | } 1017 | lessThanOrEqual(value) { 1018 | return this.lte(value); 1019 | } 1020 | eq(value) { 1021 | return new _Operator((d) => d == value, this[_filter4]); 1022 | } 1023 | equal(value) { 1024 | return new _Operator((d) => d == value, this[_filter4]); 1025 | } 1026 | ne(value) { 1027 | return new _Operator((d) => d != value, this[_filter4]); 1028 | } 1029 | notEqual(value) { 1030 | return new _Operator((d) => d != value, this[_filter4]); 1031 | } 1032 | mod(divisor, remainder) { 1033 | return new _Operator((d) => d % divisor === remainder, this[_filter4]); 1034 | } 1035 | in(list) { 1036 | return new _Operator((d) => { 1037 | if (Array.isArray(d)) { 1038 | return d.some((item) => list.includes(item)); 1039 | } 1040 | return list.includes(d); 1041 | }, this[_filter4]); 1042 | } 1043 | nin(list) { 1044 | return new _Operator((d) => { 1045 | if (Array.isArray(d)) { 1046 | return !d.some((item) => list.includes(item)); 1047 | } 1048 | return !list.includes(d); 1049 | }, this[_filter4]); 1050 | } 1051 | all(list) { 1052 | return new _Operator((d) => { 1053 | if (Array.isArray(d)) { 1054 | return d.every((item) => list.includes(item)); 1055 | } 1056 | }, this[_filter4]); 1057 | } 1058 | size(len) { 1059 | return new _Operator((d) => { 1060 | if (Array.isArray(d)) { 1061 | return d.length === len; 1062 | } 1063 | }, this[_filter4]); 1064 | } 1065 | bitsAllClear(positions) { 1066 | return new _Operator((d) => { 1067 | if (typeof d === "number") { 1068 | let mask = 0; 1069 | positions.forEach((p) => mask |= 1 << p); 1070 | return (d & mask) === 0; 1071 | } 1072 | }, this[_filter4]); 1073 | } 1074 | bitsAnyClear(positions) { 1075 | return new _Operator((d) => { 1076 | if (typeof d === "number") { 1077 | let mask = 0; 1078 | positions.forEach((p) => mask |= 1 << p); 1079 | return (d & mask) < mask; 1080 | } 1081 | }, this[_filter4]); 1082 | } 1083 | bitsAllSet(positions) { 1084 | return new _Operator((d) => { 1085 | if (typeof d === "number") { 1086 | let mask = 0; 1087 | positions.forEach((p) => mask |= 1 << p); 1088 | return (d & mask) === mask; 1089 | } 1090 | }, this[_filter4]); 1091 | } 1092 | bitsAnySet(positions) { 1093 | return new _Operator((d) => { 1094 | if (typeof d === "number") { 1095 | let mask = 0; 1096 | positions.forEach((p) => mask |= 1 << p); 1097 | return (d & mask) > 0; 1098 | } 1099 | }, this[_filter4]); 1100 | } 1101 | elemMatch(conditions) { 1102 | if (conditions instanceof _Operator) { 1103 | conditions = conditions[_filter4]; 1104 | } 1105 | const filter = typeof conditions === "function" ? conditions : mergeConditions(conditions); 1106 | return new _Operator((d) => { 1107 | if (Array.isArray(d)) { 1108 | return d.some((item) => filter(item)); 1109 | } 1110 | }, this[_filter4]); 1111 | } 1112 | exists(flag) { 1113 | return new _Operator((d, k, o) => k in o == flag, this[_filter4]); 1114 | } 1115 | type(t) { 1116 | return new _Operator((d, k, o) => k in o && getType(d) === t, this[_filter4]); 1117 | } 1118 | not(condition) { 1119 | if (condition instanceof _Operator) { 1120 | condition = condition[_filter4]; 1121 | } else if (typeof condition !== "function") { 1122 | condition = (d) => d === condition; 1123 | } 1124 | return new _Operator((d, k, o) => !condition(d, k, o), this[_filter4]); 1125 | } 1126 | and(...conditions) { 1127 | return new _Operator(mergeConditions(conditions, "and"), this[_filter4]); 1128 | } 1129 | or(...conditions) { 1130 | return new _Operator(mergeConditions(conditions, "or"), this[_filter4]); 1131 | } 1132 | nor(...conditions) { 1133 | return new _Operator(mergeConditions(conditions, "nor"), this[_filter4]); 1134 | } 1135 | inc(value) { 1136 | const filter = this[_filter4]; 1137 | return new _Operator((d) => { 1138 | if (filter) 1139 | d = filter(d); 1140 | if (typeof d !== "number") { 1141 | throw new Error("Cannot apply $inc to a value of non-numeric type."); 1142 | } 1143 | return d + value; 1144 | }); 1145 | } 1146 | mul(value) { 1147 | const filter = this[_filter4]; 1148 | return new _Operator((d) => { 1149 | if (filter) 1150 | d = filter(d); 1151 | if (typeof d !== "number") { 1152 | throw new Error("Cannot apply $inc to a value of non-numeric type."); 1153 | } 1154 | return d * value; 1155 | }); 1156 | } 1157 | min(value) { 1158 | const filter = this[_filter4]; 1159 | return new _Operator((d) => { 1160 | if (filter) 1161 | d = filter(d); 1162 | if (typeof d !== "number") { 1163 | throw new Error("Cannot apply $inc to a value of non-numeric type."); 1164 | } 1165 | return Math.min(d, value); 1166 | }); 1167 | } 1168 | max(value) { 1169 | const filter = this[_filter4]; 1170 | return new _Operator((d) => { 1171 | if (filter) 1172 | d = filter(d); 1173 | if (typeof d !== "number") { 1174 | throw new Error("Cannot apply $inc to a value of non-numeric type."); 1175 | } 1176 | return Math.max(d, value); 1177 | }); 1178 | } 1179 | rename(newKey) { 1180 | return new _Operator((d, k, o) => { 1181 | if (newKey !== k) { 1182 | o[newKey] = o[k]; 1183 | } 1184 | return; 1185 | }); 1186 | } 1187 | unset() { 1188 | return new _Operator(() => { 1189 | return; 1190 | }); 1191 | } 1192 | currentDate() { 1193 | return new _Operator(() => /* @__PURE__ */ new Date()); 1194 | } 1195 | regex(value) { 1196 | return new _Operator((d) => { 1197 | const exp = new RegExp(value); 1198 | return d.match(exp) != null; 1199 | }); 1200 | } 1201 | }; 1202 | 1203 | // lib/db.js 1204 | var db_default = class extends Operator { 1205 | #root; 1206 | #meta; 1207 | #name; 1208 | #version; 1209 | #tables = {}; 1210 | constructor({ root = ".db", meta = ".meta", name = "okeydb", version: version2 = 1 } = {}) { 1211 | super(); 1212 | this.#root = root; 1213 | this.#meta = meta; 1214 | this.#name = name; 1215 | this.#version = version2; 1216 | } 1217 | get name() { 1218 | return this.#name; 1219 | } 1220 | get version() { 1221 | return this.#version; 1222 | } 1223 | close() { 1224 | if (this.instance) 1225 | this.instance.close(); 1226 | } 1227 | table(name, { indexes } = {}) { 1228 | if (!this.#tables[name]) 1229 | this.#tables[name] = new table_default(name, { root: this.#root, meta: this.#meta, database: this, indexes }); 1230 | return this.#tables[name]; 1231 | } 1232 | }; 1233 | 1234 | // index.js 1235 | var airdb_lite_default = db_default; 1236 | export { 1237 | db_default as OkeyDB, 1238 | airdb_lite_default as default 1239 | }; 1240 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 83 | 84 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import OkeyDB from '../index.js'; 2 | 3 | // import {sjs, attr} from 'slow-json-stringify'; 4 | 5 | const db = new OkeyDB(); 6 | const personTable = db.table('bigtable'); 7 | 8 | console.time('delete0'); 9 | const res = await personTable.where().delete(); 10 | console.timeEnd('delete0'); 11 | console.log(res); 12 | 13 | const persons = []; 14 | for(let i = 0; i < 500000; i++) { 15 | persons.push({name: `student${i}`, score: 30 + Math.floor(Math.random() * 70)}); 16 | } 17 | 18 | console.time('save0'); 19 | await personTable.save(persons); 20 | console.timeEnd('save0'); 21 | 22 | console.time('find0'); 23 | console.log((await personTable.where().find()).length); 24 | console.timeEnd('find0'); 25 | 26 | console.time('save1'); 27 | personTable.save({name: 'akira', score: 111}); 28 | personTable.save({name: 'akira2', score: 222}); 29 | console.timeEnd('save1'); 30 | 31 | // console.time('save1'); 32 | // await personTable.save({name: 'akira', score: 111}); 33 | // console.timeEnd('save1'); 34 | // console.log((await personTable.where().find()).length); 35 | 36 | 37 | // const result = await personTable.where({}).sort({score: -1}).projection({_id: 0}).find(); 38 | // console.table(result); 39 | 40 | // function randomScore() { 41 | // return Math.floor(100 * Math.random()); 42 | // } 43 | 44 | // const bigTable = db.table('bigtable'); 45 | 46 | // const students = []; 47 | // for(let i = 0; i < 20000; i++) { 48 | // students.push({ 49 | // name: `student${i}`, 50 | // score: randomScore(), 51 | // createdAt: new Date(), 52 | // updatedAt: new Date(), 53 | // abc: /a/g, 54 | // }); 55 | // } 56 | 57 | // RegExp.prototype.toJSON = function() { 58 | // return {type: 'RegExp', source: this.source, flags: this.flags}; 59 | // }; 60 | 61 | // const stringify = sjs({ 62 | // students: attr('array', sjs({ 63 | // name: attr('string'), 64 | // score: attr('number'), 65 | // createdAt: attr('string', (value) => value.toISOString()), 66 | // updatedAt: attr('string', (value) => value.toISOString()), 67 | // abc: attr('string'), 68 | // })) 69 | // }); 70 | 71 | // console.time('stringify'); 72 | // JSON.stringify({students}); 73 | 74 | // // const r = stringify({students}); 75 | 76 | // console.timeEnd('stringify'); 77 | 78 | // // console.log(r); 79 | 80 | // // await bigTable.save(students); 81 | 82 | // // const res = await bigTable.where().find(); 83 | // // console.log(res.length); 84 | 85 | 86 | // // // await bigTable.where().delete(); 87 | 88 | // // const result = await bigTable.where({score: db.gt(60)}).find(); 89 | // // console.log(result.length); -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | import OkeyDB from '../index.js'; 2 | 3 | const db = new OkeyDB(); 4 | const personTable = db.table('person'); 5 | 6 | test('base set find delete', async () => { 7 | let res = await personTable.save({name: 'akira', score: 100}); 8 | expect(res.name).toBe('akira'); 9 | expect(res.score).toBe(100); 10 | 11 | res = await personTable.where({name: 'foo'}).find(); 12 | expect(Array.isArray(res)).toBeTruthy(); 13 | expect(res.length).toBe(0); 14 | 15 | res = await personTable.where({name: 'akira'}).findOne(); 16 | expect(res.score).toBe(100); 17 | 18 | res = await personTable.delete(res); 19 | 20 | expect(res.deletedCount).toBe(1); 21 | }); 22 | 23 | // test('create table', async () => { 24 | // const myTable = db.table('mytable'); 25 | 26 | // expect(existsSync(myTable.filepath)).toBeTruthy(); 27 | // }); 28 | 29 | test('filter indexes', async () => { 30 | // const filterIndexes = personTable.where({_id: 'akira'}).and({_id: 'foo'}).or({_id: '1'}).filterIndexes; 31 | // console.log(filterIndexes); 32 | }); 33 | 34 | test('clear table', async () => { 35 | 36 | }); 37 | 38 | test('regexp match', async () => { 39 | await personTable.save({name: 'akira', score: 100}); 40 | await personTable.save({name: 'aka', score: 90}); 41 | await personTable.save({name: 'bob', score: 80}); 42 | 43 | let res = await personTable.where({name: /^a/}).find(); 44 | expect(res.length).toBe(2); 45 | 46 | res = await personTable.where().delete(); 47 | }); 48 | 49 | afterAll(async () => { 50 | await personTable.where().delete(); 51 | }); 52 | 53 | // test('benchmark', async () => { 54 | // function randomScore() { 55 | // return Math.floor(100 * Math.random()); 56 | // } 57 | 58 | // const bigTable = db.table('bigtable'); 59 | // for(let i = 0; i < 100000; i++) { 60 | // await bigTable.save({name: `student${i}`, score: randomScore()}); 61 | // } 62 | 63 | // await bigTable.where().delete(); 64 | // }); -------------------------------------------------------------------------------- /test/operator.test.js: -------------------------------------------------------------------------------- 1 | function* pollItem(array) { 2 | let current = 0; 3 | while(true) { 4 | yield array[current++ % array.length]; 5 | } 6 | } 7 | 8 | import OkeyDB from '../index.js'; 9 | 10 | const db = new OkeyDB(); 11 | const table = db.table('employees'); 12 | 13 | afterAll(async () => { 14 | await db.close(); 15 | }); 16 | 17 | describe('Where Operator', () => { 18 | beforeAll(async () => { 19 | await table.where().delete(); 20 | const genTeam = pollItem(['juejin', 'segmentfault', 'aircode']); 21 | const genPosition = pollItem(['developer', 'designer', 'manager', 'intern']); 22 | 23 | const employees = []; 24 | for(let i = 0; i < 10; i++) { 25 | employees.push({ 26 | id: i, 27 | team: genTeam.next().value, 28 | position: genPosition.next().value, 29 | age: 20 + i * 2, 30 | avaliablePositions: ['developer', 'designer', 'manager', 'intern'], 31 | }); 32 | } 33 | await table.save(employees); 34 | }); 35 | 36 | it('Find many developer', async () => { 37 | let result = await table.where({position: 'developer'}).find(); 38 | expect(result.length).toBe(3); 39 | result = await table.where({position: 'manager'}).find(); 40 | expect(result.length).toBe(2); 41 | }); 42 | 43 | it('Where.and', async () => { 44 | let result = await table.where({position: 'developer'}).and({team: 'segmentfault'}).find(); 45 | expect(result.length).toBe(1); 46 | expect(result[0].id).toBe(4); 47 | result = await table.where({position: 'developer'}).and({team: 'segmentfault'}).and({team: 'juejin'}).find(); 48 | expect(result.length).toBe(0); 49 | result = await table.where({position: 'developer'}).and({team: 'segmentfault'}, {team: 'juejin'}).find(); 50 | expect(result.length).toBe(0); 51 | }); 52 | 53 | it('Where.or', async () => { 54 | let result = await table.where({position: 'developer'}).or({team: 'segmentfault'}).find(); 55 | expect(result.length).toBe(5); 56 | expect(result[0].id).toBe(0); 57 | result = await table.where({position: 'developer'}).or({team: 'segmentfault'}).or({team: 'juejin'}).find(); 58 | expect(result.length).toBe(8); 59 | result = await table.where({position: 'developer'}).or({team: 'segmentfault'}, {team: 'juejin'}).find(); 60 | expect(result.length).toBe(8); 61 | }); 62 | 63 | it('Where.nor', async () => { 64 | let result = await table.where({team: 'juejin'}).nor({team: 'segmentfault'}).find(); 65 | expect(result.length).toBe(3); 66 | expect(result[0].id).toBe(2); 67 | 68 | result = await table.where().nor({team: 'juejin'}, {team: 'segmentfault'}).find(); 69 | expect(result.length).toBe(3); 70 | expect(result[0].id).toBe(2); 71 | 72 | // // !(!(team === 'juejin) || (team === 'segmentfault')) 73 | // // -> !((team !== 'juejin') || team === 'segmentfault') 74 | result = await table.where().nor({team: 'juejin'}).nor({team: 'segmentfault'}).find(); 75 | expect(result.length).toBe(4); 76 | expect(result.every(r => r.team === 'juejin')).toBeTruthy(); 77 | }); 78 | 79 | it('DB.compare', async () => { 80 | let result = await table.where({age: db.gt(26)}).find(); 81 | expect(result.length).toBe(6); 82 | result = await table.where({age: db.gte(26)}).find(); 83 | expect(result.length).toBe(7); 84 | result = await table.where({age: db.lte(26)}).find(); 85 | expect(result.length).toBe(4); 86 | result = await table.where({age: db.lt(26)}).find(); 87 | expect(result.length).toBe(3); 88 | 89 | result = await table.where({age: db.greaterThan(26)}).find(); 90 | expect(result.length).toBe(6); 91 | result = await table.where({age: db.greaterThanOrEqual(26)}).find(); 92 | expect(result.length).toBe(7); 93 | result = await table.where({age: db.lessThanOrEqual(26)}).find(); 94 | expect(result.length).toBe(4); 95 | result = await table.where({age: db.lessThan(26)}).find(); 96 | expect(result.length).toBe(3); 97 | 98 | result = await table.where({age: db.gt(26).lt(30)}).find(); 99 | expect(result.length).toBe(1); 100 | expect(result[0].age).toBe(28); 101 | }); 102 | 103 | it('DB.in and nin', async () => { 104 | let result = await table.where({team: db.nin(['juejin'])}).find(); 105 | expect(result.length).toBe(6); 106 | expect(result.every(r => r.team !== 'juejin')).toBeTruthy(); 107 | 108 | result = await table.where({team: db.in(['juejin', 'segmentfault'])}).find(); 109 | expect(result.length).toBe(7); 110 | expect(result.every(r => ['juejin', 'segmentfault'].includes(r.team))).toBeTruthy(); 111 | }); 112 | 113 | it('DB.exists', async () => { 114 | let result = await table.where({team: db.exists(true)}).find(); 115 | expect(result.length).toBe(10); 116 | 117 | result = await table.where({team: db.exists(false)}).find(); 118 | expect(result.length).toBe(0); 119 | 120 | result = await table.where({team: db.not(db.exists(true))}).find(); 121 | expect(result.length).toBe(0); 122 | 123 | await table.save({id: 10, team: 'juejin'}); 124 | result = await table.where({position: db.exists(false)}).find(); 125 | expect(result.length).toBe(1); 126 | 127 | await table.where({id: 10}).delete(); 128 | }); 129 | 130 | it('DB.type', async () => { 131 | let result = await table.where({age: db.type('number')}).find(); 132 | expect(result.length).toBe(10); 133 | result = await table.where({team: db.type('regexp')}).find(); 134 | expect(result.length).toBe(0); 135 | 136 | await table.save({id: 10, team: 'juejin', extra: /a/}); 137 | result = await table.where({extra: db.type('regexp')}).find(); 138 | expect(result.length).toBe(1); 139 | 140 | await table.where({id: 10}).delete(); 141 | }); 142 | 143 | it('DB.mode', async () => { 144 | let result = await table.where({age: db.mod(5, 1)}).find(); 145 | expect(result.length).toBe(2); 146 | expect(result[0].age).toBe(26); 147 | expect(result[1].age).toBe(36); 148 | }); 149 | 150 | it('DB.regex', async () => { 151 | let result = await table.where({ team: /^(air|seg)/ }).find(); 152 | expect(result.length).toBe(6); 153 | expect(result.every(r => r.team !== 'juejin')).toBeTruthy(); 154 | 155 | result = await table.where({ team: db.regex('^air') }).find(); 156 | expect(result.length).toBe(3); 157 | 158 | result = await table.where({ team: /^AIR/i }).find(); 159 | expect(result.length).toBe(3); 160 | }); 161 | 162 | it('DB.all', async () => { 163 | let result = await table.where({ avaliablePositions: db.all(['developer', 'designer', 'manager', 'intern']) }).find(); 164 | expect(result.length).toBe(10); 165 | }); 166 | 167 | it('DB.elemMatch', async () => { 168 | let result = await table.where({ avaliablePositions: db.elemMatch(db.eq('developer')) }).find(); 169 | expect(result.length).toBe(10); 170 | }); 171 | 172 | it('DB.size', async () => { 173 | let result = await table.where({ avaliablePositions: db.size(4) }).find(); 174 | expect(result.length).toBe(10); 175 | }); 176 | 177 | it('DB.bits operation', async () => { 178 | await table.save({id: 10, flag: 0b01110}); 179 | let result = await table.where({flag: db.bitsAllSet([1, 2, 3])}).find(); 180 | expect(result.length).toBe(1); 181 | result = await table.where({flag: db.bitsAnySet([0, 1, 3])}).find(); 182 | expect(result.length).toBe(1); 183 | await table.where({id: 10}).set({flag: 0b011000}).save(); 184 | result = await table.where({flag: db.bitsAllClear([0, 2])}).find(); 185 | expect(result.length).toBe(1); 186 | result = await table.where({flag: db.bitsAnyClear([0, 3, 4])}).find(); 187 | expect(result.length).toBe(1); 188 | result = await table.where({flag: db.bitsAnyClear([3, 4])}).find(); 189 | expect(result.length).toBe(0); 190 | await table.where({id: 10}).delete(); 191 | }); 192 | 193 | it('Nest and or', async () => { 194 | let result = await table.where({team: 'juejin'}).and(db.or({position: 'developer'}, {position: 'manager'})).find(); 195 | expect(result.length).toBe(2); 196 | expect(result.every(r => r.team === 'juejin')).toBeTruthy(); 197 | expect(result.every(r => ['developer', 'manager'].includes(r.position))).toBeTruthy(); 198 | result = await table.where({team: 'aircode'}).and(db.or({position: db.ne('developer')}), {age: db.gt(25)}).find(); 199 | expect(result.length).toBe(1); 200 | expect(result[0].team).toBe('aircode'); 201 | expect(result[0].age).toBe(30); 202 | result = await table.where(db.and({team: 'aircode'}, db.or({position: db.ne('developer')}), {age: db.gt(25)})).find(); 203 | expect(result.length).toBe(1); 204 | expect(result[0].team).toBe('aircode'); 205 | expect(result[0].age).toBe(30); 206 | }); 207 | 208 | it('Nest nor', async () => { 209 | let result = await table.where(db.nor({team: 'juejin'}, {position: 'developer'})).find(); 210 | expect(result.length).toBe(4); 211 | expect(result.every(r => r.team !== 'juejin' && r.position !== 'developer')).toBeTruthy(); 212 | }); 213 | 214 | it('in operator', async () => { 215 | const query = table.where({ 216 | team: db.in(['juejin', 'segmentfault']) 217 | }); 218 | const result = await query.find(); 219 | expect(result.length).toBe(7); 220 | }); 221 | 222 | it('Side effect operator', async () => { 223 | await table.save({id: 10, score: 100}); 224 | await table.where({id: 10}).set({score: db.inc(10)}).save(); 225 | let result = await table.where({id: 10}).findOne(); 226 | expect(result.score).toBe(110); 227 | await table.where({id: 10}).set({score: db.mul(2)}).save(); 228 | result = await table.where({id: 10}).findOne(); 229 | expect(result.score).toBe(220); 230 | await table.where({id: 10}).set({score: db.min(200)}).save(); 231 | result = await table.where({id: 10}).findOne(); 232 | expect(result.score).toBe(200); 233 | await table.where({id: 10}).set({score: db.max(100)}).save(); 234 | result = await table.where({id: 10}).findOne(); 235 | expect(result.score).toBe(200); 236 | await table.where({id: 10}).set({score: db.rename('grade')}).save(); 237 | result = await table.where({id: 10}).findOne(); 238 | expect(result.score).toBe(undefined); 239 | expect(result.grade).toBe(200); 240 | await table.where({id: 10}).set({grade: db.unset()}).save(); 241 | result = await table.where({id: 10}).findOne(); 242 | expect(result.grade).toBe(undefined); 243 | await table.where({id: 10}).set({time: db.currentDate()}).save(); 244 | result = await table.where({id: 10}).findOne(); 245 | expect(result.time).toBeInstanceOf(Date); 246 | await table.where({id: 10}).delete(); 247 | }); 248 | 249 | afterAll(async () => { 250 | await table.where().delete(); 251 | }); 252 | }); --------------------------------------------------------------------------------