From 7df5ed685dbbd264fe15c9f97508571738d019ea Mon Sep 17 00:00:00 2001 From: kodemon Date: Sat, 3 Jan 2026 00:44:59 +0100 Subject: [PATCH] refactor: collection setup --- .vscode/settings.json | 8 +- biome.json | 45 ++ deno.json | 19 +- deno.lock | 797 +++++----------------------- package.json | 24 +- src/broadcast.ts | 8 +- src/collection.ts | 189 ++++--- src/databases/indexeddb/cache.ts | 16 +- src/databases/indexeddb/database.ts | 29 +- src/databases/indexeddb/storage.ts | 106 ++-- src/databases/memory/database.ts | 4 +- src/databases/memory/storage.ts | 174 +++--- src/databases/observer/storage.ts | 81 ++- src/databases/registrars.ts | 19 +- src/observe/observe-one.ts | 4 +- src/observe/observe.ts | 34 +- src/observe/store.ts | 42 +- src/primary-key.ts | 8 + src/storage/collections.ts | 33 ++ src/storage/errors.ts | 23 +- src/storage/operators/insert.ts | 44 +- src/storage/operators/remove.ts | 3 - src/storage/operators/update.ts | 10 +- src/storage/storage.ts | 139 +++-- src/types.ts | 19 +- tests/cache.test.ts | 2 +- tests/collection.test.ts | 2 +- tests/hash.test.ts | 2 +- tests/update.test.ts | 404 ++++++++++---- tests/users.mock.ts | 2 +- 30 files changed, 1056 insertions(+), 1234 deletions(-) create mode 100644 biome.json create mode 100644 src/primary-key.ts create mode 100644 src/storage/collections.ts delete mode 100644 src/storage/operators/remove.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 395c084..d4abb67 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,11 +1,11 @@ { + "biome.enabled": true, "deno.enable": true, "deno.lint": false, + "editor.defaultFormatter": "biomejs.biome", "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.fixAll.eslint": "explicit" - }, - "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "source.organizeImports.biome": "explicit", + "source.fixAll.biome": "explicit" } } diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..ec51055 --- /dev/null +++ b/biome.json @@ -0,0 +1,45 @@ +{ + "formatter": { + "enabled": true, + "formatWithErrors": false, + "indentStyle": "space", + "indentWidth": 2, + "lineEnding": "lf", + "lineWidth": 120, + "attributePosition": "auto" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noConfusingVoidType": "off", + "noExplicitAny": "off" + }, + "complexity": { + "noBannedTypes": "off" + } + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": { + "level": "on", + "options": { + "groups": [ + [":BUN:", ":NODE:"], + ":BLANK_LINE:", + ":PACKAGE:", + ":BLANK_LINE:", + [":ALIAS:"], + ":BLANK_LINE:", + ":PATH:" + ] + } + } + } + } + } +} diff --git a/deno.json b/deno.json index 977244e..3b618f5 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@valkyr/db", - "version": "2.0.0", + "version": "2.1.0", "exports": { ".": "./src/mod.ts" }, @@ -8,12 +8,19 @@ "exclude": [".github", ".vscode", ".gitignore", "tests"] }, "tasks": { - "check": "deno check ./src/mod.ts", - "lint": "npx eslint -c eslint.config.mjs --fix .", - "fmt": "npx prettier --write .", - "test": "deno test --allow-all", + "check": { + "command": "deno run -A npm:@biomejs/biome check --write ./src", + "description": "Format, lint, and organize imports of the entire project." + }, + "test": { + "command": "deno test --allow-all", + "description": "Runs all defined tests across the entire project." + }, "test:publish": "deno publish --dry-run", - "ncu": "npx ncu -u -p npm" + "ncu": { + "command": "npx ncu -u -p npm", + "description": "Updates all the dependencies in package.json to their latest versions." + } }, "compilerOptions": { "lib": ["deno.window", "dom"] diff --git a/deno.lock b/deno.lock index cfad626..444c583 100644 --- a/deno.lock +++ b/deno.lock @@ -1,25 +1,22 @@ { "version": "5", "specifiers": { + "npm:@biomejs/biome@*": "2.2.4", + "npm:@biomejs/biome@2.2.4": "2.2.4", "npm:@jsr/std__assert@1": "1.0.13", "npm:@jsr/std__async@1": "1.0.14", "npm:@jsr/std__testing@1": "1.0.15", "npm:@jsr/valkyr__testcontainers@2": "2.0.2", - "npm:bson@6": "6.10.4", - "npm:dot-prop@9": "9.0.0", - "npm:eslint-plugin-simple-import-sort@12": "12.1.1_eslint@9.33.0", - "npm:eslint@9": "9.33.0", - "npm:eslint@9.33.0": "9.33.0", - "npm:expect@30": "30.0.5", - "npm:fake-indexeddb@6": "6.1.0", - "npm:fast-equals@5": "5.2.2", - "npm:idb@8": "8.0.3", - "npm:mingo@6": "6.6.1", - "npm:prettier@3": "3.6.2", - "npm:prettier@3.6.2": "3.6.2", - "npm:rfdc@1": "1.4.1", - "npm:rxjs@7": "7.8.2", - "npm:typescript-eslint@8": "8.39.1_eslint@9.33.0_typescript@5.9.2_@typescript-eslint+parser@8.39.1__eslint@9.33.0__typescript@5.9.2" + "npm:bson@7.0.0": "7.0.0", + "npm:dot-prop@10.1.0": "10.1.0", + "npm:expect@30.2.0": "30.2.0", + "npm:fake-indexeddb@6.2.5": "6.2.5", + "npm:fast-equals@6.0.0": "6.0.0", + "npm:idb@8.0.3": "8.0.3", + "npm:mingo@7.1.1": "7.1.1", + "npm:rfdc@1.4.1": "1.4.1", + "npm:rxjs@7.8.2": "7.8.2", + "npm:zod@4.3.4": "4.3.4" }, "npm": { "@babel/code-frame@7.27.1": { @@ -30,93 +27,74 @@ "picocolors" ] }, - "@babel/helper-validator-identifier@7.27.1": { - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==" + "@babel/helper-validator-identifier@7.28.5": { + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==" }, - "@eslint-community/eslint-utils@4.7.0_eslint@9.33.0": { - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", - "dependencies": [ - "eslint", - "eslint-visitor-keys@3.4.3" - ] + "@biomejs/biome@2.2.4": { + "integrity": "sha512-TBHU5bUy/Ok6m8c0y3pZiuO/BZoY/OcGxoLlrfQof5s8ISVwbVBdFINPQZyFfKwil8XibYWb7JMwnT8wT4WVPg==", + "optionalDependencies": [ + "@biomejs/cli-darwin-arm64", + "@biomejs/cli-darwin-x64", + "@biomejs/cli-linux-arm64", + "@biomejs/cli-linux-arm64-musl", + "@biomejs/cli-linux-x64", + "@biomejs/cli-linux-x64-musl", + "@biomejs/cli-win32-arm64", + "@biomejs/cli-win32-x64" + ], + "bin": true }, - "@eslint-community/regexpp@4.12.1": { - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==" + "@biomejs/cli-darwin-arm64@2.2.4": { + "integrity": "sha512-RJe2uiyaloN4hne4d2+qVj3d3gFJFbmrr5PYtkkjei1O9c+BjGXgpUPVbi8Pl8syumhzJjFsSIYkcLt2VlVLMA==", + "os": ["darwin"], + "cpu": ["arm64"] }, - "@eslint/config-array@0.21.0": { - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", - "dependencies": [ - "@eslint/object-schema", - "debug", - "minimatch@3.1.2" - ] + "@biomejs/cli-darwin-x64@2.2.4": { + "integrity": "sha512-cFsdB4ePanVWfTnPVaUX+yr8qV8ifxjBKMkZwN7gKb20qXPxd/PmwqUH8mY5wnM9+U0QwM76CxFyBRJhC9tQwg==", + "os": ["darwin"], + "cpu": ["x64"] }, - "@eslint/config-helpers@0.3.1": { - "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==" + "@biomejs/cli-linux-arm64-musl@2.2.4": { + "integrity": "sha512-7TNPkMQEWfjvJDaZRSkDCPT/2r5ESFPKx+TEev+I2BXDGIjfCZk2+b88FOhnJNHtksbOZv8ZWnxrA5gyTYhSsQ==", + "os": ["linux"], + "cpu": ["arm64"] }, - "@eslint/core@0.15.2": { - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", - "dependencies": [ - "@types/json-schema" - ] + "@biomejs/cli-linux-arm64@2.2.4": { + "integrity": "sha512-M/Iz48p4NAzMXOuH+tsn5BvG/Jb07KOMTdSVwJpicmhN309BeEyRyQX+n1XDF0JVSlu28+hiTQ2L4rZPvu7nMw==", + "os": ["linux"], + "cpu": ["arm64"] }, - "@eslint/eslintrc@3.3.1": { - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", - "dependencies": [ - "ajv", - "debug", - "espree", - "globals", - "ignore@5.3.2", - "import-fresh", - "js-yaml", - "minimatch@3.1.2", - "strip-json-comments" - ] + "@biomejs/cli-linux-x64-musl@2.2.4": { + "integrity": "sha512-m41nFDS0ksXK2gwXL6W6yZTYPMH0LughqbsxInSKetoH6morVj43szqKx79Iudkp8WRT5SxSh7qVb8KCUiewGg==", + "os": ["linux"], + "cpu": ["x64"] }, - "@eslint/js@9.33.0": { - "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==" + "@biomejs/cli-linux-x64@2.2.4": { + "integrity": "sha512-orr3nnf2Dpb2ssl6aihQtvcKtLySLta4E2UcXdp7+RTa7mfJjBgIsbS0B9GC8gVu0hjOu021aU8b3/I1tn+pVQ==", + "os": ["linux"], + "cpu": ["x64"] }, - "@eslint/object-schema@2.1.6": { - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==" + "@biomejs/cli-win32-arm64@2.2.4": { + "integrity": "sha512-NXnfTeKHDFUWfxAefa57DiGmu9VyKi0cDqFpdI+1hJWQjGJhJutHPX0b5m+eXvTKOaf+brU+P0JrQAZMb5yYaQ==", + "os": ["win32"], + "cpu": ["arm64"] }, - "@eslint/plugin-kit@0.3.5": { - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", - "dependencies": [ - "@eslint/core", - "levn" - ] - }, - "@humanfs/core@0.19.1": { - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==" - }, - "@humanfs/node@0.16.6": { - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dependencies": [ - "@humanfs/core", - "@humanwhocodes/retry@0.3.1" - ] - }, - "@humanwhocodes/module-importer@1.0.1": { - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==" - }, - "@humanwhocodes/retry@0.3.1": { - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==" - }, - "@humanwhocodes/retry@0.4.3": { - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==" + "@biomejs/cli-win32-x64@2.2.4": { + "integrity": "sha512-3Y4V4zVRarVh/B/eSHczR4LYoSVyv3Dfuvm3cWs5w/HScccS0+Wt/lHOcDTRYeHjQmMYVC3rIRWqyN2EI52+zg==", + "os": ["win32"], + "cpu": ["x64"] }, "@jest/diff-sequences@30.0.1": { "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==" }, - "@jest/expect-utils@30.0.5": { - "integrity": "sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew==", + "@jest/expect-utils@30.2.0": { + "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", "dependencies": [ "@jest/get-type" ] }, - "@jest/get-type@30.0.1": { - "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==" + "@jest/get-type@30.1.0": { + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==" }, "@jest/pattern@30.0.1": { "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", @@ -131,8 +109,8 @@ "@sinclair/typebox" ] }, - "@jest/types@30.0.5": { - "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "@jest/types@30.2.0": { + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", "dependencies": [ "@jest/pattern", "@jest/schemas", @@ -213,28 +191,8 @@ "sparse-bitfield" ] }, - "@nodelib/fs.scandir@2.1.5": { - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dependencies": [ - "@nodelib/fs.stat", - "run-parallel" - ] - }, - "@nodelib/fs.stat@2.0.5": { - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" - }, - "@nodelib/fs.walk@1.2.8": { - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dependencies": [ - "@nodelib/fs.scandir", - "fastq" - ] - }, - "@sinclair/typebox@0.34.38": { - "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==" - }, - "@types/estree@1.0.8": { - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" + "@sinclair/typebox@0.34.46": { + "integrity": "sha512-kiW7CtS/NkdvTUjkjUJo7d5JsFfbJ14YjdhDk9KoEgK6nFjKNXZPrX0jfLA8ZlET4cFLHxOZ/0vFKOP+bOxIOQ==" }, "@types/istanbul-lib-coverage@2.0.6": { "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==" @@ -251,11 +209,8 @@ "@types/istanbul-lib-report" ] }, - "@types/json-schema@7.0.15": { - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" - }, - "@types/node@22.15.15": { - "integrity": "sha512-R5muMcZob3/Jjchn5LcO8jdKwSCbzqmPB6ruBxMcf9kbxtniZHP327s6C37iOfuw8mbKK3cAQa7sEl7afLrQ8A==", + "@types/node@24.2.0": { + "integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==", "dependencies": [ "undici-types" ] @@ -275,131 +230,12 @@ "@types/yargs-parser@21.0.3": { "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, - "@types/yargs@17.0.33": { - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "@types/yargs@17.0.35": { + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "dependencies": [ "@types/yargs-parser" ] }, - "@typescript-eslint/eslint-plugin@8.39.1_@typescript-eslint+parser@8.39.1__eslint@9.33.0__typescript@5.9.2_eslint@9.33.0_typescript@5.9.2": { - "integrity": "sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g==", - "dependencies": [ - "@eslint-community/regexpp", - "@typescript-eslint/parser", - "@typescript-eslint/scope-manager", - "@typescript-eslint/type-utils", - "@typescript-eslint/utils", - "@typescript-eslint/visitor-keys", - "eslint", - "graphemer", - "ignore@7.0.5", - "natural-compare", - "ts-api-utils", - "typescript" - ] - }, - "@typescript-eslint/parser@8.39.1_eslint@9.33.0_typescript@5.9.2": { - "integrity": "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==", - "dependencies": [ - "@typescript-eslint/scope-manager", - "@typescript-eslint/types", - "@typescript-eslint/typescript-estree", - "@typescript-eslint/visitor-keys", - "debug", - "eslint", - "typescript" - ] - }, - "@typescript-eslint/project-service@8.39.1_typescript@5.9.2": { - "integrity": "sha512-8fZxek3ONTwBu9ptw5nCKqZOSkXshZB7uAxuFF0J/wTMkKydjXCzqqga7MlFMpHi9DoG4BadhmTkITBcg8Aybw==", - "dependencies": [ - "@typescript-eslint/tsconfig-utils", - "@typescript-eslint/types", - "debug", - "typescript" - ] - }, - "@typescript-eslint/scope-manager@8.39.1": { - "integrity": "sha512-RkBKGBrjgskFGWuyUGz/EtD8AF/GW49S21J8dvMzpJitOF1slLEbbHnNEtAHtnDAnx8qDEdRrULRnWVx27wGBw==", - "dependencies": [ - "@typescript-eslint/types", - "@typescript-eslint/visitor-keys" - ] - }, - "@typescript-eslint/tsconfig-utils@8.39.1_typescript@5.9.2": { - "integrity": "sha512-ePUPGVtTMR8XMU2Hee8kD0Pu4NDE1CN9Q1sxGSGd/mbOtGZDM7pnhXNJnzW63zk/q+Z54zVzj44HtwXln5CvHA==", - "dependencies": [ - "typescript" - ] - }, - "@typescript-eslint/type-utils@8.39.1_eslint@9.33.0_typescript@5.9.2": { - "integrity": "sha512-gu9/ahyatyAdQbKeHnhT4R+y3YLtqqHyvkfDxaBYk97EcbfChSJXyaJnIL3ygUv7OuZatePHmQvuH5ru0lnVeA==", - "dependencies": [ - "@typescript-eslint/types", - "@typescript-eslint/typescript-estree", - "@typescript-eslint/utils", - "debug", - "eslint", - "ts-api-utils", - "typescript" - ] - }, - "@typescript-eslint/types@8.39.1": { - "integrity": "sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw==" - }, - "@typescript-eslint/typescript-estree@8.39.1_typescript@5.9.2": { - "integrity": "sha512-EKkpcPuIux48dddVDXyQBlKdeTPMmALqBUbEk38McWv0qVEZwOpVJBi7ugK5qVNgeuYjGNQxrrnoM/5+TI/BPw==", - "dependencies": [ - "@typescript-eslint/project-service", - "@typescript-eslint/tsconfig-utils", - "@typescript-eslint/types", - "@typescript-eslint/visitor-keys", - "debug", - "fast-glob", - "is-glob", - "minimatch@9.0.5", - "semver", - "ts-api-utils", - "typescript" - ] - }, - "@typescript-eslint/utils@8.39.1_eslint@9.33.0_typescript@5.9.2": { - "integrity": "sha512-VF5tZ2XnUSTuiqZFXCZfZs1cgkdd3O/sSYmdo2EpSyDlC86UM/8YytTmKnehOW3TGAlivqTDT6bS87B/GQ/jyg==", - "dependencies": [ - "@eslint-community/eslint-utils", - "@typescript-eslint/scope-manager", - "@typescript-eslint/types", - "@typescript-eslint/typescript-estree", - "eslint", - "typescript" - ] - }, - "@typescript-eslint/visitor-keys@8.39.1": { - "integrity": "sha512-W8FQi6kEh2e8zVhQ0eeRnxdvIoOkAp/CPAahcNio6nO9dsIwb9b34z90KOlheoyuVf6LSOEdjlkxSkapNEc+4A==", - "dependencies": [ - "@typescript-eslint/types", - "eslint-visitor-keys@4.2.1" - ] - }, - "acorn-jsx@5.3.2_acorn@8.15.0": { - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dependencies": [ - "acorn" - ] - }, - "acorn@8.15.0": { - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "bin": true - }, - "ajv@6.12.6": { - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": [ - "fast-deep-equal", - "fast-json-stable-stringify", - "json-schema-traverse", - "uri-js" - ] - }, "ansi-styles@4.3.0": { "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dependencies": [ @@ -409,25 +245,6 @@ "ansi-styles@5.2.0": { "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" }, - "argparse@2.0.1": { - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "balanced-match@1.0.2": { - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "brace-expansion@1.1.12": { - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dependencies": [ - "balanced-match", - "concat-map" - ] - }, - "brace-expansion@2.0.2": { - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dependencies": [ - "balanced-match" - ] - }, "braces@3.0.3": { "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": [ @@ -437,8 +254,8 @@ "bson@6.10.4": { "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==" }, - "callsites@3.1.0": { - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + "bson@7.0.0": { + "integrity": "sha512-Kwc6Wh4lQ5OmkqqKhYGKIuELXl+EPYSCObVE6bWsp1T/cGkOCBN0I8wF/T44BiuhHyNi1mmKVPXk60d41xZ7kw==" }, "chalk@4.1.2": { "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -447,8 +264,8 @@ "supports-color" ] }, - "ci-info@4.3.0": { - "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==" + "ci-info@4.3.1": { + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==" }, "color-convert@2.0.1": { "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", @@ -459,28 +276,8 @@ "color-name@1.1.4": { "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "concat-map@0.0.1": { - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "cross-spawn@7.0.6": { - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dependencies": [ - "path-key", - "shebang-command", - "which" - ] - }, - "debug@4.4.1": { - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dependencies": [ - "ms" - ] - }, - "deep-is@0.1.4": { - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" - }, - "dot-prop@9.0.0": { - "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==", + "dot-prop@10.1.0": { + "integrity": "sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q==", "dependencies": [ "type-fest" ] @@ -488,97 +285,8 @@ "escape-string-regexp@2.0.0": { "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" }, - "escape-string-regexp@4.0.0": { - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" - }, - "eslint-plugin-simple-import-sort@12.1.1_eslint@9.33.0": { - "integrity": "sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==", - "dependencies": [ - "eslint" - ] - }, - "eslint-scope@8.4.0": { - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dependencies": [ - "esrecurse", - "estraverse" - ] - }, - "eslint-visitor-keys@3.4.3": { - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==" - }, - "eslint-visitor-keys@4.2.1": { - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==" - }, - "eslint@9.33.0": { - "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", - "dependencies": [ - "@eslint-community/eslint-utils", - "@eslint-community/regexpp", - "@eslint/config-array", - "@eslint/config-helpers", - "@eslint/core", - "@eslint/eslintrc", - "@eslint/js", - "@eslint/plugin-kit", - "@humanfs/node", - "@humanwhocodes/module-importer", - "@humanwhocodes/retry@0.4.3", - "@types/estree", - "@types/json-schema", - "ajv", - "chalk", - "cross-spawn", - "debug", - "escape-string-regexp@4.0.0", - "eslint-scope", - "eslint-visitor-keys@4.2.1", - "espree", - "esquery", - "esutils", - "fast-deep-equal", - "file-entry-cache", - "find-up", - "glob-parent@6.0.2", - "ignore@5.3.2", - "imurmurhash", - "is-glob", - "json-stable-stringify-without-jsonify", - "lodash.merge", - "minimatch@3.1.2", - "natural-compare", - "optionator" - ], - "bin": true - }, - "espree@10.4.0_acorn@8.15.0": { - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dependencies": [ - "acorn", - "acorn-jsx", - "eslint-visitor-keys@4.2.1" - ] - }, - "esquery@1.6.0": { - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dependencies": [ - "estraverse" - ] - }, - "esrecurse@4.3.0": { - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dependencies": [ - "estraverse" - ] - }, - "estraverse@5.3.0": { - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" - }, - "esutils@2.0.3": { - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "expect@30.0.5": { - "integrity": "sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ==", + "expect@30.2.0": { + "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", "dependencies": [ "@jest/expect-utils", "@jest/get-type", @@ -588,42 +296,11 @@ "jest-util" ] }, - "fake-indexeddb@6.1.0": { - "integrity": "sha512-gOzajWIhEug/CQHUIxigKT9Zilh5/I6WvUBez6/UdUtT/YVEHM9r572Os8wfvhp7TkmgBtRNdqSM7YoCXWMzZg==" + "fake-indexeddb@6.2.5": { + "integrity": "sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==" }, - "fast-deep-equal@3.1.3": { - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "fast-equals@5.2.2": { - "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==" - }, - "fast-glob@3.3.3": { - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dependencies": [ - "@nodelib/fs.stat", - "@nodelib/fs.walk", - "glob-parent@5.1.2", - "merge2", - "micromatch" - ] - }, - "fast-json-stable-stringify@2.1.0": { - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "fast-levenshtein@2.0.6": { - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" - }, - "fastq@1.19.1": { - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dependencies": [ - "reusify" - ] - }, - "file-entry-cache@8.0.0": { - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dependencies": [ - "flat-cache" - ] + "fast-equals@6.0.0": { + "integrity": "sha512-PFhhIGgdM79r5Uztdj9Zb6Tt1zKafqVfdMGwVca1z5z6fbX7DmsySSuJd8HiP6I1j505DCS83cLxo5rmSNeVEA==" }, "fill-range@7.1.1": { "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", @@ -631,83 +308,20 @@ "to-regex-range" ] }, - "find-up@5.0.0": { - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": [ - "locate-path", - "path-exists" - ] - }, - "flat-cache@4.0.1": { - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dependencies": [ - "flatted", - "keyv" - ] - }, - "flatted@3.3.3": { - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==" - }, - "glob-parent@5.1.2": { - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": [ - "is-glob" - ] - }, - "glob-parent@6.0.2": { - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dependencies": [ - "is-glob" - ] - }, - "globals@14.0.0": { - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==" - }, "graceful-fs@4.2.11": { "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, - "graphemer@1.4.0": { - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" - }, "has-flag@4.0.0": { "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "idb@8.0.3": { "integrity": "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==" }, - "ignore@5.3.2": { - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==" - }, - "ignore@7.0.5": { - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==" - }, - "import-fresh@3.3.1": { - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dependencies": [ - "parent-module", - "resolve-from" - ] - }, - "imurmurhash@0.1.4": { - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" - }, - "is-extglob@2.1.1": { - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" - }, - "is-glob@4.0.3": { - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dependencies": [ - "is-extglob" - ] - }, "is-number@7.0.0": { "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, - "isexe@2.0.0": { - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "jest-diff@30.0.5": { - "integrity": "sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==", + "jest-diff@30.2.0": { + "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", "dependencies": [ "@jest/diff-sequences", "@jest/get-type", @@ -715,8 +329,8 @@ "pretty-format" ] }, - "jest-matcher-utils@30.0.5": { - "integrity": "sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ==", + "jest-matcher-utils@30.2.0": { + "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", "dependencies": [ "@jest/get-type", "chalk", @@ -724,8 +338,8 @@ "pretty-format" ] }, - "jest-message-util@30.0.5": { - "integrity": "sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA==", + "jest-message-util@30.2.0": { + "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", "dependencies": [ "@babel/code-frame", "@jest/types", @@ -738,8 +352,8 @@ "stack-utils" ] }, - "jest-mock@30.0.5": { - "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==", + "jest-mock@30.2.0": { + "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", "dependencies": [ "@jest/types", "@types/node", @@ -749,8 +363,8 @@ "jest-regex-util@30.0.1": { "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==" }, - "jest-util@30.0.5": { - "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", + "jest-util@30.2.0": { + "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", "dependencies": [ "@jest/types", "@types/node", @@ -763,50 +377,9 @@ "js-tokens@4.0.0": { "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, - "js-yaml@4.1.0": { - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": [ - "argparse" - ], - "bin": true - }, - "json-buffer@3.0.1": { - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" - }, - "json-schema-traverse@0.4.1": { - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "json-stable-stringify-without-jsonify@1.0.1": { - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" - }, - "keyv@4.5.4": { - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dependencies": [ - "json-buffer" - ] - }, - "levn@0.4.1": { - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dependencies": [ - "prelude-ls", - "type-check" - ] - }, - "locate-path@6.0.0": { - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": [ - "p-locate" - ] - }, - "lodash.merge@4.6.2": { - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" - }, "memory-pager@1.5.0": { "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, - "merge2@1.4.1": { - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" - }, "micromatch@4.0.8": { "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dependencies": [ @@ -814,20 +387,8 @@ "picomatch@2.3.1" ] }, - "mingo@6.6.1": { - "integrity": "sha512-KC6b1ODYoSdYu5fBm+SzQb7fa4ARmGwfa3Cf9F7U+2mnfD4Zhf89qQgO1cPTtaJ68w3ntIT5dVujgF52HvN7+g==" - }, - "minimatch@3.1.2": { - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": [ - "brace-expansion@1.1.12" - ] - }, - "minimatch@9.0.5": { - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dependencies": [ - "brace-expansion@2.0.2" - ] + "mingo@7.1.1": { + "integrity": "sha512-YfcRcZ4TRzRw1G3tf8Nu04fIoFr8hVYdTDktXU2ZLXnfn51E8yeUDixOGGQYQLduMjfznsCmEuBwzncw9lGxwA==" }, "mongodb-connection-string-url@3.0.2": { "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", @@ -840,51 +401,10 @@ "integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==", "dependencies": [ "@mongodb-js/saslprep", - "bson", + "bson@6.10.4", "mongodb-connection-string-url" ] }, - "ms@2.1.3": { - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "natural-compare@1.4.0": { - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" - }, - "optionator@0.9.4": { - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dependencies": [ - "deep-is", - "fast-levenshtein", - "levn", - "prelude-ls", - "type-check", - "word-wrap" - ] - }, - "p-limit@3.1.0": { - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dependencies": [ - "yocto-queue" - ] - }, - "p-locate@5.0.0": { - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": [ - "p-limit" - ] - }, - "parent-module@1.0.1": { - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dependencies": [ - "callsites" - ] - }, - "path-exists@4.0.0": { - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - }, - "path-key@3.1.1": { - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" - }, "picocolors@1.1.1": { "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, @@ -897,15 +417,8 @@ "postgres@3.4.7": { "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==" }, - "prelude-ls@1.2.1": { - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" - }, - "prettier@3.6.2": { - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", - "bin": true - }, - "pretty-format@30.0.5": { - "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", + "pretty-format@30.2.0": { + "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", "dependencies": [ "@jest/schemas", "ansi-styles@5.2.0", @@ -915,46 +428,18 @@ "punycode@2.3.1": { "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, - "queue-microtask@1.2.3": { - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" - }, "react-is@18.3.1": { "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, - "resolve-from@4.0.0": { - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" - }, - "reusify@1.1.0": { - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==" - }, "rfdc@1.4.1": { "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" }, - "run-parallel@1.2.0": { - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dependencies": [ - "queue-microtask" - ] - }, "rxjs@7.8.2": { "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "dependencies": [ "tslib" ] }, - "semver@7.7.2": { - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "bin": true - }, - "shebang-command@2.0.0": { - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": [ - "shebang-regex" - ] - }, - "shebang-regex@3.0.0": { - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" - }, "slash@3.0.0": { "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" }, @@ -967,18 +452,18 @@ "stack-utils@2.0.6": { "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dependencies": [ - "escape-string-regexp@2.0.0" + "escape-string-regexp" ] }, - "strip-json-comments@3.1.1": { - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" - }, "supports-color@7.2.0": { "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dependencies": [ "has-flag" ] }, + "tagged-tag@1.0.0": { + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==" + }, "to-regex-range@5.0.1": { "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dependencies": [ @@ -991,47 +476,17 @@ "punycode" ] }, - "ts-api-utils@2.1.0_typescript@5.9.2": { - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dependencies": [ - "typescript" - ] - }, "tslib@2.8.1": { "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, - "type-check@0.4.0": { - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "type-fest@5.3.1": { + "integrity": "sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==", "dependencies": [ - "prelude-ls" + "tagged-tag" ] }, - "type-fest@4.41.0": { - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==" - }, - "typescript-eslint@8.39.1_eslint@9.33.0_typescript@5.9.2_@typescript-eslint+parser@8.39.1__eslint@9.33.0__typescript@5.9.2": { - "integrity": "sha512-GDUv6/NDYngUlNvwaHM1RamYftxf782IyEDbdj3SeaIHHv8fNQVRC++fITT7kUJV/5rIA/tkoRSSskt6osEfqg==", - "dependencies": [ - "@typescript-eslint/eslint-plugin", - "@typescript-eslint/parser", - "@typescript-eslint/typescript-estree", - "@typescript-eslint/utils", - "eslint", - "typescript" - ] - }, - "typescript@5.9.2": { - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", - "bin": true - }, - "undici-types@6.21.0": { - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" - }, - "uri-js@4.4.1": { - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": [ - "punycode" - ] + "undici-types@7.10.0": { + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==" }, "webidl-conversions@7.0.0": { "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" @@ -1043,40 +498,28 @@ "webidl-conversions" ] }, - "which@2.0.2": { - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": [ - "isexe" - ], - "bin": true - }, - "word-wrap@1.2.5": { - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==" - }, - "yocto-queue@0.1.0": { - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + "zod@4.3.4": { + "integrity": "sha512-Zw/uYiiyF6pUT1qmKbZziChgNPRu+ZRneAsMUDU6IwmXdWt5JwcUfy2bvLOCUtz5UniaN/Zx5aFttZYbYc7O/A==" } }, "workspace": { "packageJson": { "dependencies": [ + "npm:@biomejs/biome@2.2.4", "npm:@jsr/std__assert@1", "npm:@jsr/std__async@1", "npm:@jsr/std__testing@1", "npm:@jsr/valkyr__testcontainers@2", - "npm:bson@6", - "npm:dot-prop@9", - "npm:eslint-plugin-simple-import-sort@12", - "npm:eslint@9", - "npm:expect@30", - "npm:fake-indexeddb@6", - "npm:fast-equals@5", - "npm:idb@8", - "npm:mingo@6", - "npm:prettier@3", - "npm:rfdc@1", - "npm:rxjs@7", - "npm:typescript-eslint@8" + "npm:bson@7.0.0", + "npm:dot-prop@10.1.0", + "npm:expect@30.2.0", + "npm:fake-indexeddb@6.2.5", + "npm:fast-equals@6.0.0", + "npm:idb@8.0.3", + "npm:mingo@7.1.1", + "npm:rfdc@1.4.1", + "npm:rxjs@7.8.2", + "npm:zod@4.3.4" ] } } diff --git a/package.json b/package.json index 9cdc74b..a8d5f30 100644 --- a/package.json +++ b/package.json @@ -6,24 +6,22 @@ "bugs": "https://github.com/valkyrjs/db/issues", "license": "MIT", "dependencies": { - "bson": "6", - "dot-prop": "9", - "fast-equals": "5", - "idb": "8", - "mingo": "6", - "rfdc": "1", - "rxjs": "7" + "bson": "7.0.0", + "dot-prop": "10.1.0", + "fast-equals": "6.0.0", + "idb": "8.0.3", + "mingo": "7.1.1", + "rfdc": "1.4.1", + "rxjs": "7.8.2", + "zod": "4.3.4" }, "devDependencies": { "@std/async": "npm:@jsr/std__async@1", "@std/assert": "npm:@jsr/std__assert@1", "@std/testing": "npm:@jsr/std__testing@1", "@valkyr/testcontainers": "npm:@jsr/valkyr__testcontainers@2", - "eslint": "9", - "eslint-plugin-simple-import-sort": "12", - "expect": "30", - "fake-indexeddb": "6", - "prettier": "3", - "typescript-eslint": "8" + "@biomejs/biome": "2.2.4", + "expect": "30.2.0", + "fake-indexeddb": "6.2.5" } } diff --git a/src/broadcast.ts b/src/broadcast.ts index bfcc401..9e744df 100644 --- a/src/broadcast.ts +++ b/src/broadcast.ts @@ -1,4 +1,4 @@ -import type { Document, WithId } from "./types.ts"; +import type { AnyObject } from "mingo/types"; export const BroadcastChannel = globalThis.BroadcastChannel ?? @@ -8,16 +8,16 @@ export const BroadcastChannel = close() {} }; -export type StorageBroadcast = +export type StorageBroadcast = | { name: string; type: "insertOne" | "updateOne"; - data: WithId; + data: AnyObject; } | { name: string; type: "insertMany" | "updateMany" | "remove"; - data: WithId[]; + data: AnyObject[]; } | { name: string; diff --git a/src/collection.ts b/src/collection.ts index 1cf4176..f6ec816 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -1,18 +1,12 @@ -import { UpdateOptions } from "mingo/core"; -import { UpdateExpression } from "mingo/updater"; -import { Observable, Subject, Subscription } from "rxjs"; +import type { AnyObject, Criteria } from "mingo/types"; +import type { Modifier } from "mingo/updater"; +import { Observable, type Subject, type Subscription } from "rxjs"; +import type z from "zod"; +import type { ZodObject, ZodRawShape } from "zod"; import { observe, observeOne } from "./observe/mod.ts"; -import { - ChangeEvent, - InsertManyResult, - InsertOneResult, - Options, - RemoveResult, - Storage, - UpdateResult, -} from "./storage/mod.ts"; -import { Document, Filter, WithId } from "./types.ts"; +import type { ChangeEvent, InsertResult, QueryOptions, Storage, UpdateResult } from "./storage/mod.ts"; +import type { AnyDocument } from "./types.ts"; /* |-------------------------------------------------------------------------------- @@ -20,59 +14,100 @@ import { Document, Filter, WithId } from "./types.ts"; |-------------------------------------------------------------------------------- */ -export class Collection { - constructor( - readonly name: string, - readonly storage: Storage, - ) {} +export class Collection< + TOptions extends AnyCollectionOptions = AnyCollectionOptions, + TAdapter extends Storage = TOptions["adapter"], + TPrimaryKey extends string = TOptions["primaryKey"], + TSchema extends AnyDocument = z.output>, +> { + declare readonly $schema: TSchema; + + constructor(readonly options: TOptions) {} get observable(): { - change: Subject>; + change: Subject; flush: Subject; } { return this.storage.observable; } + get storage(): TAdapter { + return this.options.adapter; + } + /* |-------------------------------------------------------------------------------- | Mutators |-------------------------------------------------------------------------------- */ - async insertOne(document: Partial>): Promise { - return this.storage.resolve().then((storage) => storage.insertOne(document)); + async insertOne(values: TSchema | Omit): Promise { + return this.storage.resolve().then((storage) => + storage.insertOne({ + collection: this.options.name, + pkey: this.options.primaryKey, + values, + }), + ); } - async insertMany(documents: Partial>[]): Promise { - return this.storage.resolve().then((storage) => storage.insertMany(documents)); + async insertMany(values: (TSchema | Omit)[]): Promise { + return this.storage.resolve().then((storage) => + storage.insertMany({ + collection: this.options.name, + pkey: this.options.primaryKey, + values, + }), + ); } async updateOne( - filter: Filter>, - expr: UpdateExpression, - arrayFilters?: Filter>[], - condition?: Filter>, - options?: UpdateOptions, + condition: Criteria, + modifier: Modifier, + arrayFilters?: AnyObject[], ): Promise { - return this.storage.resolve().then((storage) => storage.updateOne(filter, expr, arrayFilters, condition, options)); + return this.storage.resolve().then((storage) => + storage.updateOne({ + collection: this.options.name, + pkey: this.options.primaryKey, + condition, + modifier, + arrayFilters, + }), + ); } async updateMany( - filter: Filter>, - expr: UpdateExpression, - arrayFilters?: Filter>[], - condition?: Filter>, - options?: UpdateOptions, + condition: Criteria, + modifier: Modifier, + arrayFilters?: AnyObject[], ): Promise { - return this.storage.resolve().then((storage) => storage.updateMany(filter, expr, arrayFilters, condition, options)); + return this.storage.resolve().then((storage) => + storage.updateMany({ + collection: this.options.name, + pkey: this.options.primaryKey, + condition, + modifier, + arrayFilters, + }), + ); } - async replaceOne(filter: Filter>, document: TSchema): Promise { - return this.storage.resolve().then((storage) => storage.replace(filter, document)); + async replaceOne(condition: Criteria, document: TSchema): Promise { + return this.storage.resolve().then((storage) => + storage.replace({ + collection: this.options.name, + pkey: this.options.primaryKey, + condition, + document, + }), + ); } - async remove(filter: Filter>): Promise { - return this.storage.resolve().then((storage) => storage.remove(filter)); + async remove(condition: Criteria): Promise { + return this.storage + .resolve() + .then((storage) => storage.remove({ collection: this.options.name, pkey: this.options.primaryKey, condition })); } /* @@ -82,37 +117,37 @@ export class Collection { */ subscribe( - filter?: Filter>, + condition?: Criteria, options?: SubscribeToSingle, - next?: (document: WithId | undefined) => void, + next?: (document: TSchema | undefined) => void, ): Subscription; subscribe( - filter?: Filter>, + condition?: Criteria, options?: SubscribeToMany, - next?: (documents: WithId[], changed: WithId[], type: ChangeEvent["type"]) => void, + next?: (documents: TSchema[], changed: TSchema[], type: ChangeEvent["type"]) => void, ): Subscription; - subscribe(filter: Filter> = {}, options?: Options, next?: (...args: any[]) => void): Subscription { + subscribe(condition: Criteria = {}, options?: QueryOptions, next?: (...args: any[]) => void): Subscription { if (options?.limit === 1) { - return this.#observeOne(filter).subscribe({ next }); + return this.#observeOne(condition).subscribe({ next }); } - return this.#observe(filter, options).subscribe({ - next: (value: [WithId[], WithId[], ChangeEvent["type"]]) => next?.(...value), + return this.#observe(condition, options).subscribe({ + next: (value: [TSchema[], TSchema[], ChangeEvent["type"]]) => next?.(...value), }); } #observe( - filter: Filter> = {}, - options?: Options, - ): Observable<[WithId[], WithId[], ChangeEvent["type"]]> { - return new Observable<[WithId[], WithId[], ChangeEvent["type"]]>((subscriber) => { + filter: Criteria = {}, + options?: QueryOptions, + ): Observable<[TSchema[], TSchema[], ChangeEvent["type"]]> { + return new Observable<[TSchema[], TSchema[], ChangeEvent["type"]]>((subscriber) => { return observe(this as any, filter, options, (values, changed, type) => subscriber.next([values, changed, type] as any), ); }); } - #observeOne(filter: Filter> = {}): Observable | undefined> { - return new Observable | undefined>((subscriber) => { + #observeOne(filter: Criteria = {}): Observable { + return new Observable((subscriber) => { return observeOne(this as any, filter, (values) => subscriber.next(values as any)); }); } @@ -126,32 +161,34 @@ export class Collection { /** * Retrieve a record by the document 'id' key. */ - async findById(id: string): Promise | undefined> { - return this.storage.resolve().then((storage) => storage.findById(id)); + async findById(id: string): Promise { + return this.storage.resolve().then((storage) => storage.findById({ collection: this.options.name, id })); } /** * Performs a mingo filter search over the collection data and returns * a single document if one was found matching the filter and options. */ - async findOne(filter: Filter> = {}, options?: Options): Promise | undefined> { - return this.find(filter, options).then(([document]) => document); + async findOne(condition: Criteria = {}, options?: QueryOptions): Promise { + return this.find(condition, options).then(([document]) => document); } /** * Performs a mingo filter search over the collection data and returns any * documents matching the provided filter and options. */ - async find(filter: Filter> = {}, options?: Options): Promise[]> { - return this.storage.resolve().then((storage) => storage.find(filter, options)); + async find(condition: Criteria = {}, options?: QueryOptions): Promise { + return this.storage + .resolve() + .then((storage) => storage.find({ collection: this.options.name, condition, options })); } /** * Performs a mingo filter search over the collection data and returns * the count of all documents found matching the filter and options. */ - async count(filter?: Filter>): Promise { - return this.storage.resolve().then((storage) => storage.count(filter)); + async count(condition?: Criteria): Promise { + return this.storage.resolve().then((storage) => storage.count({ collection: this.options.name, condition })); } /** @@ -172,18 +209,32 @@ export class Collection { */ export type SubscriptionOptions = { - sort?: Options["sort"]; - skip?: Options["skip"]; - range?: Options["range"]; - offset?: Options["offset"]; - limit?: Options["limit"]; - index?: Options["index"]; + sort?: QueryOptions["sort"]; + skip?: QueryOptions["skip"]; + range?: QueryOptions["range"]; + offset?: QueryOptions["offset"]; + limit?: QueryOptions["limit"]; + index?: QueryOptions["index"]; }; -export type SubscribeToSingle = Options & { +export type SubscribeToSingle = QueryOptions & { limit: 1; }; -export type SubscribeToMany = Options & { +export type SubscribeToMany = QueryOptions & { limit?: number; }; + +type AnyCollectionOptions = CollectionOptions; + +type CollectionOptions< + TName extends string, + TAdapter extends Storage, + TPrimaryKey extends string | number | symbol, + TSchema extends ZodRawShape, +> = { + name: TName; + adapter: TAdapter; + primaryKey: TPrimaryKey; + schema: TSchema; +}; diff --git a/src/databases/indexeddb/cache.ts b/src/databases/indexeddb/cache.ts index 3272753..452e2f4 100644 --- a/src/databases/indexeddb/cache.ts +++ b/src/databases/indexeddb/cache.ts @@ -1,16 +1,16 @@ import { hashCodeQuery } from "../../hash.ts"; -import { Options } from "../../storage/mod.ts"; -import type { Document, Filter, WithId } from "../../types.ts"; +import type { QueryOptions } from "../../storage/mod.ts"; +import type { Document, Filter } from "../../types.ts"; -export class IndexedDbCache { +export class IndexedDBCache { readonly #cache = new Map(); - readonly #documents = new Map>(); + readonly #documents = new Map(); - hash(filter: Filter>, options: Options): number { + hash(filter: Filter, options: QueryOptions): number { return hashCodeQuery(filter, options); } - set(hashCode: number, documents: WithId[]) { + set(hashCode: number, documents: TSchema[]) { this.#cache.set( hashCode, documents.map((document) => document.id), @@ -20,10 +20,10 @@ export class IndexedDbCache { } } - get(hashCode: number): WithId[] | undefined { + get(hashCode: number): TSchema[] | undefined { const ids = this.#cache.get(hashCode); if (ids !== undefined) { - return ids.map((id) => this.#documents.get(id) as WithId); + return ids.map((id) => this.#documents.get(id) as TSchema); } } diff --git a/src/databases/indexeddb/database.ts b/src/databases/indexeddb/database.ts index 00992ef..82d298c 100644 --- a/src/databases/indexeddb/database.ts +++ b/src/databases/indexeddb/database.ts @@ -1,29 +1,32 @@ -import { IDBPDatabase, openDB } from "idb"; +import { type IDBPDatabase, openDB } from "idb"; import { Collection } from "../../collection.ts"; -import { DBLogger } from "../../logger.ts"; -import { Document } from "../../types.ts"; -import { Registrars } from "../registrars.ts"; -import { IndexedDbStorage } from "./storage.ts"; +import type { DBLogger } from "../../logger.ts"; +import type { Document } from "../../types.ts"; +import type { Registrars } from "../registrars.ts"; +import { IndexedDBStorage } from "./storage.ts"; -export class IndexedDatabase> { +export class IndexedDB> { readonly #collections = new Map>(); readonly #db: Promise>; constructor(readonly options: Options) { this.#db = openDB(options.name, options.version ?? 1, { upgrade: (db: IDBPDatabase) => { - for (const { name, indexes = [] } of options.registrars) { - const store = db.createObjectStore(name as string, { keyPath: "id" }); - store.createIndex("id", "id", { unique: true }); + for (const { name, primaryKey = "id", indexes = [] } of options.registrars) { + const store = db.createObjectStore(name as string, { keyPath: primaryKey }); + store.createIndex(primaryKey, primaryKey, { unique: true }); for (const [keyPath, options] of indexes) { store.createIndex(keyPath, keyPath, options); } } }, }); - for (const { name } of options.registrars) { - this.#collections.set(name, new Collection(name, new IndexedDbStorage(name, this.#db, options.log ?? log))); + for (const { name, primaryKey = "id" } of options.registrars) { + this.#collections.set( + name, + new Collection(name, new IndexedDBStorage(name, primaryKey, this.#db, options.log ?? log)), + ); } } @@ -33,9 +36,7 @@ export class IndexedDatabase> { |-------------------------------------------------------------------------------- */ - collection( - name: Name, - ): Collection { + collection(name: Name) { const collection = this.#collections.get(name); if (collection === undefined) { throw new Error(`Collection '${name as string}' not found`); diff --git a/src/databases/indexeddb/storage.ts b/src/databases/indexeddb/storage.ts index f4ca81d..c5cf751 100644 --- a/src/databases/indexeddb/storage.ts +++ b/src/databases/indexeddb/storage.ts @@ -1,9 +1,10 @@ -import { IDBPDatabase } from "idb"; -import { createUpdater, Query } from "mingo"; -import { UpdateOptions } from "mingo/core"; -import { UpdateExpression } from "mingo/updater"; +import type { IDBPDatabase } from "idb"; +import { Query, update } from "mingo"; +import type { Criteria, Options } from "mingo/types"; +import type { CloneMode, Modifier } from "mingo/updater"; -import { DBLogger, InsertLog, QueryLog, RemoveLog, ReplaceLog, UpdateLog } from "../../logger.ts"; +import { type DBLogger, InsertLog, QueryLog, RemoveLog, ReplaceLog, UpdateLog } from "../../logger.ts"; +import { getDocumentWithPrimaryKey } from "../../primary-key.ts"; import { DuplicateDocumentError } from "../../storage/errors.ts"; import { getInsertManyResult, @@ -13,17 +14,18 @@ import { } from "../../storage/operators/insert.ts"; import { RemoveResult } from "../../storage/operators/remove.ts"; import { UpdateResult } from "../../storage/operators/update.ts"; -import { addOptions, Index, Options, Storage } from "../../storage/storage.ts"; -import type { Document, Filter, WithId } from "../../types.ts"; -import { IndexedDbCache } from "./cache.ts"; +import { addOptions, type Index, type QueryOptions, Storage } from "../../storage/storage.ts"; +import type { Document, Filter } from "../../types.ts"; +import { IndexedDBCache } from "./cache.ts"; const OBJECT_PROTOTYPE = Object.getPrototypeOf({}); const OBJECT_TAG = "[object Object]"; -const update = createUpdater({ cloneMode: "deep" }); - -export class IndexedDbStorage extends Storage { - readonly #cache = new IndexedDbCache(); +export class IndexedDBStorage extends Storage< + TPrimaryKey, + TSchema +> { + readonly #cache = new IndexedDBCache(); readonly #promise: Promise; @@ -31,10 +33,11 @@ export class IndexedDbStorage extends Stora constructor( name: string, + primaryKey: TPrimaryKey, promise: Promise, readonly log: DBLogger, ) { - super(name); + super(name, primaryKey); this.#promise = promise; } @@ -66,11 +69,12 @@ export class IndexedDbStorage extends Stora |-------------------------------------------------------------------------------- */ - async insertOne(data: Partial): Promise { + async insertOne(values: TSchema | Omit): Promise { const logger = new InsertLog(this.name); - const document = { ...data, id: data.id ?? crypto.randomUUID() } as any; - if (await this.has(document.id)) { + const document = getDocumentWithPrimaryKey(this.primaryKey, values); + + if (await this.has(document[this.primaryKey])) { throw new DuplicateDocumentError(document, this as any); } await this.db.transaction(this.name, "readwrite", { durability: "relaxed" }).store.add(document); @@ -83,15 +87,15 @@ export class IndexedDbStorage extends Stora return getInsertOneResult(document); } - async insertMany(data: Partial[]): Promise { + async insertMany(values: (TSchema | Omit)[]): Promise { const logger = new InsertLog(this.name); - const documents: WithId[] = []; + const documents: TSchema[] = []; const tx = this.db.transaction(this.name, "readwrite", { durability: "relaxed" }); await Promise.all( - data.map((data) => { - const document = { ...data, id: data.id ?? crypto.randomUUID() } as WithId; + values.map((values) => { + const document = getDocumentWithPrimaryKey(this.primaryKey, values); documents.push(document); return tx.store.add(document); }), @@ -112,11 +116,11 @@ export class IndexedDbStorage extends Stora |-------------------------------------------------------------------------------- */ - async findById(id: string): Promise | undefined> { + async findById(id: string): Promise { return this.db.getFromIndex(this.name, "id", id); } - async find(filter: Filter>, options: Options = {}): Promise[]> { + async find(filter: Filter, options: QueryOptions = {}): Promise { const logger = new QueryLog(this.name, { filter, options }); const hashCode = this.#cache.hash(filter, options); @@ -132,7 +136,7 @@ export class IndexedDbStorage extends Stora cursor = addOptions(cursor, options); } - const documents = cursor.all() as WithId[]; + const documents = cursor.all() as TSchema[]; this.#cache.set(this.#cache.hash(filter, options), documents); this.log(logger.result()); @@ -151,8 +155,8 @@ export class IndexedDbStorage extends Stora if (indexNames.contains(key) === true) { let val: any; if (isObject(filter[key]) === true) { - if ((filter as any)[key]["$in"] !== undefined) { - val = (filter as any)[key]["$in"]; + if ((filter as any)[key].$in !== undefined) { + val = (filter as any)[key].$in; } } else { val = filter[key]; @@ -168,7 +172,7 @@ export class IndexedDbStorage extends Stora return {}; } - async #getAll({ index, offset, range, limit }: Options) { + async #getAll({ index, offset, range, limit }: QueryOptions) { if (index !== undefined) { return this.#getAllByIndex(index); } @@ -230,34 +234,34 @@ export class IndexedDbStorage extends Stora */ async updateOne( - filter: Filter>, - expr: UpdateExpression, - arrayFilters?: Filter>[], - condition?: Filter>, - options?: UpdateOptions, + filter: Filter, + modifier: Modifier, + arrayFilters?: Filter[], + condition?: Criteria, + options: { cloneMode?: CloneMode; queryOptions?: Partial } = { cloneMode: "deep" }, ): Promise { if (typeof filter.id === "string") { - return this.#update(filter.id, expr, arrayFilters, condition, options); + return this.#update(filter.id, modifier, arrayFilters, condition, options); } const documents = await this.find(filter); if (documents.length > 0) { - return this.#update(documents[0].id, expr, arrayFilters, condition, options); + return this.#update(documents[0].id, modifier, arrayFilters, condition, options); } return new UpdateResult(0, 0); } async updateMany( - filter: Filter>, - expr: UpdateExpression, - arrayFilters?: Filter>[], - condition?: Filter>, - options?: UpdateOptions, + filter: Filter, + modifier: Modifier, + arrayFilters?: Filter[], + condition?: Criteria, + options: { cloneMode?: CloneMode; queryOptions?: Partial } = { cloneMode: "deep" }, ): Promise { - const logger = new UpdateLog(this.name, { filter, expr, arrayFilters, condition, options }); + const logger = new UpdateLog(this.name, { filter, modifier, arrayFilters, condition, options }); const ids = await this.find(filter).then((data) => data.map((d) => d.id)); - const documents: WithId[] = []; + const documents: TSchema[] = []; let modifiedCount = 0; const tx = this.db.transaction(this.name, "readwrite", { durability: "relaxed" }); @@ -267,7 +271,7 @@ export class IndexedDbStorage extends Stora if (current === undefined) { return; } - const modified = update(current, expr, arrayFilters, condition, options); + const modified = update(current, modifier, arrayFilters, condition, options); if (modified.length > 0) { modifiedCount += 1; documents.push(current); @@ -287,12 +291,12 @@ export class IndexedDbStorage extends Stora return new UpdateResult(ids.length, modifiedCount); } - async replace(filter: Filter>, document: WithId): Promise { + async replace(filter: Filter, document: TSchema): Promise { const logger = new ReplaceLog(this.name, document); const ids = await this.find(filter).then((data) => data.map((d) => d.id)); - const documents: WithId[] = []; + const documents: TSchema[] = []; const count = ids.length; const tx = this.db.transaction(this.name, "readwrite", { durability: "relaxed" }); @@ -315,12 +319,12 @@ export class IndexedDbStorage extends Stora async #update( id: string | number, - expr: UpdateExpression, - arrayFilters?: Filter>[], - condition?: Filter>, - options?: UpdateOptions, + modifier: Modifier, + arrayFilters?: Filter[], + condition?: Criteria, + options: { cloneMode?: CloneMode; queryOptions?: Partial } = { cloneMode: "deep" }, ): Promise { - const logger = new UpdateLog(this.name, { id, expr }); + const logger = new UpdateLog(this.name, { id, modifier }); const tx = this.db.transaction(this.name, "readwrite", { durability: "relaxed" }); @@ -330,7 +334,7 @@ export class IndexedDbStorage extends Stora return new UpdateResult(0, 0); } - const modified = await update(current, expr, arrayFilters, condition, options); + const modified = await update(current, modifier, arrayFilters, condition, options); if (modified.length > 0) { await tx.store.put(current); } @@ -352,7 +356,7 @@ export class IndexedDbStorage extends Stora |-------------------------------------------------------------------------------- */ - async remove(filter: Filter>): Promise { + async remove(filter: Filter): Promise { const logger = new RemoveLog(this.name, { filter }); const documents = await this.find(filter); @@ -375,7 +379,7 @@ export class IndexedDbStorage extends Stora |-------------------------------------------------------------------------------- */ - async count(filter?: Filter>): Promise { + async count(filter?: Filter): Promise { if (filter !== undefined) { return (await this.find(filter)).length; } diff --git a/src/databases/memory/database.ts b/src/databases/memory/database.ts index 30be11e..b8446f7 100644 --- a/src/databases/memory/database.ts +++ b/src/databases/memory/database.ts @@ -1,6 +1,6 @@ import { Collection } from "../../collection.ts"; -import { Document } from "../../types.ts"; -import { Registrars } from "../registrars.ts"; +import type { Document } from "../../types.ts"; +import type { Registrars } from "../registrars.ts"; import { MemoryStorage } from "./storage.ts"; type Options = { diff --git a/src/databases/memory/storage.ts b/src/databases/memory/storage.ts index dbd7a83..0c23904 100644 --- a/src/databases/memory/storage.ts +++ b/src/databases/memory/storage.ts @@ -1,145 +1,155 @@ -import { createUpdater, Query } from "mingo"; -import { UpdateOptions } from "mingo/core"; -import { UpdateExpression } from "mingo/updater"; +import { Query, update } from "mingo"; +import type { AnyObject } from "mingo/types"; -import { DuplicateDocumentError } from "../../storage/errors.ts"; +import { getDocumentWithPrimaryKey } from "../../primary-key.ts"; +import { Collections } from "../../storage/collections.ts"; +import type { UpdatePayload } from "../../storage/mod.ts"; +import type { InsertResult } from "../../storage/operators/insert.ts"; +import type { UpdateResult } from "../../storage/operators/update.ts"; import { - getInsertManyResult, - getInsertOneResult, - type InsertManyResult, - type InsertOneResult, -} from "../../storage/operators/insert.ts"; -import { RemoveResult } from "../../storage/operators/remove.ts"; -import { UpdateResult } from "../../storage/operators/update.ts"; -import { addOptions, Options, Storage } from "../../storage/storage.ts"; -import type { Document, Filter, WithId } from "../../types.ts"; + addOptions, + type CountPayload, + type FindByIdPayload, + type FindPayload, + type InsertManyPayload, + type InsertOnePayload, + type RemovePayload, + type ReplacePayload, + Storage, +} from "../../storage/storage.ts"; +import type { AnyDocument } from "../../types.ts"; -const update = createUpdater({ cloneMode: "deep" }); - -export class MemoryStorage extends Storage { - readonly #documents = new Map>(); +export class MemoryStorage extends Storage { + readonly #collections = new Collections(); async resolve() { return this; } - async has(id: string): Promise { - return this.#documents.has(id); - } + async insertOne({ pkey, values, ...payload }: InsertOnePayload): Promise { + const collection = this.#collections.get(payload.collection); - async insertOne(data: Partial): Promise { - const document = { ...data, id: data.id ?? crypto.randomUUID() } as WithId; - if (await this.has(document.id)) { - throw new DuplicateDocumentError(document, this as any); + const document = getDocumentWithPrimaryKey(pkey, values); + if (collection.has(document[pkey])) { + return { insertCount: 0, insertIds: [] }; } - this.#documents.set(document.id, document); + + collection.set(document[pkey], document); this.broadcast("insertOne", document); - return getInsertOneResult(document); + + return { insertCount: 1, insertIds: [document[pkey]] }; } - async insertMany(documents: Partial[]): Promise { - const result: TSchema[] = []; - for (const data of documents) { - const document = { ...data, id: data.id ?? crypto.randomUUID() } as WithId; - result.push(document); - this.#documents.set(document.id, document); + async insertMany({ pkey, values, ...payload }: InsertManyPayload): Promise { + const collection = this.#collections.get(payload.collection); + + const documents: AnyDocument[] = []; + for (const insert of values) { + const document = getDocumentWithPrimaryKey(pkey, insert); + if (collection.has(document[pkey])) { + continue; + } + collection.set(document[pkey], document); + documents.push(document); } - this.broadcast("insertMany", result); + if (documents.length > 0) { + this.broadcast("insertMany", documents); + } - return getInsertManyResult(result); + return { insertCount: documents.length, insertIds: documents.map((document) => document[pkey]) }; } - async findById(id: string): Promise | undefined> { - return this.#documents.get(id); + async findById({ collection, id }: FindByIdPayload): Promise { + return this.#collections.get(collection).get(id); } - async find(filter?: Filter>, options?: Options): Promise[]> { - let cursor = new Query(filter ?? {}).find(Array.from(this.#documents.values())); + async find({ condition = {}, options, ...payload }: FindPayload): Promise { + let cursor = new Query(condition).find(this.#collections.documents(payload.collection)); if (options !== undefined) { cursor = addOptions(cursor, options); } - return cursor.all() as WithId[]; + return cursor.all(); } - async updateOne( - filter: Filter>, - expr: UpdateExpression, - arrayFilters?: Filter>[], - condition?: Filter>, - options?: UpdateOptions, - ): Promise { - for (const document of await this.find(filter)) { - const modified = update(document, expr, arrayFilters, condition, options); - if (modified.length > 0) { - this.#documents.set(document.id, document); - this.broadcast("updateOne", document); - return new UpdateResult(1, 1); - } - return new UpdateResult(1, 0); - } - return new UpdateResult(0, 0); - } - - async updateMany( - filter: Filter>, - expr: UpdateExpression, - arrayFilters?: Filter>[], - condition?: Filter>, - options?: UpdateOptions, - ): Promise { - const documents: WithId[] = []; + async updateOne({ pkey, condition, modifier, arrayFilters, ...payload }: UpdatePayload): Promise { + const collection = this.#collections.get(payload.collection); let matchedCount = 0; let modifiedCount = 0; - for (const document of await this.find(filter)) { + for (const document of await this.find({ collection: payload.collection, condition, options: { limit: 1 } })) { + const modified = update(document, modifier, arrayFilters, undefined, { cloneMode: "deep" }); + if (modified.length > 0) { + collection.set(document[pkey], document); + this.broadcast("updateOne", document); + modifiedCount += 1; + } matchedCount += 1; - const modified = update(document, expr, arrayFilters, condition, options); + } + return { matchedCount, modifiedCount }; + } + + async updateMany({ pkey, condition, modifier, arrayFilters, ...payload }: UpdatePayload): Promise { + const collection = this.#collections.get(payload.collection); + + const documents: AnyDocument[] = []; + + let matchedCount = 0; + let modifiedCount = 0; + + for (const document of await this.find({ collection: payload.collection, condition })) { + matchedCount += 1; + const modified = update(document, modifier, arrayFilters, undefined, { cloneMode: "deep" }); if (modified.length > 0) { modifiedCount += 1; documents.push(document); - this.#documents.set(document.id, document); + collection.set(document[pkey], document); } } this.broadcast("updateMany", documents); - return new UpdateResult(matchedCount, modifiedCount); + return { matchedCount, modifiedCount }; } - async replace(filter: Filter>, document: WithId): Promise { - const documents: WithId[] = []; + async replace({ pkey, condition, document, ...payload }: ReplacePayload): Promise { + const collection = this.#collections.get(payload.collection); let matchedCount = 0; let modifiedCount = 0; - for (const current of await this.find(filter)) { + const documents: AnyDocument[] = []; + for (const current of await this.find({ collection: payload.collection, condition })) { matchedCount += 1; modifiedCount += 1; documents.push(document); - this.#documents.set(current.id, document); + collection.set(current[pkey], document); } this.broadcast("updateMany", documents); - return new UpdateResult(matchedCount, modifiedCount); + return { matchedCount, modifiedCount }; } - async remove(filter: Filter>): Promise { - const documents = await this.find(filter); + async remove({ pkey, condition, ...payload }: RemovePayload): Promise { + const collection = this.#collections.get(payload.collection); + + const documents = await this.find({ collection: payload.collection, condition }); for (const document of documents) { - this.#documents.delete(document.id); + collection.delete(document[pkey]); } + this.broadcast("remove", documents); - return new RemoveResult(documents.length); + + return documents.length; } - async count(filter?: Filter>): Promise { - return new Query(filter ?? {}).find(Array.from(this.#documents.values())).count(); + async count({ collection, condition = {} }: CountPayload): Promise { + return new Query(condition).find(this.#collections.documents(collection)).all().length; } async flush(): Promise { - this.#documents.clear(); + this.#collections.flush(); } } diff --git a/src/databases/observer/storage.ts b/src/databases/observer/storage.ts index ea8911c..e7fb36a 100644 --- a/src/databases/observer/storage.ts +++ b/src/databases/observer/storage.ts @@ -1,23 +1,16 @@ -import { createUpdater, Query } from "mingo"; -import { UpdateOptions } from "mingo/core"; -import { UpdateExpression } from "mingo/updater"; +import { Query, update } from "mingo"; +import type { Criteria, Options } from "mingo/types"; +import type { CloneMode, Modifier } from "mingo/updater"; +import { getDocumentWithPrimaryKey } from "../../primary-key.ts"; import { DuplicateDocumentError } from "../../storage/errors.ts"; -import { - getInsertManyResult, - getInsertOneResult, - InsertManyResult, - InsertOneResult, -} from "../../storage/operators/insert.ts"; -import { RemoveResult } from "../../storage/operators/remove.ts"; +import type { InsertResult } from "../../storage/operators/insert.ts"; import { UpdateResult } from "../../storage/operators/update.ts"; -import { addOptions, Options, Storage } from "../../storage/storage.ts"; -import { Document, Filter, WithId } from "../../types.ts"; +import { addOptions, type QueryOptions, Storage } from "../../storage/storage.ts"; +import type { AnyDocument } from "../../types.ts"; -const update = createUpdater({ cloneMode: "deep" }); - -export class ObserverStorage extends Storage { - readonly #documents = new Map>(); +export class ObserverStorage extends Storage { + readonly #documents = new Map(); async resolve() { return this; @@ -27,48 +20,48 @@ export class ObserverStorage extends Storag return this.#documents.has(id); } - async insertOne(data: Partial): Promise { - const document = { ...data, id: data.id ?? crypto.randomUUID() } as WithId; - if (await this.has(document.id)) { + async insertOne(values: AnyDocument): Promise { + const document = getDocumentWithPrimaryKey(this.primaryKey, values); + if (await this.has(document[this.primaryKey])) { throw new DuplicateDocumentError(document, this as any); } - this.#documents.set(document.id, document); + this.#documents.set(document[this.primaryKey], document); return getInsertOneResult(document); } - async insertMany(documents: Partial[]): Promise { + async insertMany(list: TSchema[]): Promise { const result: TSchema[] = []; - for (const data of documents) { - const document = { ...data, id: data.id ?? crypto.randomUUID() } as WithId; + for (const values of list) { + const document = getDocumentWithPrimaryKey(this.primaryKey, values); result.push(document); this.#documents.set(document.id, document); } return getInsertManyResult(result); } - async findById(id: string): Promise | undefined> { + async findById(id: string): Promise { return this.#documents.get(id); } - async find(filter?: Filter>, options?: Options): Promise[]> { + async find(filter?: Filter, options?: QueryOptions): Promise { let cursor = new Query(filter ?? {}).find(Array.from(this.#documents.values())); if (options !== undefined) { cursor = addOptions(cursor, options); } - return cursor.all() as WithId[]; + return cursor.all(); } async updateOne( - filter: Filter>, - expr: UpdateExpression, - arrayFilters?: Filter>[], - condition?: Filter>, - options?: UpdateOptions, + filter: Filter, + modifier: Modifier, + arrayFilters?: Filter[], + condition?: Criteria, + options: { cloneMode?: CloneMode; queryOptions?: Partial } = { cloneMode: "deep" }, ): Promise { const query = new Query(filter); for (const document of Array.from(this.#documents.values())) { if (query.test(document) === true) { - const modified = update(document, expr, arrayFilters, condition, options); + const modified = update(document, modifier, arrayFilters, condition, options); if (modified.length > 0) { this.#documents.set(document.id, document); this.broadcast("updateOne", document); @@ -81,15 +74,15 @@ export class ObserverStorage extends Storag } async updateMany( - filter: Filter>, - expr: UpdateExpression, - arrayFilters?: Filter>[], - condition?: Filter>, - options?: UpdateOptions, + filter: Filter, + modifier: Modifier, + arrayFilters?: Filter[], + condition?: Criteria, + options: { cloneMode?: CloneMode; queryOptions?: Partial } = { cloneMode: "deep" }, ): Promise { const query = new Query(filter); - const documents: WithId[] = []; + const documents: TSchema[] = []; let matchedCount = 0; let modifiedCount = 0; @@ -97,7 +90,7 @@ export class ObserverStorage extends Storag for (const document of Array.from(this.#documents.values())) { if (query.test(document) === true) { matchedCount += 1; - const modified = update(filter, expr, arrayFilters, condition, options); + const modified = update(document, modifier, arrayFilters, condition, options); if (modified.length > 0) { modifiedCount += 1; documents.push(document); @@ -111,10 +104,10 @@ export class ObserverStorage extends Storag return new UpdateResult(matchedCount, modifiedCount); } - async replace(filter: Filter>, document: WithId): Promise { + async replace(filter: Filter, document: TSchema): Promise { const query = new Query(filter); - const documents: WithId[] = []; + const documents: TSchema[] = []; let matchedCount = 0; let modifiedCount = 0; @@ -131,7 +124,7 @@ export class ObserverStorage extends Storag return new UpdateResult(matchedCount, modifiedCount); } - async remove(filter: Filter>): Promise { + async remove(filter: Filter): Promise { const documents = Array.from(this.#documents.values()); const query = new Query(filter); let count = 0; @@ -144,8 +137,8 @@ export class ObserverStorage extends Storag return new RemoveResult(count); } - async count(filter?: Filter>): Promise { - return new Query(filter ?? {}).find(Array.from(this.#documents.values())).count(); + async count(filter?: Filter): Promise { + return new Query(filter ?? {}).find(Array.from(this.#documents.values())).all().length; } async flush(): Promise { diff --git a/src/databases/registrars.ts b/src/databases/registrars.ts index f349f9f..1740ad6 100644 --- a/src/databases/registrars.ts +++ b/src/databases/registrars.ts @@ -1,6 +1,23 @@ export type Registrars = { + /** + * Name of the collection. + */ name: string; + + /** + * Set the primary key of the collection. + * Default: "id" + */ + primaryKey?: string; + + /** + * List of custom indexes for the collection. + */ indexes?: Index[]; }; -type Index = [string, any?]; +type Index = [IndexKey, IndexOptions?]; + +type IndexKey = string; + +type IndexOptions = { unique: boolean }; diff --git a/src/observe/observe-one.ts b/src/observe/observe-one.ts index ef3399d..775324d 100644 --- a/src/observe/observe-one.ts +++ b/src/observe/observe-one.ts @@ -1,5 +1,5 @@ -import { Collection } from "../collection.ts"; -import { Document, Filter, WithId } from "../types.ts"; +import type { Collection } from "../collection.ts"; +import type { Document, Filter, WithId } from "../types.ts"; import { isMatch } from "./is-match.ts"; export function observeOne( diff --git a/src/observe/observe.ts b/src/observe/observe.ts index 6afaf8c..560cef1 100644 --- a/src/observe/observe.ts +++ b/src/observe/observe.ts @@ -1,23 +1,24 @@ import { Query } from "mingo"; +import type { AnyObject, Criteria } from "mingo/types"; -import { Collection } from "../collection.ts"; -import { addOptions, ChangeEvent, Options } from "../storage/mod.ts"; -import { Document, Filter, WithId } from "../types.ts"; +import type { Collection } from "../collection.ts"; +import { addOptions, type ChangeEvent, type QueryOptions } from "../storage/mod.ts"; +import type { AnyDocument } from "../types.ts"; import { Store } from "./store.ts"; -export function observe( - collection: Collection, - filter: Filter>, - options: Options | undefined, - onChange: (documents: WithId[], changed: WithId[], type: ChangeEvent["type"]) => void, +export function observe( + collection: TCollection, + condition: Criteria, + options: QueryOptions | undefined, + onChange: (documents: TSchema[], changed: TSchema[], type: ChangeEvent["type"]) => void, ): { unsubscribe: () => void; } { - const store = Store.create(); + const store = Store.create(); let debounce: any; - collection.find(filter, options).then(async (documents) => { + collection.find(condition, options).then(async (documents) => { const resolved = await store.resolve(documents); onChange(resolved, resolved, "insertMany"); }); @@ -29,17 +30,17 @@ export function observe( onChange([], [], "remove"); }), collection.observable.change.subscribe(async ({ type, data }) => { - let changed: WithId[] = []; + let changed: AnyObject[] = []; switch (type) { case "insertOne": case "updateOne": { - changed = await store[type](data, filter); + changed = await store[type](data, condition); break; } case "insertMany": case "updateMany": case "remove": { - changed = await store[type](data, filter); + changed = await store[type](data, condition); break; } } @@ -64,12 +65,9 @@ export function observe( }; } -function applyQueryOptions( - documents: WithId[], - options?: Options, -): WithId[] { +function applyQueryOptions(documents: AnyDocument[], options?: QueryOptions): AnyDocument[] { if (options !== undefined) { - return addOptions(new Query({}).find(documents), options).all() as WithId[]; + return addOptions(new Query({}).find(documents), options).all(); } return documents; } diff --git a/src/observe/store.ts b/src/observe/store.ts index 419b77a..39ad9a7 100644 --- a/src/observe/store.ts +++ b/src/observe/store.ts @@ -1,29 +1,29 @@ import { ObserverStorage } from "../databases/observer/storage.ts"; -import { Storage } from "../storage/mod.ts"; -import { Document, Filter, WithId } from "../types.ts"; +import type { Storage } from "../storage/mod.ts"; +import type { AnyDocument } from "../types.ts"; import { isMatch } from "./is-match.ts"; -export class Store { - private constructor(private storage: Storage) {} +export class Store { + private constructor(private storage: Storage) {} - static create() { - return new Store(new ObserverStorage(`observer[${crypto.randomUUID()}]`)); + static create() { + return new Store(new ObserverStorage(`observer[${crypto.randomUUID()}]`)); } get destroy() { return this.storage.destroy.bind(this.storage); } - async resolve(documents: WithId[]): Promise[]> { + async resolve(documents: AnyDocument[]): Promise { await this.storage.insertMany(documents); return this.getDocuments(); } - async getDocuments(): Promise[]> { + async getDocuments(): Promise { return this.storage.find(); } - async insertMany(documents: WithId[], filter: Filter>): Promise[]> { + async insertMany(documents: AnyDocument[], filter: Filter): Promise { const matched = []; for (const document of documents) { matched.push(...(await this.insertOne(document, filter))); @@ -31,15 +31,15 @@ export class Store { return matched; } - async insertOne(document: WithId, filter: Filter>): Promise[]> { - if (isMatch(document, filter)) { + async insertOne(document: AnyDocument, filter: Filter): Promise { + if (isMatch(document, filter)) { await this.storage.insertOne(document); return [document]; } return []; } - async updateMany(documents: WithId[], filter: Filter>): Promise[]> { + async updateMany(documents: AnyDocument[], filter: Filter): Promise { const matched = []; for (const document of documents) { matched.push(...(await this.updateOne(document, filter))); @@ -47,33 +47,33 @@ export class Store { return matched; } - async updateOne(document: WithId, filter: Filter>): Promise[]> { + async updateOne(document: AnyDocument, filter: Filter): Promise { if (await this.storage.has(document.id)) { await this.#updateOrRemove(document, filter); return [document]; - } else if (isMatch(document, filter)) { + } else if (isMatch(document, filter)) { await this.storage.insertOne(document); return [document]; } return []; } - async remove(documents: WithId[]): Promise[]> { + async remove(documents: AnyDocument[]): Promise { const matched = []; for (const document of documents) { - if (isMatch(document, { id: document.id } as WithId)) { - await this.storage.remove({ id: document.id } as WithId); + if (isMatch(document, { id: document.id } as AnyDocument)) { + await this.storage.remove({ id: document.id } as AnyDocument); matched.push(document); } } return matched; } - async #updateOrRemove(document: WithId, filter: Filter>): Promise { - if (isMatch(document, filter)) { - await this.storage.replace({ id: document.id } as WithId, document); + async #updateOrRemove(document: AnyDocument, filter: Filter): Promise { + if (isMatch(document, filter)) { + await this.storage.replace({ id: document.id } as AnyDocument, document); } else { - await this.storage.remove({ id: document.id } as WithId); + await this.storage.remove({ id: document.id } as AnyDocument); } } diff --git a/src/primary-key.ts b/src/primary-key.ts new file mode 100644 index 0000000..6de3d64 --- /dev/null +++ b/src/primary-key.ts @@ -0,0 +1,8 @@ +import type { AnyDocument } from "./types.ts"; + +export function getDocumentWithPrimaryKey(pkey: TPKey, document: AnyDocument): AnyDocument { + if (Object.hasOwn(document, pkey) === true) { + return document; + } + return { [pkey]: crypto.randomUUID(), ...document }; +} diff --git a/src/storage/collections.ts b/src/storage/collections.ts new file mode 100644 index 0000000..4b15b5f --- /dev/null +++ b/src/storage/collections.ts @@ -0,0 +1,33 @@ +import type { AnyObject } from "mingo/types"; + +import { CollectionNotFoundError } from "./errors.ts"; + +export class Collections { + #collections = new Map(); + + has(name: string): boolean { + return this.#collections.has(name); + } + + documents(name: string): AnyObject[] { + return Array.from(this.get(name).values()); + } + + get(name: string): Documents { + const collection = this.#collections.get(name); + if (collection === undefined) { + throw new CollectionNotFoundError(name); + } + return collection; + } + + delete(name: string): boolean { + return this.#collections.delete(name); + } + + flush() { + this.#collections.clear(); + } +} + +type Documents = Map; diff --git a/src/storage/errors.ts b/src/storage/errors.ts index e2836cb..08b1171 100644 --- a/src/storage/errors.ts +++ b/src/storage/errors.ts @@ -1,25 +1,28 @@ -import { RawObject } from "mingo/types"; - -import { Document } from "../types.ts"; -import type { Storage } from "./storage.ts"; +import type { AnyObject } from "mingo/types"; export class DuplicateDocumentError extends Error { readonly type = "DuplicateDocumentError"; constructor( - readonly document: Document, - storage: Storage, + readonly collection: string, + readonly document: AnyObject, ) { - super( - `Collection Insert Violation: Document '${document.id}' already exists in ${storage.name} collection ${storage.id}`, - ); + super(`Collection Insert Violation: Document '${document.id}' already exists in '${collection}' collection`); + } +} + +export class CollectionNotFoundError extends Error { + readonly type = "CollectionNotFoundError"; + + constructor(readonly collection: string) { + super(`Collection Retrieve Violation: Collection '${collection}' does not exist`); } } export class DocumentNotFoundError extends Error { readonly type = "DocumentNotFoundError"; - constructor(readonly criteria: RawObject) { + constructor(readonly criteria: AnyObject) { super(`Collection Update Violation: Document matching criteria does not exists`); } } diff --git a/src/storage/operators/insert.ts b/src/storage/operators/insert.ts index a333afd..69e8505 100644 --- a/src/storage/operators/insert.ts +++ b/src/storage/operators/insert.ts @@ -1,40 +1,4 @@ -import type { Document } from "../../types.ts"; - -export function getInsertManyResult(documents: Document[]): InsertManyResult { - return { - acknowledged: true, - insertedCount: documents.length, - insertedIds: documents.reduce<{ [key: number]: string | number }>((map, document, index) => { - map[index] = document.id; - return map; - }, {}), - }; -} - -export function getInsertOneResult(document: Document): InsertOneResult { - return { - acknowledged: true, - insertedId: document.id, - }; -} - -export type InsertManyResult = - | { - acknowledged: false; - } - | { - acknowledged: true; - insertedCount: number; - insertedIds: { - [key: number]: string | number; - }; - }; - -export type InsertOneResult = - | { - acknowledged: false; - } - | { - acknowledged: true; - insertedId: string | number; - }; +export type InsertResult = { + insertCount: number; + insertIds: (string | number | symbol)[]; +}; diff --git a/src/storage/operators/remove.ts b/src/storage/operators/remove.ts deleted file mode 100644 index 77818b8..0000000 --- a/src/storage/operators/remove.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class RemoveResult { - constructor(readonly matched = 0) {} -} diff --git a/src/storage/operators/update.ts b/src/storage/operators/update.ts index fab37d8..be0fa0c 100644 --- a/src/storage/operators/update.ts +++ b/src/storage/operators/update.ts @@ -1,6 +1,4 @@ -export class UpdateResult { - constructor( - readonly matched = 0, - readonly modified = 0, - ) {} -} +export type UpdateResult = { + matchedCount: number; + modifiedCount: number; +}; diff --git a/src/storage/storage.ts b/src/storage/storage.ts index dec0f39..2c6ff5f 100644 --- a/src/storage/storage.ts +++ b/src/storage/storage.ts @@ -1,20 +1,19 @@ -import { UpdateOptions } from "mingo/core"; -import { Cursor } from "mingo/cursor"; -import { UpdateExpression } from "mingo/updater"; +import type { Cursor } from "mingo/cursor"; +import type { AnyObject, Criteria } from "mingo/types"; +import type { Modifier } from "mingo/updater"; import { Subject } from "rxjs"; -import { BroadcastChannel, StorageBroadcast } from "../broadcast.ts"; -import { Document, Filter, WithId } from "../types.ts"; -import { InsertManyResult, InsertOneResult } from "./operators/insert.ts"; -import { RemoveResult } from "./operators/remove.ts"; -import { UpdateResult } from "./operators/update.ts"; +import { BroadcastChannel, type StorageBroadcast } from "../broadcast.ts"; +import type { Prettify } from "../types.ts"; +import type { InsertResult } from "./operators/insert.ts"; +import type { UpdateResult } from "./operators/update.ts"; -export abstract class Storage { +export abstract class Storage { readonly observable: { - change: Subject>; + change: Subject; flush: Subject; } = { - change: new Subject>(), + change: new Subject(), flush: new Subject(), }; @@ -22,12 +21,9 @@ export abstract class Storage { readonly #channel: BroadcastChannel; - constructor( - readonly name: string, - readonly id: string = crypto.randomUUID(), - ) { + constructor(readonly name: string) { this.#channel = new BroadcastChannel(`valkyr:db:${name}`); - this.#channel.onmessage = ({ data }: MessageEvent>) => { + this.#channel.onmessage = ({ data }: MessageEvent) => { if (data.name !== this.name) { return; } @@ -72,7 +68,7 @@ export abstract class Storage { | */ - broadcast(type: StorageBroadcast["type"], data?: TSchema | TSchema[]): void { + broadcast(type: StorageBroadcast["type"], data?: AnyObject | AnyObject[]): void { switch (type) { case "flush": { this.observable.flush.next(); @@ -92,37 +88,23 @@ export abstract class Storage { |-------------------------------------------------------------------------------- */ - abstract has(id: string): Promise; + abstract insertOne(payload: InsertOnePayload): Promise; - abstract insertOne(document: Partial>): Promise; + abstract insertMany(payload: InsertManyPayload): Promise; - abstract insertMany(documents: Partial>[]): Promise; + abstract findById(payload: FindByIdPayload): Promise; - abstract findById(id: string): Promise | undefined>; + abstract find(payload: FindPayload): Promise; - abstract find(filter?: Filter>, options?: Options): Promise[]>; + abstract updateOne(payload: UpdatePayload): Promise; - abstract updateOne( - filter: Filter>, - expr: UpdateExpression, - arrayFilters?: Filter>[], - condition?: Filter>, - options?: UpdateOptions, - ): Promise; + abstract updateMany(payload: UpdatePayload): Promise; - abstract updateMany( - filter: Filter>, - expr: UpdateExpression, - arrayFilters?: Filter>[], - condition?: Filter>, - options?: UpdateOptions, - ): Promise; + abstract replace(payload: ReplacePayload): Promise; - abstract replace(filter: Filter>, document: TSchema): Promise; + abstract remove(payload: RemovePayload): Promise; - abstract remove(filter: Filter>): Promise; - - abstract count(filter?: Filter>): Promise; + abstract count(payload: CountPayload): Promise; abstract flush(): Promise; @@ -143,9 +125,9 @@ export abstract class Storage { |-------------------------------------------------------------------------------- */ -export function addOptions( +export function addOptions( cursor: Cursor, - options: Options, + options: QueryOptions, ): Cursor { if (options.sort) { cursor.sort(options.sort); @@ -167,17 +149,17 @@ export function addOptions( type Status = "loading" | "ready"; -export type ChangeEvent = +export type ChangeEvent = | { type: "insertOne" | "updateOne"; - data: WithId; + data: AnyObject; } | { type: "insertMany" | "updateMany" | "remove"; - data: WithId[]; + data: AnyObject[]; }; -export type Options = { +export type QueryOptions = { sort?: { [key: string]: 1 | -1; }; @@ -197,3 +179,68 @@ export type Options = { export type Index = { [index: string]: any; }; + +export type InsertOnePayload = Prettify< + CollectionPayload & + PrimaryKeyPayload & { + values: AnyObject; + } +>; + +export type InsertManyPayload = Prettify< + CollectionPayload & + PrimaryKeyPayload & { + values: AnyObject[]; + } +>; + +export type FindByIdPayload = Prettify< + CollectionPayload & { + id: string; + } +>; + +export type FindPayload = Prettify< + CollectionPayload & { + condition?: Criteria; + options?: QueryOptions; + } +>; + +export type UpdatePayload = Prettify< + CollectionPayload & + PrimaryKeyPayload & { + condition: Criteria; + modifier: Modifier; + arrayFilters?: AnyObject[]; + } +>; + +export type ReplacePayload = Prettify< + CollectionPayload & + PrimaryKeyPayload & { + condition: Criteria; + document: AnyObject; + } +>; + +export type RemovePayload = Prettify< + CollectionPayload & + PrimaryKeyPayload & { + condition: Criteria; + } +>; + +export type CountPayload = Prettify< + CollectionPayload & { + condition?: Criteria; + } +>; + +type CollectionPayload = { + collection: string; +}; + +type PrimaryKeyPayload = { + pkey: string; +}; diff --git a/src/types.ts b/src/types.ts index f2bc496..b88a8ac 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,12 +1,8 @@ import type { BSONRegExp, BSONType } from "bson"; -export type Document = { - [key: string]: any; -}; +export type Prettify = { [K in keyof T]: T[K] } & {}; -export type WithId = { - id: string; -} & TSchema; +export type AnyDocument = Record; export type Filter = { [P in keyof TSchema]?: Condition; @@ -129,12 +125,11 @@ type Flatten = Type extends ReadonlyArray ? Item : Type; type IsAny = true extends false & Type ? ResultIfAny : ResultIfNotAny; -type FilterOperations = - T extends Record - ? { - [key in keyof T]?: FilterOperators; - } - : FilterOperators; +type FilterOperations = T extends Record + ? { + [key in keyof T]?: FilterOperators; + } + : FilterOperators; type ArrayOperator = { $each?: Array>; diff --git a/tests/cache.test.ts b/tests/cache.test.ts index 119e01d..b540145 100644 --- a/tests/cache.test.ts +++ b/tests/cache.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, it } from "@std/testing/bdd"; import { expect } from "expect"; import { IndexedDbCache } from "../src/databases/indexeddb/cache.ts"; -import { Options } from "../src/storage/storage.ts"; +import type { Options } from "../src/storage/storage.ts"; import type { WithId } from "../src/types.ts"; describe("IndexedDbCache", () => { diff --git a/tests/collection.test.ts b/tests/collection.test.ts index be30e7b..b29aeef 100644 --- a/tests/collection.test.ts +++ b/tests/collection.test.ts @@ -3,7 +3,7 @@ import { expect } from "expect"; import { Collection } from "../src/collection.ts"; import { MemoryStorage } from "../src/databases/memory/storage.ts"; -import { getUsers, UserDocument } from "./users.mock.ts"; +import { getUsers, type UserDocument } from "./users.mock.ts"; /* |-------------------------------------------------------------------------------- diff --git a/tests/hash.test.ts b/tests/hash.test.ts index fe6eed2..7978764 100644 --- a/tests/hash.test.ts +++ b/tests/hash.test.ts @@ -2,7 +2,7 @@ import { describe, it } from "@std/testing/bdd"; import { expect } from "expect"; import { hashCodeQuery } from "../src/hash.ts"; -import { Options } from "../src/mod.ts"; +import type { Options } from "../src/mod.ts"; describe("hashCodeQuery", () => { const filter = { name: { $eq: "Document 1" } }; diff --git a/tests/update.test.ts b/tests/update.test.ts index da8dc6a..3c3b5d5 100644 --- a/tests/update.test.ts +++ b/tests/update.test.ts @@ -1,5 +1,6 @@ import { describe, it } from "@std/testing/bdd"; import { expect } from "expect"; +import z from "zod"; import { Collection } from "../src/collection.ts"; import { MemoryStorage } from "../src/databases/memory/storage.ts"; @@ -13,20 +14,28 @@ describe("Field Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/set/#set-top-level-fields */ it("should set top level fields", async () => { - const collection = new Collection<{ - quantity: number; - instock: boolean; - reorder: boolean; - details: { - model: string; - make: string; - }; - tags: string[]; - ratings: { - by: string; - rating: number; - }[]; - }>("tests", new MemoryStorage("tests")); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id" as const, + schema: { + id: z.string(), + quantity: z.number(), + instock: z.boolean(), + reorder: z.boolean(), + details: z.object({ + model: z.string(), + make: z.string(), + }), + tags: z.array(z.string()), + ratings: z.array( + z.object({ + by: z.string(), + rating: z.number(), + }), + ), + }, + }); await collection.insertOne({ id: "100", @@ -73,20 +82,28 @@ describe("Field Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/set/#set-fields-in-embedded-documents */ it("should set fields in the embedded documents", async () => { - const collection = new Collection<{ - quantity: number; - instock: boolean; - reorder: boolean; - details: { - model: string; - make: string; - }; - tags: string[]; - ratings: { - by: string; - rating: number; - }[]; - }>("tests", new MemoryStorage("tests")); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + quantity: z.number(), + instock: z.boolean(), + reorder: z.boolean(), + details: z.object({ + model: z.string(), + make: z.string(), + }), + tags: z.array(z.string()), + ratings: z.array( + z.object({ + by: z.string(), + rating: z.number(), + }), + ), + }, + }); await collection.insertOne({ id: "100", @@ -131,20 +148,28 @@ describe("Field Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/set/#set-elements-in-arrays */ it("should set elements in arrays", async () => { - const collection = new Collection<{ - quantity: number; - instock: boolean; - reorder: boolean; - details: { - model: string; - make: string; - }; - tags: string[]; - ratings: { - by: string; - rating: number; - }[]; - }>("tests", new MemoryStorage("tests")); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + quantity: z.number(), + instock: z.boolean(), + reorder: z.boolean(), + details: z.object({ + model: z.string(), + make: z.string(), + }), + tags: z.array(z.string()), + ratings: z.array( + z.object({ + by: z.string(), + rating: z.number(), + }), + ), + }, + }); await collection.insertOne({ id: "100", @@ -189,12 +214,18 @@ describe("Field Update Operators", () => { describe("$unset", () => { it("should unset keys", async () => { - const collection = new Collection<{ - item: string; - sku: string; - quantity: number; - instock: boolean; - }>("tests", new MemoryStorage("tests")); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + item: z.string(), + sku: z.string(), + quantity: z.number(), + instock: z.boolean(), + }, + }); await collection.insertMany([ { id: "1", item: "chisel", sku: "C001", quantity: 4, instock: true }, @@ -252,10 +283,20 @@ describe("Field Update Operators", () => { describe("Array Update Operators", () => { describe("$(update)", () => { it("should replace a object in an array", async () => { - const collection = new Collection<{ grades: { id: string; value: number }[] }>( - "students", - new MemoryStorage("students"), - ); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + grades: z.array( + z.object({ + id: z.string(), + value: z.number(), + }), + ), + }, + }); await collection.insertOne({ id: "1", @@ -319,7 +360,7 @@ describe("Array Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/positional/#update-values-in-an-array */ // it("should update values in an array", async () => { - // const collection = new Collection<{ grades: number[] }>("students", new MemoryStorage("students")); + // const collection = new Collection<"id", { grades: number[] }>("students", new MemoryStorage("students", "id")); // await collection.insertMany([ // { id: "1", grades: [85, 80, 80] }, @@ -358,9 +399,9 @@ describe("Array Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/positional/#update-documents-in-an-array */ // it("should update documents in an array", async () => { - // const collection = new Collection<{ grades: { grade: number; mean: number; std: number }[] }>( + // const collection = new Collection<"id", { grades: { grade: number; mean: number; std: number }[] }>( // "students", - // new MemoryStorage("students"), + // new MemoryStorage("students", "id"), // ); // await collection.insertOne({ @@ -406,9 +447,9 @@ describe("Array Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/positional/#update-embedded-documents-using-multiple-field-matches */ // it("should update embedded documents using multiple field matches", async () => { - // const collection = new Collection<{ grades: { grade: number; mean: number; std: number }[] }>( + // const collection = new Collection<"id", { grades: { grade: number; mean: number; std: number }[] }>( // "students", - // new MemoryStorage("students"), + // new MemoryStorage("students", "id"), // ); // await collection.insertOne({ @@ -464,10 +505,16 @@ describe("Array Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/pull/#remove-all-items-that-equal-a-specified-value */ it("should remove all items that equal a specified value", async () => { - const collection = new Collection<{ fruits: string[]; vegetables: string[] }>( - "stores", - new MemoryStorage("stores"), - ); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + fruits: z.array(z.string()), + vegetables: z.array(z.string()), + }, + }); await collection.insertMany([ { @@ -519,7 +566,15 @@ describe("Array Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/pull/#remove-all-items-that-match-a-specified--pull-condition */ it("should remove all items that match a specific $pull condition", async () => { - const collection = new Collection<{ votes: number[] }>("profiles", new MemoryStorage("profiles")); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + votes: z.array(z.number()), + }, + }); await collection.insertOne({ id: "1", votes: [3, 5, 6, 7, 7, 8] }); @@ -548,10 +603,20 @@ describe("Array Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/pull/#remove-items-from-an-array-of-documents */ it("should remove items from an array of documents", async () => { - const collection = new Collection<{ results: { item: string; score: number }[] }>( - "surveys", - new MemoryStorage("surveys"), - ); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + results: z.array( + z.object({ + item: z.string(), + score: z.number(), + }), + ), + }, + }); await collection.insertMany([ { @@ -596,9 +661,26 @@ describe("Array Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/pull/#remove-documents-from-nested-arrays */ it("should remove documents from nested arrays", async () => { - const collection = new Collection<{ - results: { item: string; score: number; answers: { q: number; a: number }[] }[]; - }>("surveys", new MemoryStorage("surveys")); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + results: z.array( + z.object({ + item: z.string(), + score: z.number(), + answers: z.array( + z.object({ + q: z.number(), + a: z.number(), + }), + ), + }), + ), + }, + }); await collection.insertMany([ { @@ -699,7 +781,15 @@ describe("Array Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/push/#append-a-value-to-an-array */ it("should append a value to an array", async () => { - const collection = new Collection<{ scores: number[] }>("students", new MemoryStorage("students")); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + scores: z.array(z.number()), + }, + }); await collection.insertOne({ id: "1", scores: [44, 78, 38, 80] }); @@ -726,7 +816,15 @@ describe("Array Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/push/#append-a-value-to-arrays-in-multiple-documents */ it("should append a value to arrays in multiple documents", async () => { - const collection = new Collection<{ scores: number[] }>("students", new MemoryStorage("students")); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + scores: z.array(z.number()), + }, + }); await collection.insertMany([ { id: "1", scores: [44, 78, 38, 80, 89] }, @@ -763,7 +861,16 @@ describe("Array Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/push/#append-multiple-values-to-an-array */ it("should append multiple values to an array", async () => { - const collection = new Collection<{ name: string; scores: number[] }>("students", new MemoryStorage("students")); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + name: z.string(), + scores: z.array(z.number()), + }, + }); await collection.insertOne({ id: "1", name: "Joe", scores: [44, 78] }); @@ -792,10 +899,20 @@ describe("Array Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/push/#use--push-operator-with-multiple-modifiers */ it("should use $push operator with multiple modifiers", async () => { - const collection = new Collection<{ quizzes: { wk: number; score: number }[] }>( - "students", - new MemoryStorage("students"), - ); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + quizzes: z.array( + z.object({ + wk: z.number(), + score: z.number(), + }), + ), + }, + }); await collection.insertOne({ id: "5", @@ -845,7 +962,15 @@ describe("Array Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/slice/#slice-from-the-end-of-the-array */ it("should slice from the end of the array", async () => { - const collection = new Collection<{ scores: number[] }>("students", new MemoryStorage("students")); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + scores: z.array(z.number()), + }, + }); await collection.insertOne({ id: "1", scores: [40, 50, 60] }); @@ -875,7 +1000,15 @@ describe("Array Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/slice/#slice-from-the-front-of-the-array */ it("should slice from the front of the array", async () => { - const collection = new Collection<{ scores: number[] }>("students", new MemoryStorage("students")); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + scores: z.array(z.number()), + }, + }); await collection.insertOne({ id: "2", scores: [89, 90] }); @@ -905,7 +1038,15 @@ describe("Array Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/slice/#update-array-using-slice-only */ it("should update array using slice only", async () => { - const collection = new Collection<{ scores: number[] }>("students", new MemoryStorage("students")); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + scores: z.array(z.number()), + }, + }); await collection.insertOne({ id: "3", scores: [89, 70, 100, 20] }); @@ -935,7 +1076,15 @@ describe("Array Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/position/#add-elements-at-the-start-of-the-array */ it("should add elements to the start of the array", async () => { - const collection = new Collection<{ scores: number[] }>("students", new MemoryStorage("students")); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + scores: z.array(z.number()), + }, + }); await collection.insertOne({ id: "1", scores: [100] }); @@ -965,7 +1114,15 @@ describe("Array Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/position/#add-elements-to-the-middle-of-the-array */ it("should add elements to the middle of the array", async () => { - const collection = new Collection<{ scores: number[] }>("students", new MemoryStorage("students")); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + scores: z.array(z.number()), + }, + }); await collection.insertOne({ id: "2", scores: [50, 60, 70, 100] }); @@ -992,7 +1149,15 @@ describe("Array Update Operators", () => { }); it("should use a negative index to add elements to the array", async () => { - const collection = new Collection<{ scores: number[] }>("students", new MemoryStorage("students")); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + scores: z.array(z.number()), + }, + }); await collection.insertOne({ id: "3", scores: [50, 60, 20, 30, 70, 100] }); @@ -1022,10 +1187,20 @@ describe("Array Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/sort/#sort-array-of-documents-by-a-field-in-the-documents */ it("should sort array of documents by a field in the documents", async () => { - const collection = new Collection<{ quizzes: { id: number; score: number }[] }>( - "students", - new MemoryStorage("students"), - ); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + quizzes: z.array( + z.object({ + id: z.number(), + score: z.number(), + }), + ), + }, + }); await collection.insertOne({ id: "1", @@ -1074,7 +1249,15 @@ describe("Array Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/sort/#sort-array-elements-that-are-not-documents */ it("should sort array elements that are not documents", async () => { - const collection = new Collection<{ tests: number[] }>("students", new MemoryStorage("students")); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + tests: z.array(z.number()), + }, + }); await collection.insertOne({ id: "2", tests: [89, 70, 89, 50] }); @@ -1106,7 +1289,15 @@ describe("Array Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/sort/#update-array-using-sort-only */ it("should update array using sort only", async () => { - const collection = new Collection<{ tests: number[] }>("students", new MemoryStorage("students")); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + tests: z.array(z.number()), + }, + }); await collection.insertOne({ id: "3", tests: [89, 70, 100, 20] }); @@ -1140,11 +1331,20 @@ describe("Array Update Operators", () => { * @see https://www.mongodb.com/docs/manual/reference/operator/update/inc */ it("should increment and decrement values", async () => { - const collection = new Collection<{ - sku: string; - quantity: number; - metrics: { orders: number; ratings: number }; - }>("products", new MemoryStorage("products")); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + sku: z.string(), + quantity: z.number(), + metrics: z.object({ + orders: z.number(), + ratings: z.number(), + }), + }, + }); await collection.insertOne({ id: "1", sku: "abc123", quantity: 10, metrics: { orders: 2, ratings: 3.5 } }); @@ -1176,10 +1376,20 @@ describe("Array Update Operators", () => { }); it("should increment value of an array element with array index", async () => { - const collection = new Collection<{ details: { id: number; quantity: number }[] }>( - "products", - new MemoryStorage("products"), - ); + const collection = new Collection({ + name: "test", + adapter: new MemoryStorage("tests"), + primaryKey: "id", + schema: { + id: z.string(), + details: z.array( + z.object({ + id: z.number(), + quantity: z.number(), + }), + ), + }, + }); await collection.insertOne({ id: "3", diff --git a/tests/users.mock.ts b/tests/users.mock.ts index a2f9f81..1baafce 100644 --- a/tests/users.mock.ts +++ b/tests/users.mock.ts @@ -1,5 +1,5 @@ import { clone } from "../src/clone.ts"; -import { WithId } from "../src/types.ts"; +import type { WithId } from "../src/types.ts"; const users: WithId[] = [ {