feat: 2.0.0 beta release
This commit is contained in:
@@ -1,24 +0,0 @@
|
|||||||
version: "2"
|
|
||||||
|
|
||||||
plugins:
|
|
||||||
eslint:
|
|
||||||
enabled: true
|
|
||||||
config: configs/eslint/index.js
|
|
||||||
|
|
||||||
checks:
|
|
||||||
similar-code:
|
|
||||||
enabled: true
|
|
||||||
config:
|
|
||||||
threshold: 75
|
|
||||||
return-statements:
|
|
||||||
config:
|
|
||||||
threshold: 8
|
|
||||||
|
|
||||||
exclude_patterns:
|
|
||||||
- "dist/"
|
|
||||||
- "scripts/"
|
|
||||||
- "**/node_modules/"
|
|
||||||
- "**/tests/"
|
|
||||||
- "**/*.test.ts"
|
|
||||||
- "**/*.test.tsx"
|
|
||||||
- "**/*.d.ts"
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: ["simple-import-sort", "check-file"],
|
|
||||||
extends: ["prettier", "plugin:anti-trojan-source/recommended"],
|
|
||||||
ignorePatterns: ["dist"],
|
|
||||||
rules: {
|
|
||||||
// ### Import
|
|
||||||
|
|
||||||
"no-restricted-imports": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
patterns: [
|
|
||||||
{
|
|
||||||
group: ["*/src"],
|
|
||||||
message: "Do not directly import packages from the src/ directory."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// ### Simple Import Sort
|
|
||||||
|
|
||||||
"simple-import-sort/imports": "error",
|
|
||||||
"simple-import-sort/exports": "error",
|
|
||||||
|
|
||||||
// ### Check File
|
|
||||||
|
|
||||||
"check-file/filename-naming-convention": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"src/**/*.{tsx,ts}": "PASCAL_CASE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ignoreMiddleExtensions: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: ["tests/**/*.ts"],
|
|
||||||
rules: {
|
|
||||||
"no-restricted-imports": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ["*.ts", "*.tsx"],
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
extends: ["plugin:@typescript-eslint/recommended"],
|
|
||||||
rules: {
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "off",
|
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
|
||||||
"@typescript-eslint/no-empty-function": "off",
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
"@typescript-eslint/no-use-before-define": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
|
||||||
"@typescript-eslint/no-var-requires": "off",
|
|
||||||
"@typescript-eslint/prefer-arrow-callback": "off",
|
|
||||||
"@typescript-eslint/ban-types": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ["types.d.ts", "main.ts", "mod.ts", "index.ts"],
|
|
||||||
rules: {
|
|
||||||
"check-file/filename-naming-convention": ["off"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
46
.github/workflows/ci.yml
vendored
46
.github/workflows/ci.yml
vendored
@@ -1,46 +0,0 @@
|
|||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
Build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
|
|
||||||
|
|
||||||
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c
|
|
||||||
with:
|
|
||||||
node-version-file: ".nvmrc"
|
|
||||||
cache: npm
|
|
||||||
|
|
||||||
- run: npm install
|
|
||||||
|
|
||||||
Lint:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
|
|
||||||
|
|
||||||
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c
|
|
||||||
with:
|
|
||||||
node-version-file: ".nvmrc"
|
|
||||||
cache: npm
|
|
||||||
|
|
||||||
- run: npm install
|
|
||||||
- run: npm run lint
|
|
||||||
|
|
||||||
Test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
|
|
||||||
|
|
||||||
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c
|
|
||||||
with:
|
|
||||||
node-version-file: ".nvmrc"
|
|
||||||
cache: npm
|
|
||||||
|
|
||||||
- run: npm install
|
|
||||||
- run: npm test
|
|
||||||
64
.github/workflows/publish.yml
vendored
64
.github/workflows/publish.yml
vendored
@@ -1,42 +1,46 @@
|
|||||||
name: Publish
|
name: Publish
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
workflow_dispatch:
|
||||||
types: [published]
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
npm:
|
lint:
|
||||||
name: NPM
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: NPM Release Publishing
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c
|
- name: Setup Deno
|
||||||
|
uses: maximousblk/setup-deno@v2
|
||||||
|
|
||||||
|
- name: Setup Node.JS
|
||||||
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version: 22
|
||||||
cache: npm
|
|
||||||
|
|
||||||
- uses: actions/github-script@e3cbab99d3a9b271e1b79fc96d103a4a5534998c
|
- run: deno install
|
||||||
id: version
|
- run: deno task lint
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const semver = context.ref.replace('refs/tags/v', '')
|
|
||||||
if (semver.match(/^[0-9]+\.[0-9]+\.[0-9]+$/)) {
|
|
||||||
return semver
|
|
||||||
}
|
|
||||||
throw new Error('not semver')
|
|
||||||
result-encoding: string
|
|
||||||
|
|
||||||
- run: npm install
|
test:
|
||||||
- run: npm run build
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Version
|
- name: Setup Deno
|
||||||
run: |
|
uses: maximousblk/setup-deno@v2
|
||||||
node ./version.cjs --version ${{ steps.version.outputs.result }}
|
|
||||||
- name: Publish
|
- run: deno install
|
||||||
run: |
|
- run: deno task test
|
||||||
npm config set //registry.npmjs.org/:_authToken=$NPM_AUTH_TOKEN
|
|
||||||
npm publish --access public
|
publish:
|
||||||
env:
|
runs-on: ubuntu-latest
|
||||||
NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
|
needs: [lint, test]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Publish package
|
||||||
|
run: npx jsr publish
|
||||||
|
|||||||
38
.github/workflows/test.yml
vendored
Normal file
38
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup Deno
|
||||||
|
uses: maximousblk/setup-deno@v2
|
||||||
|
|
||||||
|
- name: Setup Node.JS
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
|
||||||
|
- run: deno install
|
||||||
|
- run: deno task lint
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup Deno
|
||||||
|
uses: maximousblk/setup-deno@v2
|
||||||
|
|
||||||
|
- run: deno install
|
||||||
|
- run: deno task test
|
||||||
|
- run: deno task test:publish
|
||||||
58
.gitignore
vendored
58
.gitignore
vendored
@@ -1,59 +1 @@
|
|||||||
# IDE - VSCode
|
|
||||||
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/settings.json
|
|
||||||
!.vscode/tasks.json
|
|
||||||
!.vscode/launch.json
|
|
||||||
!.vscode/extensions.json
|
|
||||||
*.code-workspace
|
|
||||||
|
|
||||||
# IDE - IntelliJ
|
|
||||||
|
|
||||||
.idea/*
|
|
||||||
!.idea/inspectionProfiles/
|
|
||||||
!.idea/dictionaries/
|
|
||||||
!.idea/codeStyles/
|
|
||||||
!.idea/modules.xml
|
|
||||||
!.idea/*.iml
|
|
||||||
!.idea/README.md
|
|
||||||
|
|
||||||
# Development
|
|
||||||
|
|
||||||
.publish
|
|
||||||
.turbo
|
|
||||||
.docker
|
|
||||||
.parcel-cache
|
|
||||||
|
|
||||||
# node_modules
|
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
# cargo
|
|
||||||
|
|
||||||
/target
|
|
||||||
Cargo.lock
|
|
||||||
|
|
||||||
# dist & pack
|
|
||||||
|
|
||||||
.contented
|
|
||||||
dist
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Tests
|
|
||||||
|
|
||||||
coverage
|
|
||||||
|
|
||||||
# OS
|
|
||||||
|
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
# TypeScript
|
|
||||||
|
|
||||||
tsconfig.*.tsbuildinfo
|
|
||||||
|
|
||||||
# Logs
|
|
||||||
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
.vscode/
|
|
||||||
node_modules/
|
|
||||||
dist/
|
|
||||||
14
.prettierrc
Normal file
14
.prettierrc
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "all",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"printWidth": 120,
|
||||||
|
"singleQuote": false,
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.ts",
|
||||||
|
"options": {
|
||||||
|
"parser": "typescript"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
50
.vscode/settings.json
vendored
50
.vscode/settings.json
vendored
@@ -1,51 +1,9 @@
|
|||||||
{
|
{
|
||||||
"eslint.workingDirectories": [
|
"deno.enable": true,
|
||||||
{
|
"deno.lint": false,
|
||||||
"mode": "auto"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"editor.codeActionsOnSave": {
|
|
||||||
"source.fixAll.eslint": true
|
|
||||||
},
|
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"material-icon-theme.folders.associations": {
|
"editor.codeActionsOnSave": {
|
||||||
"cerberus": "secure",
|
"source.fixAll.eslint": "explicit"
|
||||||
"jsonrpc": "public"
|
|
||||||
},
|
|
||||||
"material-icon-theme.files.associations": {
|
|
||||||
"index.ts": "tree",
|
|
||||||
"main.ts": "tree",
|
|
||||||
"mod.ts": "tree",
|
|
||||||
"config.ts": "settings",
|
|
||||||
"*.access.ts": "key",
|
|
||||||
"*.database.ts": "database",
|
|
||||||
"*.collection.ts": "mxml",
|
|
||||||
"*.controller.ts": "makefile",
|
|
||||||
"*.events.ts": "lib",
|
|
||||||
"*.method.ts": "makefile",
|
|
||||||
"*.methods.ts": "makefile",
|
|
||||||
"*.mock.ts": "hardhat",
|
|
||||||
"*.entity.ts": "database",
|
|
||||||
"*.form.ts": "log",
|
|
||||||
"*.gateway.ts": "parcel",
|
|
||||||
"*.module.ts": "robot",
|
|
||||||
"*.projector.ts": "apiblueprint",
|
|
||||||
"*.role.ts": "playwright",
|
|
||||||
"*.service.ts": "console",
|
|
||||||
"*.validator.ts": "key",
|
|
||||||
"*.component.tsx": "virtual",
|
|
||||||
"*.modal.tsx": "opam",
|
|
||||||
"*.view.tsx": "virtual",
|
|
||||||
},
|
|
||||||
"files.exclude": {
|
|
||||||
"**/.turbo": true,
|
|
||||||
"**/.git": true,
|
|
||||||
"**/.svn": true,
|
|
||||||
"**/.hg": true,
|
|
||||||
"**/CVS": true,
|
|
||||||
"**/.DS_Store": true,
|
|
||||||
"**/Thumbs.db": true
|
|
||||||
},
|
},
|
||||||
"[typescript]": {
|
"[typescript]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2020-2023 Christoffer Rødvik, Valkyr
|
Copyright (c) 2020-2025 Christoffer Rødvik, Valkyr
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
19
deno.json
Normal file
19
deno.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "@valkyr/db",
|
||||||
|
"version": "2.0.0-beta.1",
|
||||||
|
"exports": {
|
||||||
|
".": "./src/mod.ts"
|
||||||
|
},
|
||||||
|
"publish": {
|
||||||
|
"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",
|
||||||
|
"test:publish": "deno publish --dry-run",
|
||||||
|
"ncu": "npx ncu -u -p npm"
|
||||||
|
},
|
||||||
|
"nodeModulesDir": "auto"
|
||||||
|
}
|
||||||
36
eslint.config.mjs
Normal file
36
eslint.config.mjs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import simpleImportSort from "eslint-plugin-simple-import-sort";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
|
export default [
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
"simple-import-sort": simpleImportSort,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
"simple-import-sort/imports": "error",
|
||||||
|
"simple-import-sort/exports": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["**/*.ts"],
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/ban-ts-comment": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"ts-expect-error": "allow-with-description",
|
||||||
|
minimumDescriptionLength: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/ban-types": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
argsIgnorePattern: "^_",
|
||||||
|
varsIgnorePattern: "^_",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
15
mod.ts
Normal file
15
mod.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export * from "./libraries/aggregate.ts";
|
||||||
|
export * from "./libraries/errors.ts";
|
||||||
|
export * from "./libraries/event.ts";
|
||||||
|
export * from "./libraries/event-factory.ts";
|
||||||
|
export * from "./libraries/event-store.ts";
|
||||||
|
export * from "./libraries/projector.ts";
|
||||||
|
export * from "./libraries/queue.ts";
|
||||||
|
export * from "./libraries/reducer.ts";
|
||||||
|
export * from "./libraries/time.ts";
|
||||||
|
export * from "./libraries/timestamp.ts";
|
||||||
|
export type * from "./types/adapter.ts";
|
||||||
|
export type * from "./types/common.ts";
|
||||||
|
export type * from "./types/projector.ts";
|
||||||
|
export type * from "./types/query.ts";
|
||||||
|
export type * from "./types/utilities.ts";
|
||||||
7397
package-lock.json
generated
7397
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
92
package.json
92
package.json
@@ -5,83 +5,25 @@
|
|||||||
"repository": "https://github.com/valkyrjs/db.git",
|
"repository": "https://github.com/valkyrjs/db.git",
|
||||||
"bugs": "https://github.com/valkyrjs/db/issues",
|
"bugs": "https://github.com/valkyrjs/db/issues",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
|
||||||
"main": "./dist/index.js",
|
|
||||||
"types": "./dist/index.d.ts",
|
|
||||||
"files": [
|
|
||||||
"dist"
|
|
||||||
],
|
|
||||||
"keywords": [
|
|
||||||
"browser",
|
|
||||||
"database",
|
|
||||||
"mingo",
|
|
||||||
"mongodb"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc -b ./tsconfig.build.json",
|
|
||||||
"flush": "npm run clean && rm -rf ./node_modules",
|
|
||||||
"clean": "rm -rf ./dist",
|
|
||||||
"lint": "eslint ./src --fix",
|
|
||||||
"test": "NODE_OPTIONS=--experimental-vm-modules npx jest"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dot-prop": "8.0.2",
|
"bson": "6",
|
||||||
"fast-equals": "5.0.1",
|
"dot-prop": "9",
|
||||||
"idb": "7.1.1",
|
"fast-equals": "5",
|
||||||
"mingo": "6.4.6",
|
"idb": "8",
|
||||||
"nanoid": "5.0.2",
|
"mingo": "6",
|
||||||
"rfdc": "1.3.0",
|
"rfdc": "1",
|
||||||
"rxjs": "7.8.1"
|
"rxjs": "7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "29.5.6",
|
"@std/async": "npm:@jsr/std__async@1",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.8.0",
|
"@std/assert": "npm:@jsr/std__assert@1",
|
||||||
"@typescript-eslint/parser": "^6.8.0",
|
"@std/testing": "npm:@jsr/std__testing@1",
|
||||||
"bson": "6.2.0",
|
"@valkyr/testcontainers": "npm:@jsr/valkyr__testcontainers@2",
|
||||||
"eslint": "8.51.0",
|
"eslint": "9",
|
||||||
"eslint-config-prettier": "9.0.0",
|
"eslint-plugin-simple-import-sort": "12",
|
||||||
"eslint-plugin-anti-trojan-source": "1.1.1",
|
"expect": "30",
|
||||||
"eslint-plugin-check-file": "2.6.2",
|
"fake-indexeddb": "6",
|
||||||
"eslint-plugin-import": "2.28.1",
|
"prettier": "3",
|
||||||
"eslint-plugin-simple-import-sort": "10.0.0",
|
"typescript-eslint": "8"
|
||||||
"fake-indexeddb": "5.0.0",
|
|
||||||
"jest": "29.7.0",
|
|
||||||
"lint-staged": "15.0.2",
|
|
||||||
"prettier": "3.0.3",
|
|
||||||
"ts-jest": "29.1.1",
|
|
||||||
"type-fest": "4.5.0",
|
|
||||||
"typescript": "5.2.2"
|
|
||||||
},
|
|
||||||
"prettier": {
|
|
||||||
"printWidth": 120,
|
|
||||||
"trailingComma": "none"
|
|
||||||
},
|
|
||||||
"jest": {
|
|
||||||
"extensionsToTreatAsEsm": [
|
|
||||||
".ts"
|
|
||||||
],
|
|
||||||
"verbose": true,
|
|
||||||
"testEnvironment": "node",
|
|
||||||
"modulePathIgnorePatterns": [
|
|
||||||
"<rootDir>/node_modules",
|
|
||||||
"<rootDir>/dist"
|
|
||||||
],
|
|
||||||
"moduleFileExtensions": [
|
|
||||||
"ts",
|
|
||||||
"js",
|
|
||||||
"json"
|
|
||||||
],
|
|
||||||
"moduleNameMapper": {
|
|
||||||
"^(\\.{1,2}/.*)\\.js$": "$1"
|
|
||||||
},
|
|
||||||
"testRegex": ".*\\.Test\\.ts$",
|
|
||||||
"transform": {
|
|
||||||
"^.+\\.ts$": [
|
|
||||||
"ts-jest",
|
|
||||||
{
|
|
||||||
"useESM": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from "./IndexedDb.js";
|
|
||||||
export * from "./MemoryDb.js";
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export type { Action } from "./Action.js";
|
|
||||||
export * from "./Observe.js";
|
|
||||||
export * from "./ObserveOne.js";
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from "./Result.js";
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from "./Result.js";
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import * as dot from "dot-prop";
|
|
||||||
|
|
||||||
import { Document, Filter, UpdateFilter, WithId } from "../../../Types.js";
|
|
||||||
import { setPositionalData } from "./Utils.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute a $inc based operators.
|
|
||||||
*
|
|
||||||
* Supports positional array operator $(update)
|
|
||||||
*
|
|
||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/positional
|
|
||||||
*
|
|
||||||
* @param document - Document being updated.
|
|
||||||
* @param filter - Search filter provided with the operation. Eg. updateOne({ id: "1" })
|
|
||||||
* @param $set - $set action being executed.
|
|
||||||
*/
|
|
||||||
export function $inc<TSchema extends Document = Document>(
|
|
||||||
document: WithId<TSchema>,
|
|
||||||
filter: Filter<WithId<TSchema>>,
|
|
||||||
$inc: UpdateFilter<TSchema>["$inc"] = {}
|
|
||||||
): boolean {
|
|
||||||
let modified = false;
|
|
||||||
for (const key in $inc) {
|
|
||||||
if (key.includes("$")) {
|
|
||||||
if (
|
|
||||||
setPositionalData(document, filter, key, {
|
|
||||||
object: (data, key, target) => {
|
|
||||||
if (typeof data === "number") {
|
|
||||||
return data + ($inc[key] as number);
|
|
||||||
}
|
|
||||||
const value = dot.getProperty(data, target);
|
|
||||||
if (typeof value !== "number") {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return value + $inc[key];
|
|
||||||
},
|
|
||||||
value: (data, key) => data + $inc[key]
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
document = increment(document, key, $inc[key]);
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return modified;
|
|
||||||
}
|
|
||||||
|
|
||||||
function increment<D extends Document>(document: D, key: string, value: number): D {
|
|
||||||
let currentValue = dot.getProperty(document, key) as unknown;
|
|
||||||
if (typeof currentValue !== "number") {
|
|
||||||
currentValue = 0;
|
|
||||||
}
|
|
||||||
return dot.setProperty(document, key, (currentValue as number) + value);
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import * as dot from "dot-prop";
|
|
||||||
import { Query } from "mingo";
|
|
||||||
import { RawObject } from "mingo/types";
|
|
||||||
|
|
||||||
import { Document, UpdateFilter, WithId } from "../../../Types.js";
|
|
||||||
import { PullUpdateArrayError } from "../../Errors.js";
|
|
||||||
|
|
||||||
export function $pull<TSchema extends Document>(
|
|
||||||
document: WithId<TSchema>,
|
|
||||||
operator: UpdateFilter<TSchema>["$pull"] = {}
|
|
||||||
): boolean {
|
|
||||||
let modified = false;
|
|
||||||
for (const key in operator) {
|
|
||||||
const values = getPullValues(document, key);
|
|
||||||
const result = getPullResult(operator, key, values);
|
|
||||||
dot.setProperty(document, key, result);
|
|
||||||
if (result.length !== values.length) {
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return modified;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPullValues(document: Document, key: string): any[] {
|
|
||||||
const values: any[] | undefined = dot.getProperty(document, key);
|
|
||||||
if (values === undefined || Array.isArray(values) === false) {
|
|
||||||
throw new PullUpdateArrayError(document.id, key);
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPullResult(operator: RawObject, key: string, values: any[]): any[] {
|
|
||||||
if (typeof operator[key] === "object") {
|
|
||||||
return new Query(getPullCriteria(operator, key)).remove(values);
|
|
||||||
}
|
|
||||||
return values.filter((value) => value !== operator[key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Criteria used during pull depends on the structure of the query under the pulled
|
|
||||||
* key. If the object has a mongodb filter key with a $ prefix we need to provide
|
|
||||||
* the query with the array key as the query wrapper. If a $ prefix is not present
|
|
||||||
* we want the value under the key being the criteria.
|
|
||||||
*
|
|
||||||
* @param operator - Object under operator action.
|
|
||||||
* @param key - Specific key being resolved to a criteria.
|
|
||||||
*/
|
|
||||||
function getPullCriteria(operator: any, key: string): RawObject {
|
|
||||||
let hasFilters = false;
|
|
||||||
for (const left in operator[key]) {
|
|
||||||
if (left.includes("$")) {
|
|
||||||
hasFilters = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hasFilters === true) {
|
|
||||||
return { [key]: dot.getProperty(operator, key) };
|
|
||||||
}
|
|
||||||
return dot.getProperty(operator, key) as RawObject;
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
import * as dot from "dot-prop";
|
|
||||||
import { deepEqual } from "fast-equals";
|
|
||||||
import { Query } from "mingo";
|
|
||||||
import type { RawObject } from "mingo/types";
|
|
||||||
|
|
||||||
import { Document, UpdateFilter, WithId } from "../../../Types.js";
|
|
||||||
|
|
||||||
export function $push<TSchema extends Document = Document>(
|
|
||||||
document: WithId<TSchema>,
|
|
||||||
operator: UpdateFilter<TSchema>["$push"] = {}
|
|
||||||
): boolean {
|
|
||||||
let modified = false;
|
|
||||||
for (const key in operator) {
|
|
||||||
const values = getPushValues(document, key);
|
|
||||||
const result = getPushResult(operator, key, values);
|
|
||||||
dot.setProperty(document, key, result);
|
|
||||||
if (deepEqual(values, result) === false) {
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return modified;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPushValues(document: any, key: string): any[] {
|
|
||||||
const values = dot.getProperty(document, key);
|
|
||||||
if (values === undefined) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPushResult(operator: RawObject, key: string, values: any[]): any[] {
|
|
||||||
if (typeof operator[key] === "object") {
|
|
||||||
return getPushFromModifiers(operator[key], values);
|
|
||||||
}
|
|
||||||
return [...values, operator[key]];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPushFromModifiers(obj: any, values: any[]): any[] {
|
|
||||||
if (obj.$each === undefined) {
|
|
||||||
return [...values, obj];
|
|
||||||
}
|
|
||||||
let items: any[];
|
|
||||||
|
|
||||||
if (obj.$position !== undefined) {
|
|
||||||
items = [...values.slice(0, obj.$position), ...obj.$each, ...values.slice(obj.$position)];
|
|
||||||
} else {
|
|
||||||
items = [...values, ...obj.$each];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj.$sort !== undefined) {
|
|
||||||
if (typeof obj.$sort === "object") {
|
|
||||||
items = new Query({}).find(items).sort(obj.$sort).all();
|
|
||||||
} else {
|
|
||||||
items = items.sort((a, b) => {
|
|
||||||
if (obj.$sort === 1) {
|
|
||||||
return a < b ? -1 : 1;
|
|
||||||
}
|
|
||||||
return a < b ? 1 : -1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj.$slice !== undefined) {
|
|
||||||
if (obj.$slice < 0) {
|
|
||||||
return items.slice(obj.$slice);
|
|
||||||
}
|
|
||||||
return items.slice(0, obj.$slice);
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export class UpdateResult {
|
|
||||||
constructor(readonly matched = 0, readonly modified = 0) {}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import * as dot from "dot-prop";
|
|
||||||
|
|
||||||
import { Document, Filter, UpdateFilter, WithId } from "../../../Types.js";
|
|
||||||
import { setPositionalData } from "./Utils.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute a $set based operators.
|
|
||||||
*
|
|
||||||
* Supports positional array operator $(update)
|
|
||||||
*
|
|
||||||
* @see https://www.mongodb.com/docs/manual/reference/operator/update/positional
|
|
||||||
*
|
|
||||||
* @param document - Document being updated.
|
|
||||||
* @param filter - Search filter provided with the operation. Eg. updateOne({ id: "1" })
|
|
||||||
* @param $set - $set action being executed.
|
|
||||||
*/
|
|
||||||
export function $set<TSchema extends Document = Document>(
|
|
||||||
document: WithId<WithId<TSchema>>,
|
|
||||||
filter: Filter<WithId<TSchema>>,
|
|
||||||
$set: UpdateFilter<TSchema>["$set"] = {} as any
|
|
||||||
): boolean {
|
|
||||||
let modified = false;
|
|
||||||
for (const key in $set) {
|
|
||||||
if (key.includes("$")) {
|
|
||||||
if (
|
|
||||||
setPositionalData(document, filter, key, {
|
|
||||||
object: (data, key) => getSetValue(data, key, $set),
|
|
||||||
value: (_, key) => $set[key]
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
document = dot.setProperty(document, key, getSetValue(document, key, $set));
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return modified;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSetValue(data: any, key: string, $set: UpdateFilter<Document>["$set"] = {}) {
|
|
||||||
const value = $set[key];
|
|
||||||
if (typeof value === "function") {
|
|
||||||
return value(dot.getProperty(data, key), data);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import * as dot from "dot-prop";
|
|
||||||
|
|
||||||
import { Document, UpdateFilter, WithId } from "../../../Types.js";
|
|
||||||
|
|
||||||
export function $unset<TSchema extends Document = Document>(
|
|
||||||
document: WithId<TSchema>,
|
|
||||||
$unset: UpdateFilter<TSchema>["$unset"] = {}
|
|
||||||
): boolean {
|
|
||||||
let modified = false;
|
|
||||||
for (const key in $unset) {
|
|
||||||
if (dot.deleteProperty(document, key)) {
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return modified;
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { clone } from "../../../Clone.js";
|
|
||||||
import { Document, Filter, UpdateFilter, WithId } from "../../../Types.js";
|
|
||||||
import { $inc } from "./Inc.js";
|
|
||||||
import { $pull } from "./Pull.js";
|
|
||||||
import { $push } from "./Push.js";
|
|
||||||
import { $set } from "./Set.js";
|
|
||||||
import { $unset } from "./Unset.js";
|
|
||||||
|
|
||||||
export function update<TSchema extends Document>(
|
|
||||||
filter: Filter<WithId<TSchema>>,
|
|
||||||
operators: UpdateFilter<TSchema>,
|
|
||||||
document: WithId<TSchema>
|
|
||||||
) {
|
|
||||||
const updatedDocument = clone(document);
|
|
||||||
|
|
||||||
const setModified = $set<TSchema>(updatedDocument, filter, operators.$set);
|
|
||||||
const runModified = $unset<TSchema>(updatedDocument, operators.$unset);
|
|
||||||
const pushModified = $push<TSchema>(updatedDocument, operators.$push);
|
|
||||||
const pullModified = $pull<TSchema>(updatedDocument, operators.$pull);
|
|
||||||
const incModified = $inc<TSchema>(updatedDocument, filter, operators.$inc);
|
|
||||||
|
|
||||||
return {
|
|
||||||
modified: setModified || runModified || pushModified || pullModified || incModified,
|
|
||||||
document: updatedDocument
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
import * as dot from "dot-prop";
|
|
||||||
import { deepEqual } from "fast-equals";
|
|
||||||
import { Query } from "mingo";
|
|
||||||
|
|
||||||
import { clone } from "../../../Clone.js";
|
|
||||||
import { Document, Filter, WithId } from "../../../Types.js";
|
|
||||||
|
|
||||||
type UpdateValue = (data: any, key: string, target: string) => any;
|
|
||||||
|
|
||||||
export function setPositionalData<TSchema extends Document = Document>(
|
|
||||||
document: WithId<TSchema>,
|
|
||||||
criteria: Filter<WithId<TSchema>>,
|
|
||||||
key: string,
|
|
||||||
update: {
|
|
||||||
object: UpdateValue;
|
|
||||||
value: UpdateValue;
|
|
||||||
}
|
|
||||||
): boolean {
|
|
||||||
const { filter, path, target } = getPositionalFilter(criteria, key);
|
|
||||||
|
|
||||||
const values = getPropertyValues(document, path);
|
|
||||||
const items =
|
|
||||||
typeof filter === "object"
|
|
||||||
? getPositionalUpdateQuery(clone(values), key, filter, target, update.object)
|
|
||||||
: getPositionalUpdate(clone(values), key, filter, target, update.value);
|
|
||||||
|
|
||||||
dot.setProperty(document, path, items);
|
|
||||||
|
|
||||||
return deepEqual(values, items) === false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPropertyValues(document: Document, path: string): string[] {
|
|
||||||
const values = dot.getProperty(document, path);
|
|
||||||
if (values === undefined) {
|
|
||||||
throw new Error("Values is undefined");
|
|
||||||
}
|
|
||||||
if (Array.isArray(values) === false) {
|
|
||||||
throw new Error("Values is not an array");
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPositionalUpdate(
|
|
||||||
items: any[],
|
|
||||||
key: string,
|
|
||||||
filter: string,
|
|
||||||
target: string,
|
|
||||||
updateValue: UpdateValue
|
|
||||||
): any[] {
|
|
||||||
let index = 0;
|
|
||||||
for (const item of items) {
|
|
||||||
if (item === filter) {
|
|
||||||
items[index] = updateValue(items[index], key, target);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPositionalUpdateQuery(
|
|
||||||
items: any[],
|
|
||||||
key: string,
|
|
||||||
filter: Filter<any>,
|
|
||||||
target: string,
|
|
||||||
updateValue: UpdateValue
|
|
||||||
): any[] {
|
|
||||||
let index = 0;
|
|
||||||
for (const item of items) {
|
|
||||||
if (new Query(filter).test(item) === true) {
|
|
||||||
if (target === "") {
|
|
||||||
items[index] = updateValue(items[index], key, target);
|
|
||||||
} else {
|
|
||||||
dot.setProperty(item, target, updateValue(items[index], key, target));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPositionalFilter(criteria: Filter<any>, key: string): PositionalFilter {
|
|
||||||
const [leftPath, rightPath] = key.split("$");
|
|
||||||
|
|
||||||
const lKey = trimSeparators(leftPath);
|
|
||||||
const rKey = trimSeparators(rightPath);
|
|
||||||
|
|
||||||
for (const key in criteria) {
|
|
||||||
const result = getPositionalCriteriaFilter(key, lKey, rKey, criteria);
|
|
||||||
if (result !== undefined) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
filter: criteria[lKey],
|
|
||||||
path: lKey,
|
|
||||||
target: rKey
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPositionalCriteriaFilter(
|
|
||||||
key: string,
|
|
||||||
lKey: string,
|
|
||||||
rKey: string,
|
|
||||||
criteria: Filter<any>
|
|
||||||
): PositionalFilter | undefined {
|
|
||||||
if (key.includes(lKey) === true) {
|
|
||||||
const isObject = typeof criteria[key] === "object";
|
|
||||||
if (key.includes(".") === true || isObject === true) {
|
|
||||||
return {
|
|
||||||
filter:
|
|
||||||
trimSeparators(key.replace(lKey, "")) === ""
|
|
||||||
? (criteria[key] as any).$elemMatch !== undefined
|
|
||||||
? (criteria[key] as any).$elemMatch
|
|
||||||
: criteria[key]
|
|
||||||
: {
|
|
||||||
[trimSeparators(key.replace(lKey, ""))]: criteria[key]
|
|
||||||
},
|
|
||||||
path: lKey,
|
|
||||||
target: rKey
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function trimSeparators(value: string): string {
|
|
||||||
return value.replace(/^\.+|\.+$/gm, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A position filter is used to find documents to update in an array of values.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* const document = {
|
|
||||||
* grades: [
|
|
||||||
* { grade: 80, mean: 75, std: 8 },
|
|
||||||
* { grade: 85, mean: 90, std: 5 },
|
|
||||||
* { grade: 85, mean: 85, std: 8 }
|
|
||||||
* ]
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* updateOne({ "grades.grade": 85 }, { $set: { "grades.$.std": 6 } } })
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* In the above example the filter would be `{ grade: 85 }` which is used to find
|
|
||||||
* objects to update in an array of values.
|
|
||||||
*/
|
|
||||||
type PositionalFilter = {
|
|
||||||
/**
|
|
||||||
* The filter to use to find the values to update in an array.
|
|
||||||
*/
|
|
||||||
filter: any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The path to the array of values of the parent document. Eg. `grades`.
|
|
||||||
*/
|
|
||||||
path: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The path to the key to update in the array of values. Eg. `std`.
|
|
||||||
*/
|
|
||||||
target: string;
|
|
||||||
};
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from "./Result.js";
|
|
||||||
export * from "./Update.js";
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export * from "./Errors.js";
|
|
||||||
export * from "./Operators/Insert/mod.js";
|
|
||||||
export * from "./Operators/Remove/mod.js";
|
|
||||||
export * from "./Operators/Update/mod.js";
|
|
||||||
export * from "./Storage.js";
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Document, WithId } from "./Types.js";
|
import type { Document, WithId } from "./types.ts";
|
||||||
|
|
||||||
export const BroadcastChannel =
|
export const BroadcastChannel =
|
||||||
globalThis.BroadcastChannel ??
|
globalThis.BroadcastChannel ??
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import { UpdateOptions } from "mingo/core";
|
||||||
|
import { UpdateExpression } from "mingo/updater";
|
||||||
import { Observable, Subscription } from "rxjs";
|
import { Observable, Subscription } from "rxjs";
|
||||||
|
|
||||||
import { observe, observeOne } from "./Observe/mod.js";
|
import { observe, observeOne } from "./observe/mod.ts";
|
||||||
import {
|
import {
|
||||||
ChangeEvent,
|
ChangeEvent,
|
||||||
InsertManyResult,
|
InsertManyResult,
|
||||||
@@ -8,9 +10,9 @@ import {
|
|||||||
Options,
|
Options,
|
||||||
RemoveResult,
|
RemoveResult,
|
||||||
Storage,
|
Storage,
|
||||||
UpdateResult
|
UpdateResult,
|
||||||
} from "./Storage/mod.js";
|
} from "./storage/mod.ts";
|
||||||
import { Document, Filter, UpdateFilter, WithId } from "./Types.js";
|
import { Document, Filter, WithId } from "./types.ts";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
@@ -21,7 +23,7 @@ import { Document, Filter, UpdateFilter, WithId } from "./Types.js";
|
|||||||
export class Collection<TSchema extends Document = Document> {
|
export class Collection<TSchema extends Document = Document> {
|
||||||
constructor(
|
constructor(
|
||||||
readonly name: string,
|
readonly name: string,
|
||||||
readonly storage: Storage<TSchema>
|
readonly storage: Storage<TSchema>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
get observable() {
|
get observable() {
|
||||||
@@ -42,12 +44,24 @@ export class Collection<TSchema extends Document = Document> {
|
|||||||
return this.storage.resolve().then((storage) => storage.insertMany(documents));
|
return this.storage.resolve().then((storage) => storage.insertMany(documents));
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOne(filter: Filter<WithId<TSchema>>, update: UpdateFilter<TSchema>): Promise<UpdateResult> {
|
async updateOne(
|
||||||
return this.storage.resolve().then((storage) => storage.updateOne(filter, update));
|
filter: Filter<WithId<TSchema>>,
|
||||||
|
expr: UpdateExpression,
|
||||||
|
arrayFilters?: Filter<WithId<TSchema>>[],
|
||||||
|
condition?: Filter<WithId<TSchema>>,
|
||||||
|
options?: UpdateOptions,
|
||||||
|
): Promise<UpdateResult> {
|
||||||
|
return this.storage.resolve().then((storage) => storage.updateOne(filter, expr, arrayFilters, condition, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateMany(filter: Filter<WithId<TSchema>>, update: UpdateFilter<TSchema>): Promise<UpdateResult> {
|
async updateMany(
|
||||||
return this.storage.resolve().then((storage) => storage.updateMany(filter, update));
|
filter: Filter<WithId<TSchema>>,
|
||||||
|
expr: UpdateExpression,
|
||||||
|
arrayFilters?: Filter<WithId<TSchema>>[],
|
||||||
|
condition?: Filter<WithId<TSchema>>,
|
||||||
|
options?: UpdateOptions,
|
||||||
|
): Promise<UpdateResult> {
|
||||||
|
return this.storage.resolve().then((storage) => storage.updateMany(filter, expr, arrayFilters, condition, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
async replaceOne(filter: Filter<WithId<TSchema>>, document: TSchema): Promise<UpdateResult> {
|
async replaceOne(filter: Filter<WithId<TSchema>>, document: TSchema): Promise<UpdateResult> {
|
||||||
@@ -67,29 +81,29 @@ export class Collection<TSchema extends Document = Document> {
|
|||||||
subscribe(
|
subscribe(
|
||||||
filter?: Filter<WithId<TSchema>>,
|
filter?: Filter<WithId<TSchema>>,
|
||||||
options?: SubscribeToSingle,
|
options?: SubscribeToSingle,
|
||||||
next?: (document: WithId<TSchema> | undefined) => void
|
next?: (document: WithId<TSchema> | undefined) => void,
|
||||||
): Subscription;
|
): Subscription;
|
||||||
subscribe(
|
subscribe(
|
||||||
filter?: Filter<WithId<TSchema>>,
|
filter?: Filter<WithId<TSchema>>,
|
||||||
options?: SubscribeToMany,
|
options?: SubscribeToMany,
|
||||||
next?: (documents: WithId<TSchema>[], changed: WithId<TSchema>[], type: ChangeEvent["type"]) => void
|
next?: (documents: WithId<TSchema>[], changed: WithId<TSchema>[], type: ChangeEvent["type"]) => void,
|
||||||
): Subscription;
|
): Subscription;
|
||||||
subscribe(filter: Filter<WithId<TSchema>> = {}, options?: Options, next?: (...args: any[]) => void): Subscription {
|
subscribe(filter: Filter<WithId<TSchema>> = {}, options?: Options, next?: (...args: any[]) => void): Subscription {
|
||||||
if (options?.limit === 1) {
|
if (options?.limit === 1) {
|
||||||
return this.#observeOne(filter).subscribe({ next });
|
return this.#observeOne(filter).subscribe({ next });
|
||||||
}
|
}
|
||||||
return this.#observe(filter, options).subscribe({
|
return this.#observe(filter, options).subscribe({
|
||||||
next: (value: [WithId<TSchema>[], WithId<TSchema>[], ChangeEvent["type"]]) => next?.(...value)
|
next: (value: [WithId<TSchema>[], WithId<TSchema>[], ChangeEvent["type"]]) => next?.(...value),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#observe(
|
#observe(
|
||||||
filter: Filter<WithId<TSchema>> = {},
|
filter: Filter<WithId<TSchema>> = {},
|
||||||
options?: Options
|
options?: Options,
|
||||||
): Observable<[WithId<TSchema>[], WithId<TSchema>[], ChangeEvent["type"]]> {
|
): Observable<[WithId<TSchema>[], WithId<TSchema>[], ChangeEvent["type"]]> {
|
||||||
return new Observable<[WithId<TSchema>[], WithId<TSchema>[], ChangeEvent["type"]]>((subscriber) => {
|
return new Observable<[WithId<TSchema>[], WithId<TSchema>[], ChangeEvent["type"]]>((subscriber) => {
|
||||||
return observe(this as any, filter, options, (values, changed, type) =>
|
return observe(this as any, filter, options, (values, changed, type) =>
|
||||||
subscriber.next([values, changed, type] as any)
|
subscriber.next([values, changed, type] as any),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1,25 +1,11 @@
|
|||||||
import type { IDBPDatabase } from "idb";
|
import type { IDBPDatabase } from "idb";
|
||||||
import { Query } from "mingo";
|
import { Query } from "mingo";
|
||||||
import type { AnyVal } from "mingo/types";
|
import type { AnyVal } from "mingo/types";
|
||||||
import { nanoid } from "nanoid";
|
|
||||||
|
|
||||||
import { DBLogger, InsertLog, QueryLog, RemoveLog, ReplaceLog, UpdateLog } from "../Logger.js";
|
import { DBLogger, InsertLog, QueryLog, RemoveLog, ReplaceLog, UpdateLog } from "../../logger.ts";
|
||||||
import {
|
import { Storage } from "../../storage/storage.ts";
|
||||||
addOptions,
|
import { Document, Filter, UpdateFilter, WithId } from "../../types.ts";
|
||||||
DuplicateDocumentError,
|
import { IndexedDbCache } from "./cache.ts";
|
||||||
getInsertManyResult,
|
|
||||||
getInsertOneResult,
|
|
||||||
Index,
|
|
||||||
InsertManyResult,
|
|
||||||
InsertOneResult,
|
|
||||||
Options,
|
|
||||||
RemoveResult,
|
|
||||||
Storage,
|
|
||||||
update,
|
|
||||||
UpdateResult
|
|
||||||
} from "../Storage/mod.js";
|
|
||||||
import { Document, Filter, UpdateFilter, WithId } from "../Types.js";
|
|
||||||
import { IndexedDbCache } from "./IndexedDb.Cache.js";
|
|
||||||
|
|
||||||
const OBJECT_PROTOTYPE = Object.getPrototypeOf({}) as AnyVal;
|
const OBJECT_PROTOTYPE = Object.getPrototypeOf({}) as AnyVal;
|
||||||
const OBJECT_TAG = "[object Object]";
|
const OBJECT_TAG = "[object Object]";
|
||||||
@@ -33,7 +19,7 @@ export class IndexedDbStorage<TSchema extends Document = Document> extends Stora
|
|||||||
constructor(
|
constructor(
|
||||||
name: string,
|
name: string,
|
||||||
promise: Promise<IDBPDatabase>,
|
promise: Promise<IDBPDatabase>,
|
||||||
readonly log: DBLogger
|
readonly log: DBLogger,
|
||||||
) {
|
) {
|
||||||
super(name);
|
super(name);
|
||||||
this.#promise = promise;
|
this.#promise = promise;
|
||||||
@@ -70,7 +56,7 @@ export class IndexedDbStorage<TSchema extends Document = Document> extends Stora
|
|||||||
async insertOne(data: Partial<WithId<TSchema>>): Promise<InsertOneResult> {
|
async insertOne(data: Partial<WithId<TSchema>>): Promise<InsertOneResult> {
|
||||||
const logger = new InsertLog(this.name);
|
const logger = new InsertLog(this.name);
|
||||||
|
|
||||||
const document = { ...data, id: data.id ?? nanoid() } as any;
|
const document = { ...data, id: data.id ?? crypto.randomUUID() } as any;
|
||||||
if (await this.has(document.id)) {
|
if (await this.has(document.id)) {
|
||||||
throw new DuplicateDocumentError(document, this as any);
|
throw new DuplicateDocumentError(document, this as any);
|
||||||
}
|
}
|
||||||
@@ -95,7 +81,7 @@ export class IndexedDbStorage<TSchema extends Document = Document> extends Stora
|
|||||||
const document = { ...data, id: data.id ?? nanoid() } as WithId<TSchema>;
|
const document = { ...data, id: data.id ?? nanoid() } as WithId<TSchema>;
|
||||||
documents.push(document);
|
documents.push(document);
|
||||||
return tx.store.add(document);
|
return tx.store.add(document);
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
await tx.done;
|
await tx.done;
|
||||||
|
|
||||||
@@ -262,8 +248,8 @@ export class IndexedDbStorage<TSchema extends Document = Document> extends Stora
|
|||||||
documents.push(document);
|
documents.push(document);
|
||||||
return tx.store.put(document);
|
return tx.store.put(document);
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
await tx.done;
|
await tx.done;
|
||||||
@@ -290,7 +276,7 @@ export class IndexedDbStorage<TSchema extends Document = Document> extends Stora
|
|||||||
const next = { ...document, id };
|
const next = { ...document, id };
|
||||||
documents.push(next);
|
documents.push(next);
|
||||||
return tx.store.put(next);
|
return tx.store.put(next);
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
await tx.done;
|
await tx.done;
|
||||||
|
|
||||||
@@ -305,7 +291,7 @@ export class IndexedDbStorage<TSchema extends Document = Document> extends Stora
|
|||||||
async #update(
|
async #update(
|
||||||
id: string | number,
|
id: string | number,
|
||||||
filter: Filter<WithId<TSchema>>,
|
filter: Filter<WithId<TSchema>>,
|
||||||
operators: UpdateFilter<TSchema>
|
operators: UpdateFilter<TSchema>,
|
||||||
): Promise<UpdateResult> {
|
): Promise<UpdateResult> {
|
||||||
const logger = new UpdateLog(this.name, { filter, operators });
|
const logger = new UpdateLog(this.name, { filter, operators });
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { hashCodeQuery } from "../Hash.js";
|
import { hashCodeQuery } from "../../hash.ts";
|
||||||
import { Options } from "../Storage/mod.js";
|
import { Options } from "../../storage/mod.ts";
|
||||||
import type { Document, Filter, WithId } from "../Types.js";
|
import type { Document, Filter, WithId } from "../../types.ts";
|
||||||
|
|
||||||
export class IndexedDbCache<TSchema extends Document = Document> {
|
export class IndexedDbCache<TSchema extends Document = Document> {
|
||||||
readonly #cache = new Map<number, string[]>();
|
readonly #cache = new Map<number, string[]>();
|
||||||
@@ -13,7 +13,7 @@ export class IndexedDbCache<TSchema extends Document = Document> {
|
|||||||
set(hashCode: number, documents: WithId<TSchema>[]) {
|
set(hashCode: number, documents: WithId<TSchema>[]) {
|
||||||
this.#cache.set(
|
this.#cache.set(
|
||||||
hashCode,
|
hashCode,
|
||||||
documents.map((document) => document.id)
|
documents.map((document) => document.id),
|
||||||
);
|
);
|
||||||
for (const document of documents) {
|
for (const document of documents) {
|
||||||
this.#documents.set(document.id, document);
|
this.#documents.set(document.id, document);
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { IDBPDatabase, openDB } from "idb/with-async-ittr";
|
import { IDBPDatabase, openDB } from "idb";
|
||||||
|
|
||||||
import { Collection } from "../Collection.js";
|
import { Collection } from "../../collection.ts";
|
||||||
import { DBLogger } from "../Logger.js";
|
import { DBLogger } from "../../logger.ts";
|
||||||
import { Document } from "../Types.js";
|
import { Document } from "../../types.ts";
|
||||||
import { IndexedDbStorage } from "./IndexedDb.Storage.js";
|
import { Registrars } from "../registrars.ts";
|
||||||
import { Registrars } from "./Registrars.js";
|
import { IndexedDbStorage } from "./storage.ts";
|
||||||
|
|
||||||
function log() {}
|
function log() {}
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ export class IndexedDatabase<T extends StringRecord<Document>> {
|
|||||||
store.createIndex(keyPath, keyPath, options);
|
store.createIndex(keyPath, keyPath, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
for (const { name } of options.registrars) {
|
for (const { name } of options.registrars) {
|
||||||
this.#collections.set(name, new Collection(name, new IndexedDbStorage(name, this.#db, options.log ?? log)));
|
this.#collections.set(name, new Collection(name, new IndexedDbStorage(name, this.#db, options.log ?? log)));
|
||||||
288
src/databases/indexeddb/storage.ts
Normal file
288
src/databases/indexeddb/storage.ts
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
import { IDBPDatabase } from "idb";
|
||||||
|
import { createUpdater, Query } from "mingo";
|
||||||
|
import { UpdateOptions } from "mingo/core";
|
||||||
|
import { UpdateExpression } from "mingo/updater";
|
||||||
|
|
||||||
|
import { DBLogger, InsertLog, QueryLog, RemoveLog, ReplaceLog, UpdateLog } from "../../logger.ts";
|
||||||
|
import { DuplicateDocumentError } from "../../storage/errors.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";
|
||||||
|
import { IndexedDbCache } from "./cache.ts";
|
||||||
|
|
||||||
|
const update = createUpdater({ cloneMode: "deep" });
|
||||||
|
|
||||||
|
export class IndexedDbStorage<TSchema extends Document = Document> extends Storage<TSchema> {
|
||||||
|
readonly #cache = new IndexedDbCache<TSchema>();
|
||||||
|
readonly #documents = new Map<string, WithId<TSchema>>();
|
||||||
|
readonly #promise: Promise<IDBPDatabase>;
|
||||||
|
|
||||||
|
#db?: IDBPDatabase;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
name: string,
|
||||||
|
promise: Promise<IDBPDatabase>,
|
||||||
|
readonly log: DBLogger,
|
||||||
|
) {
|
||||||
|
super(name);
|
||||||
|
this.#promise = promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async resolve() {
|
||||||
|
if (this.#db === undefined) {
|
||||||
|
this.#db = await this.#promise;
|
||||||
|
}
|
||||||
|
const documents = await this.db.getAll(this.name);
|
||||||
|
for (const document of documents) {
|
||||||
|
this.#documents.set(document.id, document);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async has(id: string): Promise<boolean> {
|
||||||
|
return this.#documents.has(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
get db() {
|
||||||
|
if (this.#db === undefined) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
return this.#db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------------
|
||||||
|
| Insert
|
||||||
|
|--------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
async insertOne(data: Partial<TSchema>): Promise<InsertOneResult> {
|
||||||
|
const logger = new InsertLog(this.name);
|
||||||
|
|
||||||
|
const document = { ...data, id: data.id ?? crypto.randomUUID() } as WithId<TSchema>;
|
||||||
|
if (await this.has(document.id)) {
|
||||||
|
throw new DuplicateDocumentError(document, this as any);
|
||||||
|
}
|
||||||
|
this.#documents.set(document.id, document);
|
||||||
|
|
||||||
|
this.broadcast("insertOne", document);
|
||||||
|
this.#cache.flush();
|
||||||
|
|
||||||
|
this.log(logger.result());
|
||||||
|
|
||||||
|
return getInsertOneResult(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
async insertMany(documents: Partial<TSchema>[]): Promise<InsertManyResult> {
|
||||||
|
const logger = new InsertLog(this.name);
|
||||||
|
|
||||||
|
const result: TSchema[] = [];
|
||||||
|
for (const data of documents) {
|
||||||
|
const document = { ...data, id: data.id ?? crypto.randomUUID() } as WithId<TSchema>;
|
||||||
|
result.push(document);
|
||||||
|
this.#documents.set(document.id, document);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.broadcast("insertMany", result);
|
||||||
|
this.#cache.flush();
|
||||||
|
|
||||||
|
this.log(logger.result());
|
||||||
|
|
||||||
|
return getInsertManyResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------------
|
||||||
|
| Read
|
||||||
|
|--------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
async findById(id: string): Promise<WithId<TSchema> | undefined> {
|
||||||
|
return this.#documents.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async find(filter: Filter<WithId<TSchema>>, options: Options = {}): Promise<WithId<TSchema>[]> {
|
||||||
|
const logger = new QueryLog(this.name, { filter, options });
|
||||||
|
|
||||||
|
const hashCode = this.#cache.hash(filter, options);
|
||||||
|
const cached = this.#cache.get(hashCode);
|
||||||
|
if (cached !== undefined) {
|
||||||
|
this.log(logger.result({ cached: true }));
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cursor = new Query(filter ?? {}).find<TSchema>(Array.from(this.#documents.values()));
|
||||||
|
if (options !== undefined) {
|
||||||
|
cursor = addOptions(cursor, options);
|
||||||
|
}
|
||||||
|
return cursor.all() as WithId<TSchema>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------------
|
||||||
|
| Update
|
||||||
|
|--------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
async updateOne(
|
||||||
|
filter: Filter<WithId<TSchema>>,
|
||||||
|
expr: UpdateExpression,
|
||||||
|
arrayFilters?: Filter<WithId<TSchema>>[],
|
||||||
|
condition?: Filter<WithId<TSchema>>,
|
||||||
|
options?: UpdateOptions,
|
||||||
|
): Promise<UpdateResult> {
|
||||||
|
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);
|
||||||
|
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<WithId<TSchema>>,
|
||||||
|
expr: UpdateExpression,
|
||||||
|
arrayFilters?: Filter<WithId<TSchema>>[],
|
||||||
|
condition?: Filter<WithId<TSchema>>,
|
||||||
|
options?: UpdateOptions,
|
||||||
|
): Promise<UpdateResult> {
|
||||||
|
const logger = new UpdateLog(this.name, { filter, expr, arrayFilters, condition, options });
|
||||||
|
const query = new Query(filter);
|
||||||
|
|
||||||
|
const documents: WithId<TSchema>[] = [];
|
||||||
|
|
||||||
|
let matchedCount = 0;
|
||||||
|
let modifiedCount = 0;
|
||||||
|
|
||||||
|
for (const document of Array.from(this.#documents.values())) {
|
||||||
|
if (query.test(document) === true) {
|
||||||
|
matchedCount += 1;
|
||||||
|
const modified = update(document, expr, arrayFilters, condition, options);
|
||||||
|
if (modified.length > 0) {
|
||||||
|
modifiedCount += 1;
|
||||||
|
documents.push(document);
|
||||||
|
this.#documents.set(document.id, document);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.broadcast("updateMany", documents);
|
||||||
|
this.#cache.flush();
|
||||||
|
|
||||||
|
this.log(logger.result());
|
||||||
|
|
||||||
|
return new UpdateResult(matchedCount, modifiedCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
async replace(filter: Filter<WithId<TSchema>>, document: WithId<TSchema>): Promise<UpdateResult> {
|
||||||
|
const logger = new ReplaceLog(this.name, document);
|
||||||
|
|
||||||
|
const query = new Query(filter);
|
||||||
|
|
||||||
|
const documents: WithId<TSchema>[] = [];
|
||||||
|
|
||||||
|
let matchedCount = 0;
|
||||||
|
let modifiedCount = 0;
|
||||||
|
|
||||||
|
for (const current of Array.from(this.#documents.values())) {
|
||||||
|
if (query.test(current) === true) {
|
||||||
|
matchedCount += 1;
|
||||||
|
modifiedCount += 1;
|
||||||
|
documents.push(document);
|
||||||
|
this.#documents.set(document.id, document);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.broadcast("updateMany", documents);
|
||||||
|
this.#cache.flush();
|
||||||
|
|
||||||
|
this.log(logger.result({ count: matchedCount }));
|
||||||
|
|
||||||
|
return new UpdateResult(matchedCount, modifiedCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------------
|
||||||
|
| Remove
|
||||||
|
|--------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
async remove(filter: Filter<WithId<TSchema>>): Promise<RemoveResult> {
|
||||||
|
const logger = new RemoveLog(this.name, { filter });
|
||||||
|
const documents = Array.from(this.#documents.values());
|
||||||
|
const query = new Query(filter);
|
||||||
|
let count = 0;
|
||||||
|
for (const document of documents) {
|
||||||
|
if (query.test(document) === true) {
|
||||||
|
this.#documents.delete(document.id);
|
||||||
|
this.broadcast("remove", document);
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.#cache.flush();
|
||||||
|
this.log(logger.result({ count: documents.length }));
|
||||||
|
return new RemoveResult(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------------
|
||||||
|
| Count
|
||||||
|
|--------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
async count(filter?: Filter<WithId<TSchema>>): Promise<number> {
|
||||||
|
return new Query(filter ?? {}).find(Array.from(this.#documents.values())).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------------
|
||||||
|
| Flush
|
||||||
|
|--------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
async flush(): Promise<void> {
|
||||||
|
await this.db.clear(this.name);
|
||||||
|
this.#documents.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------------
|
||||||
|
| Save
|
||||||
|
|--------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
async save(): Promise<void> {
|
||||||
|
// this.db.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
const logger = new InsertLog(this.name);
|
||||||
|
|
||||||
|
const document = { ...data, id: data.id ?? crypto.randomUUID() } as any;
|
||||||
|
if (await this.has(document.id)) {
|
||||||
|
throw new DuplicateDocumentError(document, this as any);
|
||||||
|
}
|
||||||
|
await this.db.transaction(this.name, "readwrite", { durability: "relaxed" }).store.add(document);
|
||||||
|
|
||||||
|
this.broadcast("insertOne", document);
|
||||||
|
this.#cache.flush();
|
||||||
|
|
||||||
|
this.log(logger.result());
|
||||||
|
|
||||||
|
return getInsertOneResult(document);
|
||||||
|
*/
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Collection } from "../Collection.js";
|
import { Collection } from "../../collection.ts";
|
||||||
import { Document } from "../Types.js";
|
import { Document } from "../../types.ts";
|
||||||
import { MemoryStorage } from "./MemoryDb.Storage.js";
|
import { Registrars } from "../registrars.ts";
|
||||||
import { Registrars } from "./Registrars.js";
|
import { MemoryStorage } from "./storage.ts";
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
import { Query } from "mingo";
|
import { createUpdater, Query } from "mingo";
|
||||||
import { nanoid } from "nanoid";
|
import { UpdateOptions } from "mingo/core";
|
||||||
|
import { UpdateExpression } from "mingo/updater";
|
||||||
|
|
||||||
|
import { DuplicateDocumentError } from "../../storage/errors.ts";
|
||||||
import {
|
import {
|
||||||
addOptions,
|
|
||||||
DuplicateDocumentError,
|
|
||||||
getInsertManyResult,
|
getInsertManyResult,
|
||||||
getInsertOneResult,
|
getInsertOneResult,
|
||||||
InsertManyResult,
|
type InsertManyResult,
|
||||||
InsertOneResult,
|
type InsertOneResult,
|
||||||
Options,
|
} from "../../storage/operators/insert.ts";
|
||||||
RemoveResult,
|
import { RemoveResult } from "../../storage/operators/remove.ts";
|
||||||
Storage,
|
import { UpdateResult } from "../../storage/operators/update.ts";
|
||||||
update,
|
import { addOptions, Options, Storage } from "../../storage/storage.ts";
|
||||||
UpdateResult
|
import type { Document, Filter, WithId } from "../../types.ts";
|
||||||
} from "../Storage/mod.js";
|
|
||||||
import { Document, Filter, UpdateFilter, WithId } from "../Types.js";
|
const update = createUpdater({ cloneMode: "deep" });
|
||||||
|
|
||||||
export class MemoryStorage<TSchema extends Document = Document> extends Storage<TSchema> {
|
export class MemoryStorage<TSchema extends Document = Document> extends Storage<TSchema> {
|
||||||
readonly #documents = new Map<string, WithId<TSchema>>();
|
readonly #documents = new Map<string, WithId<TSchema>>();
|
||||||
@@ -28,7 +28,7 @@ export class MemoryStorage<TSchema extends Document = Document> extends Storage<
|
|||||||
}
|
}
|
||||||
|
|
||||||
async insertOne(data: Partial<TSchema>): Promise<InsertOneResult> {
|
async insertOne(data: Partial<TSchema>): Promise<InsertOneResult> {
|
||||||
const document = { ...data, id: data.id ?? nanoid() } as WithId<TSchema>;
|
const document = { ...data, id: data.id ?? crypto.randomUUID() } as WithId<TSchema>;
|
||||||
if (await this.has(document.id)) {
|
if (await this.has(document.id)) {
|
||||||
throw new DuplicateDocumentError(document, this as any);
|
throw new DuplicateDocumentError(document, this as any);
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ export class MemoryStorage<TSchema extends Document = Document> extends Storage<
|
|||||||
async insertMany(documents: Partial<TSchema>[]): Promise<InsertManyResult> {
|
async insertMany(documents: Partial<TSchema>[]): Promise<InsertManyResult> {
|
||||||
const result: TSchema[] = [];
|
const result: TSchema[] = [];
|
||||||
for (const data of documents) {
|
for (const data of documents) {
|
||||||
const document = { ...data, id: data.id ?? nanoid() } as WithId<TSchema>;
|
const document = { ...data, id: data.id ?? crypto.randomUUID() } as WithId<TSchema>;
|
||||||
result.push(document);
|
result.push(document);
|
||||||
this.#documents.set(document.id, document);
|
this.#documents.set(document.id, document);
|
||||||
}
|
}
|
||||||
@@ -62,12 +62,18 @@ export class MemoryStorage<TSchema extends Document = Document> extends Storage<
|
|||||||
return cursor.all() as WithId<TSchema>[];
|
return cursor.all() as WithId<TSchema>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOne(filter: Filter<WithId<TSchema>>, operators: UpdateFilter<TSchema>): Promise<UpdateResult> {
|
async updateOne(
|
||||||
|
filter: Filter<WithId<TSchema>>,
|
||||||
|
expr: UpdateExpression,
|
||||||
|
arrayFilters?: Filter<WithId<TSchema>>[],
|
||||||
|
condition?: Filter<WithId<TSchema>>,
|
||||||
|
options?: UpdateOptions,
|
||||||
|
): Promise<UpdateResult> {
|
||||||
const query = new Query(filter);
|
const query = new Query(filter);
|
||||||
for (const current of Array.from(this.#documents.values())) {
|
for (const document of Array.from(this.#documents.values())) {
|
||||||
if (query.test(current) === true) {
|
if (query.test(document) === true) {
|
||||||
const { modified, document } = update<TSchema>(filter, operators, current);
|
const modified = update(document, expr, arrayFilters, condition, options);
|
||||||
if (modified === true) {
|
if (modified.length > 0) {
|
||||||
this.#documents.set(document.id, document);
|
this.#documents.set(document.id, document);
|
||||||
this.broadcast("updateOne", document);
|
this.broadcast("updateOne", document);
|
||||||
return new UpdateResult(1, 1);
|
return new UpdateResult(1, 1);
|
||||||
@@ -78,7 +84,13 @@ export class MemoryStorage<TSchema extends Document = Document> extends Storage<
|
|||||||
return new UpdateResult(0, 0);
|
return new UpdateResult(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateMany(filter: Filter<WithId<TSchema>>, operators: UpdateFilter<TSchema>): Promise<UpdateResult> {
|
async updateMany(
|
||||||
|
filter: Filter<WithId<TSchema>>,
|
||||||
|
expr: UpdateExpression,
|
||||||
|
arrayFilters?: Filter<WithId<TSchema>>[],
|
||||||
|
condition?: Filter<WithId<TSchema>>,
|
||||||
|
options?: UpdateOptions,
|
||||||
|
): Promise<UpdateResult> {
|
||||||
const query = new Query(filter);
|
const query = new Query(filter);
|
||||||
|
|
||||||
const documents: WithId<TSchema>[] = [];
|
const documents: WithId<TSchema>[] = [];
|
||||||
@@ -86,11 +98,11 @@ export class MemoryStorage<TSchema extends Document = Document> extends Storage<
|
|||||||
let matchedCount = 0;
|
let matchedCount = 0;
|
||||||
let modifiedCount = 0;
|
let modifiedCount = 0;
|
||||||
|
|
||||||
for (const current of Array.from(this.#documents.values())) {
|
for (const document of Array.from(this.#documents.values())) {
|
||||||
if (query.test(current) === true) {
|
if (query.test(document) === true) {
|
||||||
matchedCount += 1;
|
matchedCount += 1;
|
||||||
const { modified, document } = update<TSchema>(filter, operators, current);
|
const modified = update(document, expr, arrayFilters, condition, options);
|
||||||
if (modified === true) {
|
if (modified.length > 0) {
|
||||||
modifiedCount += 1;
|
modifiedCount += 1;
|
||||||
documents.push(document);
|
documents.push(document);
|
||||||
this.#documents.set(document.id, document);
|
this.#documents.set(document.id, document);
|
||||||
2
src/databases/mod.ts
Normal file
2
src/databases/mod.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./indexeddb/database.ts";
|
||||||
|
export * from "./memory/database.ts";
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
import { Query } from "mingo";
|
import { createUpdater, Query } from "mingo";
|
||||||
import { nanoid } from "nanoid";
|
import { UpdateOptions } from "mingo/core";
|
||||||
|
import { UpdateExpression } from "mingo/updater";
|
||||||
|
|
||||||
|
import { DuplicateDocumentError } from "../../storage/errors.ts";
|
||||||
import {
|
import {
|
||||||
addOptions,
|
|
||||||
DuplicateDocumentError,
|
|
||||||
getInsertManyResult,
|
getInsertManyResult,
|
||||||
getInsertOneResult,
|
getInsertOneResult,
|
||||||
InsertManyResult,
|
InsertManyResult,
|
||||||
InsertOneResult,
|
InsertOneResult,
|
||||||
Options,
|
} from "../../storage/operators/insert.ts";
|
||||||
RemoveResult,
|
import { RemoveResult } from "../../storage/operators/remove.ts";
|
||||||
Storage,
|
import { UpdateResult } from "../../storage/operators/update.ts";
|
||||||
update,
|
import { addOptions, Options, Storage } from "../../storage/storage.ts";
|
||||||
UpdateResult
|
import { Document, Filter, WithId } from "../../types.ts";
|
||||||
} from "../Storage/mod.js";
|
|
||||||
import { Document, Filter, UpdateFilter, WithId } from "../Types.js";
|
const update = createUpdater({ cloneMode: "deep" });
|
||||||
|
|
||||||
export class ObserverStorage<TSchema extends Document = Document> extends Storage<TSchema> {
|
export class ObserverStorage<TSchema extends Document = Document> extends Storage<TSchema> {
|
||||||
readonly #documents = new Map<string, WithId<TSchema>>();
|
readonly #documents = new Map<string, WithId<TSchema>>();
|
||||||
@@ -28,7 +28,7 @@ export class ObserverStorage<TSchema extends Document = Document> extends Storag
|
|||||||
}
|
}
|
||||||
|
|
||||||
async insertOne(data: Partial<TSchema>): Promise<InsertOneResult> {
|
async insertOne(data: Partial<TSchema>): Promise<InsertOneResult> {
|
||||||
const document = { ...data, id: data.id ?? nanoid() } as WithId<TSchema>;
|
const document = { ...data, id: data.id ?? crypto.randomUUID() } as WithId<TSchema>;
|
||||||
if (await this.has(document.id)) {
|
if (await this.has(document.id)) {
|
||||||
throw new DuplicateDocumentError(document, this as any);
|
throw new DuplicateDocumentError(document, this as any);
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ export class ObserverStorage<TSchema extends Document = Document> extends Storag
|
|||||||
async insertMany(documents: Partial<TSchema>[]): Promise<InsertManyResult> {
|
async insertMany(documents: Partial<TSchema>[]): Promise<InsertManyResult> {
|
||||||
const result: TSchema[] = [];
|
const result: TSchema[] = [];
|
||||||
for (const data of documents) {
|
for (const data of documents) {
|
||||||
const document = { ...data, id: data.id ?? nanoid() } as WithId<TSchema>;
|
const document = { ...data, id: data.id ?? crypto.randomUUID() } as WithId<TSchema>;
|
||||||
result.push(document);
|
result.push(document);
|
||||||
this.#documents.set(document.id, document);
|
this.#documents.set(document.id, document);
|
||||||
}
|
}
|
||||||
@@ -58,13 +58,20 @@ export class ObserverStorage<TSchema extends Document = Document> extends Storag
|
|||||||
return cursor.all() as WithId<TSchema>[];
|
return cursor.all() as WithId<TSchema>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOne(filter: Filter<WithId<TSchema>>, operators: UpdateFilter<TSchema>): Promise<UpdateResult> {
|
async updateOne(
|
||||||
|
filter: Filter<WithId<TSchema>>,
|
||||||
|
expr: UpdateExpression,
|
||||||
|
arrayFilters?: Filter<WithId<TSchema>>[],
|
||||||
|
condition?: Filter<WithId<TSchema>>,
|
||||||
|
options?: UpdateOptions,
|
||||||
|
): Promise<UpdateResult> {
|
||||||
const query = new Query(filter);
|
const query = new Query(filter);
|
||||||
for (const current of Array.from(this.#documents.values())) {
|
for (const document of Array.from(this.#documents.values())) {
|
||||||
if (query.test(current) === true) {
|
if (query.test(document) === true) {
|
||||||
const { modified, document } = update<TSchema>(filter, operators, current);
|
const modified = update(document, expr, arrayFilters, condition, options);
|
||||||
if (modified === true) {
|
if (modified.length > 0) {
|
||||||
this.#documents.set(document.id, document);
|
this.#documents.set(document.id, document);
|
||||||
|
this.broadcast("updateOne", document);
|
||||||
return new UpdateResult(1, 1);
|
return new UpdateResult(1, 1);
|
||||||
}
|
}
|
||||||
return new UpdateResult(1, 0);
|
return new UpdateResult(1, 0);
|
||||||
@@ -73,7 +80,13 @@ export class ObserverStorage<TSchema extends Document = Document> extends Storag
|
|||||||
return new UpdateResult(0, 0);
|
return new UpdateResult(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateMany(filter: Filter<WithId<TSchema>>, operators: UpdateFilter<TSchema>): Promise<UpdateResult> {
|
async updateMany(
|
||||||
|
filter: Filter<WithId<TSchema>>,
|
||||||
|
expr: UpdateExpression,
|
||||||
|
arrayFilters?: Filter<WithId<TSchema>>[],
|
||||||
|
condition?: Filter<WithId<TSchema>>,
|
||||||
|
options?: UpdateOptions,
|
||||||
|
): Promise<UpdateResult> {
|
||||||
const query = new Query(filter);
|
const query = new Query(filter);
|
||||||
|
|
||||||
const documents: WithId<TSchema>[] = [];
|
const documents: WithId<TSchema>[] = [];
|
||||||
@@ -81,11 +94,11 @@ export class ObserverStorage<TSchema extends Document = Document> extends Storag
|
|||||||
let matchedCount = 0;
|
let matchedCount = 0;
|
||||||
let modifiedCount = 0;
|
let modifiedCount = 0;
|
||||||
|
|
||||||
for (const current of Array.from(this.#documents.values())) {
|
for (const document of Array.from(this.#documents.values())) {
|
||||||
if (query.test(current) === true) {
|
if (query.test(document) === true) {
|
||||||
matchedCount += 1;
|
matchedCount += 1;
|
||||||
const { modified, document } = update<TSchema>(filter, operators, current);
|
const modified = update(filter, expr, arrayFilters, condition, options);
|
||||||
if (modified === true) {
|
if (modified.length > 0) {
|
||||||
modifiedCount += 1;
|
modifiedCount += 1;
|
||||||
documents.push(document);
|
documents.push(document);
|
||||||
this.#documents.set(document.id, document);
|
this.#documents.set(document.id, document);
|
||||||
@@ -93,6 +106,8 @@ export class ObserverStorage<TSchema extends Document = Document> extends Storag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.broadcast("updateMany", documents);
|
||||||
|
|
||||||
return new UpdateResult(matchedCount, modifiedCount);
|
return new UpdateResult(matchedCount, modifiedCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3,4 +3,4 @@ export type Registrars = {
|
|||||||
indexes?: Index[];
|
indexes?: Index[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type Index = [string, IDBIndexParameters?];
|
type Index = [string, any?];
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export * from "./Collection.js";
|
|
||||||
export * from "./Databases/mod.js";
|
|
||||||
export * from "./Storage/mod.js";
|
|
||||||
export type { Document, Filter } from "./Types.js";
|
|
||||||
@@ -14,7 +14,10 @@ abstract class LogEvent {
|
|||||||
|
|
||||||
data?: Record<string, any>;
|
data?: Record<string, any>;
|
||||||
|
|
||||||
constructor(readonly collection: string, readonly query?: Record<string, any>) {}
|
constructor(
|
||||||
|
readonly collection: string,
|
||||||
|
readonly query?: Record<string, any>,
|
||||||
|
) {}
|
||||||
|
|
||||||
result(data?: Record<string, any>): this {
|
result(data?: Record<string, any>): this {
|
||||||
this.performance.result();
|
this.performance.result();
|
||||||
4
src/mod.ts
Normal file
4
src/mod.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./collection.ts";
|
||||||
|
export * from "./databases/mod.ts";
|
||||||
|
export * from "./storage/mod.ts";
|
||||||
|
export type { Document, Filter } from "./types.ts";
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Query } from "mingo";
|
import { Query } from "mingo";
|
||||||
|
|
||||||
import { Document, Filter, WithId } from "../Types.js";
|
import type { Document, Filter, WithId } from "../types.ts";
|
||||||
|
|
||||||
export function isMatch<TSchema extends Document = Document>(
|
export function isMatch<TSchema extends Document = Document>(
|
||||||
document: WithId<TSchema>,
|
document: WithId<TSchema>,
|
||||||
filter?: Filter<WithId<TSchema>>
|
filter?: Filter<WithId<TSchema>>,
|
||||||
): boolean {
|
): boolean {
|
||||||
return !filter || new Query(filter).test(document);
|
return !filter || new Query(filter).test(document);
|
||||||
}
|
}
|
||||||
3
src/observe/mod.ts
Normal file
3
src/observe/mod.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export type { Action } from "./action.ts";
|
||||||
|
export * from "./observe.ts";
|
||||||
|
export * from "./observe-one.ts";
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Collection } from "../Collection.js";
|
import { Collection } from "../collection.ts";
|
||||||
import { Document, Filter, WithId } from "../Types.js";
|
import { Document, Filter, WithId } from "../types.ts";
|
||||||
import { isMatch } from "./IsMatch.js";
|
import { isMatch } from "./is-match.ts";
|
||||||
|
|
||||||
export function observeOne<TSchema extends Document = Document>(
|
export function observeOne<TSchema extends Document = Document>(
|
||||||
collection: Collection<TSchema>,
|
collection: Collection<TSchema>,
|
||||||
filter: Filter<WithId<TSchema>>,
|
filter: Filter<WithId<TSchema>>,
|
||||||
onChange: (document: Document | undefined) => void
|
onChange: (document: Document | undefined) => void,
|
||||||
): {
|
): {
|
||||||
unsubscribe: () => void;
|
unsubscribe: () => void;
|
||||||
} {
|
} {
|
||||||
@@ -35,6 +35,6 @@ export function observeOne<TSchema extends Document = Document>(
|
|||||||
return {
|
return {
|
||||||
unsubscribe: () => {
|
unsubscribe: () => {
|
||||||
subscription.unsubscribe();
|
subscription.unsubscribe();
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
import { Query } from "mingo";
|
import { Query } from "mingo";
|
||||||
|
|
||||||
import { Collection } from "../Collection.js";
|
import { Collection } from "../collection.ts";
|
||||||
import { addOptions, ChangeEvent, Options } from "../Storage/mod.js";
|
import { addOptions, ChangeEvent, Options } from "../storage/mod.ts";
|
||||||
import { Document, Filter, WithId } from "../Types.js";
|
import { Document, Filter, WithId } from "../types.ts";
|
||||||
import { Store } from "./Store.js";
|
import { Store } from "./store.ts";
|
||||||
|
|
||||||
export function observe<TSchema extends Document = Document>(
|
export function observe<TSchema extends Document = Document>(
|
||||||
collection: Collection<TSchema>,
|
collection: Collection<TSchema>,
|
||||||
filter: Filter<WithId<TSchema>>,
|
filter: Filter<WithId<TSchema>>,
|
||||||
options: Options | undefined,
|
options: Options | undefined,
|
||||||
onChange: (documents: WithId<TSchema>[], changed: WithId<TSchema>[], type: ChangeEvent<TSchema>["type"]) => void
|
onChange: (documents: WithId<TSchema>[], changed: WithId<TSchema>[], type: ChangeEvent<TSchema>["type"]) => void,
|
||||||
): {
|
): {
|
||||||
unsubscribe: () => void;
|
unsubscribe: () => void;
|
||||||
} {
|
} {
|
||||||
const store = Store.create<TSchema>();
|
const store = Store.create<TSchema>();
|
||||||
|
|
||||||
let debounce: NodeJS.Timeout;
|
let debounce: any;
|
||||||
|
|
||||||
collection.find(filter, options).then(async (documents) => {
|
collection.find(filter, options).then(async (documents) => {
|
||||||
const resolved = await store.resolve(documents);
|
const resolved = await store.resolve(documents);
|
||||||
@@ -51,7 +51,7 @@ export function observe<TSchema extends Document = Document>(
|
|||||||
});
|
});
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -60,13 +60,13 @@ export function observe<TSchema extends Document = Document>(
|
|||||||
subscription.unsubscribe();
|
subscription.unsubscribe();
|
||||||
}
|
}
|
||||||
store.destroy();
|
store.destroy();
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyQueryOptions<TSchema extends Document = Document>(
|
function applyQueryOptions<TSchema extends Document = Document>(
|
||||||
documents: WithId<TSchema>[],
|
documents: WithId<TSchema>[],
|
||||||
options?: Options
|
options?: Options,
|
||||||
): WithId<TSchema>[] {
|
): WithId<TSchema>[] {
|
||||||
if (options !== undefined) {
|
if (options !== undefined) {
|
||||||
return addOptions(new Query({}).find<TSchema>(documents), options).all() as WithId<TSchema>[];
|
return addOptions(new Query({}).find<TSchema>(documents), options).all() as WithId<TSchema>[];
|
||||||
@@ -1,15 +1,13 @@
|
|||||||
import { nanoid } from "nanoid";
|
import { ObserverStorage } from "../databases/observer/storage.ts";
|
||||||
|
import { Storage } from "../storage/mod.ts";
|
||||||
import { ObserverStorage } from "../Databases/Observer.Storage.js";
|
import { Document, Filter, WithId } from "../types.ts";
|
||||||
import { Storage } from "../Storage/mod.js";
|
import { isMatch } from "./is-match.ts";
|
||||||
import { Document, Filter, WithId } from "../Types.js";
|
|
||||||
import { isMatch } from "./IsMatch.js";
|
|
||||||
|
|
||||||
export class Store<TSchema extends Document = Document> {
|
export class Store<TSchema extends Document = Document> {
|
||||||
private constructor(private storage: Storage<TSchema>) {}
|
private constructor(private storage: Storage<TSchema>) {}
|
||||||
|
|
||||||
static create<TSchema extends Document = Document>() {
|
static create<TSchema extends Document = Document>() {
|
||||||
return new Store<TSchema>(new ObserverStorage<TSchema>(`observer[${nanoid()}]`));
|
return new Store<TSchema>(new ObserverStorage<TSchema>(`observer[${crypto.randomUUID()}]`));
|
||||||
}
|
}
|
||||||
|
|
||||||
get destroy() {
|
get destroy() {
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
import { RawObject } from "mingo/types";
|
import { RawObject } from "mingo/types";
|
||||||
|
|
||||||
import { Document } from "../Types.js";
|
import { Document } from "../types.ts";
|
||||||
import type { Storage } from "./Storage.js";
|
import type { Storage } from "./storage.ts";
|
||||||
|
|
||||||
export class DuplicateDocumentError extends Error {
|
export class DuplicateDocumentError extends Error {
|
||||||
readonly type = "DuplicateDocumentError";
|
readonly type = "DuplicateDocumentError";
|
||||||
|
|
||||||
constructor(readonly document: Document, storage: Storage) {
|
constructor(
|
||||||
|
readonly document: Document,
|
||||||
|
storage: Storage,
|
||||||
|
) {
|
||||||
super(
|
super(
|
||||||
`Collection Insert Violation: Document '${document.id}' already exists in ${storage.name} collection ${storage.id}`
|
`Collection Insert Violation: Document '${document.id}' already exists in ${storage.name} collection ${storage.id}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
5
src/storage/mod.ts
Normal file
5
src/storage/mod.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export * from "./errors.ts";
|
||||||
|
export * from "./operators/insert.ts";
|
||||||
|
export * from "./operators/remove.ts";
|
||||||
|
export * from "./operators/update.ts";
|
||||||
|
export * from "./storage.ts";
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Document } from "../../../Types.js";
|
import type { Document } from "../../types.ts";
|
||||||
|
|
||||||
export function getInsertManyResult(documents: Document[]): InsertManyResult {
|
export function getInsertManyResult(documents: Document[]): InsertManyResult {
|
||||||
return {
|
return {
|
||||||
@@ -7,14 +7,14 @@ export function getInsertManyResult(documents: Document[]): InsertManyResult {
|
|||||||
insertedIds: documents.reduce<{ [key: number]: string | number }>((map, document, index) => {
|
insertedIds: documents.reduce<{ [key: number]: string | number }>((map, document, index) => {
|
||||||
map[index] = document.id;
|
map[index] = document.id;
|
||||||
return map;
|
return map;
|
||||||
}, {})
|
}, {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getInsertOneResult(document: Document): InsertOneResult {
|
export function getInsertOneResult(document: Document): InsertOneResult {
|
||||||
return {
|
return {
|
||||||
acknowledged: true,
|
acknowledged: true,
|
||||||
insertedId: document.id
|
insertedId: document.id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
6
src/storage/operators/update.ts
Normal file
6
src/storage/operators/update.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export class UpdateResult {
|
||||||
|
constructor(
|
||||||
|
readonly matched = 0,
|
||||||
|
readonly modified = 0,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
|
import { UpdateOptions } from "mingo/core";
|
||||||
import { Cursor } from "mingo/cursor";
|
import { Cursor } from "mingo/cursor";
|
||||||
import { nanoid } from "nanoid";
|
import { UpdateExpression } from "mingo/updater";
|
||||||
import { Subject } from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
|
|
||||||
import { BroadcastChannel, StorageBroadcast } from "../Broadcast.js";
|
import { BroadcastChannel, StorageBroadcast } from "../broadcast.ts";
|
||||||
import { Document, Filter, UpdateFilter, WithId } from "../Types.js";
|
import { Document, Filter, WithId } from "../types.ts";
|
||||||
import { InsertManyResult, InsertOneResult } from "./Operators/Insert/mod.js";
|
import { InsertManyResult, InsertOneResult } from "./operators/insert.ts";
|
||||||
import { RemoveResult } from "./Operators/Remove/mod.js";
|
import { RemoveResult } from "./operators/remove.ts";
|
||||||
import { UpdateResult } from "./Operators/Update/mod.js";
|
import { UpdateResult } from "./operators/update.ts";
|
||||||
|
|
||||||
export abstract class Storage<TSchema extends Document = Document> {
|
export abstract class Storage<TSchema extends Document = Document> {
|
||||||
readonly observable = {
|
readonly observable = {
|
||||||
change: new Subject<ChangeEvent<TSchema>>(),
|
change: new Subject<ChangeEvent<TSchema>>(),
|
||||||
flush: new Subject<void>()
|
flush: new Subject<void>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
status: Status = "loading";
|
status: Status = "loading";
|
||||||
@@ -20,7 +21,7 @@ export abstract class Storage<TSchema extends Document = Document> {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly name: string,
|
readonly name: string,
|
||||||
readonly id = nanoid()
|
readonly id = crypto.randomUUID(),
|
||||||
) {
|
) {
|
||||||
this.#channel = new BroadcastChannel(`valkyr:db:${name}`);
|
this.#channel = new BroadcastChannel(`valkyr:db:${name}`);
|
||||||
this.#channel.onmessage = ({ data }: MessageEvent<StorageBroadcast<TSchema>>) => {
|
this.#channel.onmessage = ({ data }: MessageEvent<StorageBroadcast<TSchema>>) => {
|
||||||
@@ -98,9 +99,21 @@ export abstract class Storage<TSchema extends Document = Document> {
|
|||||||
|
|
||||||
abstract find(filter?: Filter<WithId<TSchema>>, options?: Options): Promise<WithId<TSchema>[]>;
|
abstract find(filter?: Filter<WithId<TSchema>>, options?: Options): Promise<WithId<TSchema>[]>;
|
||||||
|
|
||||||
abstract updateOne(filter: Filter<WithId<TSchema>>, operators: UpdateFilter<TSchema>): Promise<UpdateResult>;
|
abstract updateOne(
|
||||||
|
filter: Filter<WithId<TSchema>>,
|
||||||
|
expr: UpdateExpression,
|
||||||
|
arrayFilters?: Filter<WithId<TSchema>>[],
|
||||||
|
condition?: Filter<WithId<TSchema>>,
|
||||||
|
options?: UpdateOptions,
|
||||||
|
): Promise<UpdateResult>;
|
||||||
|
|
||||||
abstract updateMany(filter: Filter<WithId<TSchema>>, operators: UpdateFilter<TSchema>): Promise<UpdateResult>;
|
abstract updateMany(
|
||||||
|
filter: Filter<WithId<TSchema>>,
|
||||||
|
expr: UpdateExpression,
|
||||||
|
arrayFilters?: Filter<WithId<TSchema>>[],
|
||||||
|
condition?: Filter<WithId<TSchema>>,
|
||||||
|
options?: UpdateOptions,
|
||||||
|
): Promise<UpdateResult>;
|
||||||
|
|
||||||
abstract replace(filter: Filter<WithId<TSchema>>, document: TSchema): Promise<UpdateResult>;
|
abstract replace(filter: Filter<WithId<TSchema>>, document: TSchema): Promise<UpdateResult>;
|
||||||
|
|
||||||
@@ -129,7 +142,7 @@ export abstract class Storage<TSchema extends Document = Document> {
|
|||||||
|
|
||||||
export function addOptions<TSchema extends Document = Document>(
|
export function addOptions<TSchema extends Document = Document>(
|
||||||
cursor: Cursor<TSchema>,
|
cursor: Cursor<TSchema>,
|
||||||
options: Options
|
options: Options,
|
||||||
): Cursor<TSchema> {
|
): Cursor<TSchema> {
|
||||||
if (options.sort) {
|
if (options.sort) {
|
||||||
cursor.sort(options.sort);
|
cursor.sort(options.sort);
|
||||||
@@ -129,11 +129,12 @@ type Flatten<Type> = Type extends ReadonlyArray<infer Item> ? Item : Type;
|
|||||||
|
|
||||||
type IsAny<Type, ResultIfAny, ResultIfNotAny> = true extends false & Type ? ResultIfAny : ResultIfNotAny;
|
type IsAny<Type, ResultIfAny, ResultIfNotAny> = true extends false & Type ? ResultIfAny : ResultIfNotAny;
|
||||||
|
|
||||||
type FilterOperations<T> = T extends Record<string, any>
|
type FilterOperations<T> =
|
||||||
? {
|
T extends Record<string, any>
|
||||||
[key in keyof T]?: FilterOperators<T[key]>;
|
? {
|
||||||
}
|
[key in keyof T]?: FilterOperators<T[key]>;
|
||||||
: FilterOperators<T>;
|
}
|
||||||
|
: FilterOperators<T>;
|
||||||
|
|
||||||
type ArrayOperator<Type> = {
|
type ArrayOperator<Type> = {
|
||||||
$each?: Array<Flatten<Type>>;
|
$each?: Array<Flatten<Type>>;
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import { IndexedDbCache } from "../src/Databases/IndexedDb.Cache.js";
|
import { afterEach, beforeEach, describe, it } from "@std/testing/bdd";
|
||||||
import { Options } from "../src/index.js";
|
import { expect } from "expect";
|
||||||
import { WithId } from "../src/Types.js";
|
|
||||||
|
import { IndexedDbCache } from "../src/databases/indexeddb/cache.ts";
|
||||||
|
import { Options } from "../src/storage/storage.ts";
|
||||||
|
import type { WithId } from "../src/types.ts";
|
||||||
|
|
||||||
describe("IndexedDbCache", () => {
|
describe("IndexedDbCache", () => {
|
||||||
let cache: IndexedDbCache;
|
let cache: IndexedDbCache;
|
||||||
@@ -15,31 +18,31 @@ describe("IndexedDbCache", () => {
|
|||||||
|
|
||||||
const sampleDocuments: WithId<{ name: string }>[] = [
|
const sampleDocuments: WithId<{ name: string }>[] = [
|
||||||
{ id: "doc1", name: "Document 1" },
|
{ id: "doc1", name: "Document 1" },
|
||||||
{ id: "doc2", name: "Document 2" }
|
{ id: "doc2", name: "Document 2" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const sampleCriteria = { name: { $eq: "Document 1" } };
|
const sampleCriteria = { name: { $eq: "Document 1" } };
|
||||||
const sampleOptions: Options = { sort: { name: 1 } };
|
const sampleOptions: Options = { sort: { name: 1 } };
|
||||||
|
|
||||||
test("hash", () => {
|
it("hash", () => {
|
||||||
const hashCode = cache.hash(sampleCriteria, sampleOptions);
|
const hashCode = cache.hash(sampleCriteria, sampleOptions);
|
||||||
expect(typeof hashCode).toBe("number");
|
expect(typeof hashCode).toBe("number");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("set and get", () => {
|
it("set and get", () => {
|
||||||
const hashCode = cache.hash(sampleCriteria, sampleOptions);
|
const hashCode = cache.hash(sampleCriteria, sampleOptions);
|
||||||
cache.set(hashCode, sampleDocuments);
|
cache.set(hashCode, sampleDocuments);
|
||||||
const result = cache.get(hashCode);
|
const result = cache.get(hashCode);
|
||||||
expect(result).toEqual(sampleDocuments);
|
expect(result).toEqual(sampleDocuments);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("get undefined", () => {
|
it("get undefined", () => {
|
||||||
const hashCode = cache.hash(sampleCriteria, sampleOptions);
|
const hashCode = cache.hash(sampleCriteria, sampleOptions);
|
||||||
const result = cache.get(hashCode);
|
const result = cache.get(hashCode);
|
||||||
expect(result).toBeUndefined();
|
expect(result).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("flush", () => {
|
it("flush", () => {
|
||||||
const hashCode = cache.hash(sampleCriteria, sampleOptions);
|
const hashCode = cache.hash(sampleCriteria, sampleOptions);
|
||||||
cache.set(hashCode, sampleDocuments);
|
cache.set(hashCode, sampleDocuments);
|
||||||
cache.flush();
|
cache.flush();
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import { Collection } from "../src/Collection.js";
|
import { describe, it } from "@std/testing/bdd";
|
||||||
import { MemoryStorage } from "../src/Databases/MemoryDb.Storage.js";
|
import { expect } from "expect";
|
||||||
import { getUsers, UserDocument } from "./Users.Mock.js";
|
|
||||||
|
import { Collection } from "../src/collection.ts";
|
||||||
|
import { MemoryStorage } from "../src/databases/memory/storage.ts";
|
||||||
|
import { getUsers, UserDocument } from "./users.mock.ts";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
import { hashCodeQuery } from "../src/Hash.js";
|
import { describe, it } from "@std/testing/bdd";
|
||||||
import { Options } from "../src/index.js";
|
import { expect } from "expect";
|
||||||
|
|
||||||
|
import { hashCodeQuery } from "../src/hash.ts";
|
||||||
|
import { Options } from "../src/mod.ts";
|
||||||
|
|
||||||
describe("hashCodeQuery", () => {
|
describe("hashCodeQuery", () => {
|
||||||
const filter = { name: { $eq: "Document 1" } };
|
const filter = { name: { $eq: "Document 1" } };
|
||||||
const options: Options = { sort: { name: 1 } };
|
const options: Options = { sort: { name: 1 } };
|
||||||
|
|
||||||
test("return correct hash code", () => {
|
it("return correct hash code", () => {
|
||||||
const hashCode = hashCodeQuery(filter, options);
|
const hashCode = hashCodeQuery(filter, options);
|
||||||
expect(typeof hashCode).toBe("number");
|
expect(typeof hashCode).toBe("number");
|
||||||
});
|
});
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
import { Collection } from "../src/Collection.js";
|
import "fake-indexeddb/auto";
|
||||||
import { MemoryStorage } from "../src/Databases/MemoryDb.Storage.js";
|
|
||||||
import { DuplicateDocumentError } from "../src/index.js";
|
import { describe, it } from "@std/testing/bdd";
|
||||||
import { getUsers } from "./Users.Mock.js";
|
import { expect } from "expect";
|
||||||
|
|
||||||
|
import { Collection } from "../src/collection.ts";
|
||||||
|
import { MemoryStorage } from "../src/databases/memory/storage.ts";
|
||||||
|
import { DuplicateDocumentError } from "../src/storage/errors.ts";
|
||||||
|
import { getUsers } from "./users.mock.ts";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
import { Collection } from "../src/Collection.js";
|
import { describe, it } from "@std/testing/bdd";
|
||||||
import { MemoryStorage } from "../src/Databases/MemoryDb.Storage.js";
|
import { expect } from "expect";
|
||||||
import { RemoveResult } from "../src/index.js";
|
|
||||||
import { getUsers } from "./Users.Mock.js";
|
import { Collection } from "../src/collection.ts";
|
||||||
|
import { MemoryStorage } from "../src/databases/memory/storage.ts";
|
||||||
|
import { RemoveResult } from "../src/storage/operators/remove.ts";
|
||||||
|
import { getUsers } from "./users.mock.ts";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
import { clone } from "../src/Clone.js";
|
import { clone } from "../src/clone.ts";
|
||||||
import { WithId } from "../src/Types.js";
|
import { WithId } from "../src/types.ts";
|
||||||
|
|
||||||
const users: WithId<UserDocument>[] = [
|
const users: WithId<UserDocument>[] = [
|
||||||
{
|
{
|
||||||
@@ -9,10 +9,10 @@ const users: WithId<UserDocument>[] = [
|
|||||||
friends: [
|
friends: [
|
||||||
{
|
{
|
||||||
id: "user-2",
|
id: "user-2",
|
||||||
alias: "Jane"
|
alias: "Jane",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
interests: ["movies", "tv", "sports"]
|
interests: ["movies", "tv", "sports"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "user-2",
|
id: "user-2",
|
||||||
@@ -21,11 +21,11 @@ const users: WithId<UserDocument>[] = [
|
|||||||
friends: [
|
friends: [
|
||||||
{
|
{
|
||||||
id: "user-1",
|
id: "user-1",
|
||||||
alias: "John"
|
alias: "John",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
interests: ["movies", "fitness", "dance"]
|
interests: ["movies", "fitness", "dance"],
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getUsers(): WithId<UserDocument>[] {
|
export function getUsers(): WithId<UserDocument>[] {
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"rootDir": "./src",
|
|
||||||
"outDir": "./dist"
|
|
||||||
},
|
|
||||||
"exclude": ["./tests"]
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
|
||||||
"display": "Default",
|
|
||||||
"compilerOptions": {
|
|
||||||
"strict": true,
|
|
||||||
|
|
||||||
"lib": ["ES2022", "dom", "dom.iterable"],
|
|
||||||
"target": "ES2022",
|
|
||||||
"module": "ES2022",
|
|
||||||
"moduleResolution": "Bundler",
|
|
||||||
|
|
||||||
"declaration": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"useUnknownInCatchVariables": false,
|
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true
|
|
||||||
},
|
|
||||||
"include": ["./src", "./tests"],
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
||||||
16
version.cjs
16
version.cjs
@@ -1,16 +0,0 @@
|
|||||||
const { readFileSync, writeFileSync } = require("node:fs");
|
|
||||||
const { join, resolve } = require("node:path");
|
|
||||||
|
|
||||||
const ROOT_DIR = resolve(__dirname);
|
|
||||||
const VERSION = process.argv[process.argv.indexOf("--version") + 1];
|
|
||||||
|
|
||||||
setPackageVersions(ROOT_DIR, VERSION);
|
|
||||||
|
|
||||||
function setPackageVersions(rootDir, version) {
|
|
||||||
const packagePath = join(rootDir, "package.json");
|
|
||||||
const packageJson = JSON.parse(readFileSync(packagePath, "utf-8"));
|
|
||||||
|
|
||||||
packageJson.version = version;
|
|
||||||
|
|
||||||
writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user