@vandenberghinc/volt 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vrepo +28 -0
- package/.vscode/tasks.json +87 -0
- package/README.md +67 -0
- package/backend/dist/cjs/blacklist.d.ts +10 -0
- package/backend/dist/cjs/blacklist.js +53 -0
- package/backend/dist/cjs/cli.d.ts +2 -0
- package/backend/dist/cjs/cli.js +263 -0
- package/backend/dist/cjs/database.d.ts +364 -0
- package/backend/dist/cjs/database.js +1962 -0
- package/backend/dist/cjs/endpoint.d.ts +57 -0
- package/backend/dist/cjs/endpoint.js +425 -0
- package/backend/dist/cjs/file_watcher.d.ts +44 -0
- package/backend/dist/cjs/file_watcher.js +348 -0
- package/backend/dist/cjs/frontend.d.ts +13 -0
- package/backend/dist/cjs/frontend.js +30 -0
- package/backend/dist/cjs/image_endpoint.d.ts +24 -0
- package/backend/dist/cjs/image_endpoint.js +210 -0
- package/backend/dist/cjs/logger.d.ts +5 -0
- package/backend/dist/cjs/logger.js +16 -0
- package/backend/dist/cjs/meta.d.ts +50 -0
- package/backend/dist/cjs/meta.js +153 -0
- package/backend/dist/cjs/mutex.d.ts +24 -0
- package/backend/dist/cjs/mutex.js +52 -0
- package/backend/dist/cjs/package.json +1 -0
- package/backend/dist/cjs/payments/paddle.d.ts +161 -0
- package/backend/dist/cjs/payments/paddle.js +2301 -0
- package/backend/dist/cjs/plugins/browser.d.ts +36 -0
- package/backend/dist/cjs/plugins/browser.js +183 -0
- package/backend/dist/cjs/plugins/communication.d.ts +70 -0
- package/backend/dist/cjs/plugins/communication.js +177 -0
- package/backend/dist/cjs/plugins/css.d.ts +10 -0
- package/backend/dist/cjs/plugins/css.js +71 -0
- package/backend/dist/cjs/plugins/mail.d.ts +277 -0
- package/backend/dist/cjs/plugins/mail.js +1419 -0
- package/backend/dist/cjs/plugins/pdf.d.ts +757 -0
- package/backend/dist/cjs/plugins/pdf.js +1694 -0
- package/backend/dist/cjs/plugins/thread_monitor.d.ts +18 -0
- package/backend/dist/cjs/plugins/thread_monitor.js +127 -0
- package/backend/dist/cjs/plugins/ts/compiler.d.ts +132 -0
- package/backend/dist/cjs/plugins/ts/compiler.js +944 -0
- package/backend/dist/cjs/plugins/ts/preprocessing.d.ts +14 -0
- package/backend/dist/cjs/plugins/ts/preprocessing.js +762 -0
- package/backend/dist/cjs/rate_limit.d.ts +65 -0
- package/backend/dist/cjs/rate_limit.js +463 -0
- package/backend/dist/cjs/request.deprc.d.ts +48 -0
- package/backend/dist/cjs/request.deprc.js +572 -0
- package/backend/dist/cjs/response.deprc.d.ts +55 -0
- package/backend/dist/cjs/response.deprc.js +275 -0
- package/backend/dist/cjs/server.d.ts +311 -0
- package/backend/dist/cjs/server.js +3475 -0
- package/backend/dist/cjs/splash_screen.d.ts +35 -0
- package/backend/dist/cjs/splash_screen.js +152 -0
- package/backend/dist/cjs/status.d.ts +60 -0
- package/backend/dist/cjs/status.js +199 -0
- package/backend/dist/cjs/stream.d.ts +75 -0
- package/backend/dist/cjs/stream.js +954 -0
- package/backend/dist/cjs/users.d.ts +111 -0
- package/backend/dist/cjs/users.js +1945 -0
- package/backend/dist/cjs/utils.d.ts +27 -0
- package/backend/dist/cjs/utils.js +329 -0
- package/backend/dist/cjs/view.d.ts +52 -0
- package/backend/dist/cjs/view.js +568 -0
- package/backend/dist/cjs/vinc.d.ts +2 -0
- package/backend/dist/cjs/vinc.dev.d.ts +2 -0
- package/backend/dist/cjs/vinc.dev.js +42 -0
- package/backend/dist/cjs/vinc.js +42 -0
- package/backend/dist/cjs/volt.d.ts +15 -0
- package/backend/dist/cjs/volt.js +64 -0
- package/backend/dist/css/adyen.css +92 -0
- package/backend/dist/css/volt.css +65 -0
- package/backend/dist/esm/blacklist.d.ts +10 -0
- package/backend/dist/esm/blacklist.js +49 -0
- package/backend/dist/esm/cli.d.ts +2 -0
- package/backend/dist/esm/cli.js +228 -0
- package/backend/dist/esm/database.d.ts +364 -0
- package/backend/dist/esm/database.js +1957 -0
- package/backend/dist/esm/endpoint.d.ts +57 -0
- package/backend/dist/esm/endpoint.js +421 -0
- package/backend/dist/esm/file_watcher.d.ts +44 -0
- package/backend/dist/esm/file_watcher.js +313 -0
- package/backend/dist/esm/frontend.d.ts +13 -0
- package/backend/dist/esm/frontend.js +27 -0
- package/backend/dist/esm/image_endpoint.d.ts +24 -0
- package/backend/dist/esm/image_endpoint.js +206 -0
- package/backend/dist/esm/logger.d.ts +5 -0
- package/backend/dist/esm/logger.js +13 -0
- package/backend/dist/esm/meta.d.ts +50 -0
- package/backend/dist/esm/meta.js +149 -0
- package/backend/dist/esm/mutex.d.ts +24 -0
- package/backend/dist/esm/mutex.js +48 -0
- package/backend/dist/esm/payments/paddle.d.ts +161 -0
- package/backend/dist/esm/payments/paddle.js +2261 -0
- package/backend/dist/esm/plugins/browser.d.ts +36 -0
- package/backend/dist/esm/plugins/browser.js +176 -0
- package/backend/dist/esm/plugins/communication.d.ts +70 -0
- package/backend/dist/esm/plugins/communication.js +169 -0
- package/backend/dist/esm/plugins/css.d.ts +10 -0
- package/backend/dist/esm/plugins/css.js +64 -0
- package/backend/dist/esm/plugins/mail.d.ts +277 -0
- package/backend/dist/esm/plugins/mail.js +1403 -0
- package/backend/dist/esm/plugins/pdf.d.ts +757 -0
- package/backend/dist/esm/plugins/pdf.js +1694 -0
- package/backend/dist/esm/plugins/thread_monitor.d.ts +18 -0
- package/backend/dist/esm/plugins/thread_monitor.js +120 -0
- package/backend/dist/esm/plugins/ts/compiler.d.ts +132 -0
- package/backend/dist/esm/plugins/ts/compiler.js +907 -0
- package/backend/dist/esm/plugins/ts/preprocessing.d.ts +14 -0
- package/backend/dist/esm/plugins/ts/preprocessing.js +724 -0
- package/backend/dist/esm/rate_limit.d.ts +65 -0
- package/backend/dist/esm/rate_limit.js +425 -0
- package/backend/dist/esm/request.deprc.d.ts +48 -0
- package/backend/dist/esm/request.deprc.js +572 -0
- package/backend/dist/esm/response.deprc.d.ts +55 -0
- package/backend/dist/esm/response.deprc.js +275 -0
- package/backend/dist/esm/server.d.ts +311 -0
- package/backend/dist/esm/server.js +3435 -0
- package/backend/dist/esm/splash_screen.d.ts +35 -0
- package/backend/dist/esm/splash_screen.js +148 -0
- package/backend/dist/esm/status.d.ts +60 -0
- package/backend/dist/esm/status.js +196 -0
- package/backend/dist/esm/stream.d.ts +75 -0
- package/backend/dist/esm/stream.js +947 -0
- package/backend/dist/esm/users.d.ts +111 -0
- package/backend/dist/esm/users.js +1908 -0
- package/backend/dist/esm/utils.d.ts +27 -0
- package/backend/dist/esm/utils.js +324 -0
- package/backend/dist/esm/view.d.ts +52 -0
- package/backend/dist/esm/view.js +561 -0
- package/backend/dist/esm/vinc.d.ts +2 -0
- package/backend/dist/esm/vinc.dev.d.ts +2 -0
- package/backend/dist/esm/vinc.dev.js +6 -0
- package/backend/dist/esm/vinc.js +6 -0
- package/backend/dist/esm/volt.d.ts +15 -0
- package/backend/dist/esm/volt.js +23 -0
- package/backend/dist/esm-dev/blacklist.d.ts +10 -0
- package/backend/dist/esm-dev/blacklist.js +49 -0
- package/backend/dist/esm-dev/cli.d.ts +2 -0
- package/backend/dist/esm-dev/cli.js +228 -0
- package/backend/dist/esm-dev/database.d.ts +364 -0
- package/backend/dist/esm-dev/database.js +1957 -0
- package/backend/dist/esm-dev/endpoint.d.ts +57 -0
- package/backend/dist/esm-dev/endpoint.js +421 -0
- package/backend/dist/esm-dev/file_watcher.d.ts +44 -0
- package/backend/dist/esm-dev/file_watcher.js +313 -0
- package/backend/dist/esm-dev/frontend.d.ts +13 -0
- package/backend/dist/esm-dev/frontend.js +27 -0
- package/backend/dist/esm-dev/image_endpoint.d.ts +24 -0
- package/backend/dist/esm-dev/image_endpoint.js +206 -0
- package/backend/dist/esm-dev/logger.d.ts +5 -0
- package/backend/dist/esm-dev/logger.js +13 -0
- package/backend/dist/esm-dev/meta.d.ts +50 -0
- package/backend/dist/esm-dev/meta.js +149 -0
- package/backend/dist/esm-dev/mutex.d.ts +24 -0
- package/backend/dist/esm-dev/mutex.js +48 -0
- package/backend/dist/esm-dev/payments/paddle.d.ts +161 -0
- package/backend/dist/esm-dev/payments/paddle.js +2261 -0
- package/backend/dist/esm-dev/plugins/browser.d.ts +36 -0
- package/backend/dist/esm-dev/plugins/browser.js +176 -0
- package/backend/dist/esm-dev/plugins/communication.d.ts +70 -0
- package/backend/dist/esm-dev/plugins/communication.js +169 -0
- package/backend/dist/esm-dev/plugins/css.d.ts +10 -0
- package/backend/dist/esm-dev/plugins/css.js +64 -0
- package/backend/dist/esm-dev/plugins/mail.d.ts +277 -0
- package/backend/dist/esm-dev/plugins/mail.js +1403 -0
- package/backend/dist/esm-dev/plugins/pdf.d.ts +757 -0
- package/backend/dist/esm-dev/plugins/pdf.js +1694 -0
- package/backend/dist/esm-dev/plugins/thread_monitor.d.ts +18 -0
- package/backend/dist/esm-dev/plugins/thread_monitor.js +120 -0
- package/backend/dist/esm-dev/plugins/ts/compiler.d.ts +132 -0
- package/backend/dist/esm-dev/plugins/ts/compiler.js +907 -0
- package/backend/dist/esm-dev/plugins/ts/preprocessing.d.ts +14 -0
- package/backend/dist/esm-dev/plugins/ts/preprocessing.js +724 -0
- package/backend/dist/esm-dev/rate_limit.d.ts +65 -0
- package/backend/dist/esm-dev/rate_limit.js +425 -0
- package/backend/dist/esm-dev/request.deprc.d.ts +48 -0
- package/backend/dist/esm-dev/request.deprc.js +572 -0
- package/backend/dist/esm-dev/response.deprc.d.ts +55 -0
- package/backend/dist/esm-dev/response.deprc.js +275 -0
- package/backend/dist/esm-dev/server.d.ts +311 -0
- package/backend/dist/esm-dev/server.js +3435 -0
- package/backend/dist/esm-dev/splash_screen.d.ts +35 -0
- package/backend/dist/esm-dev/splash_screen.js +148 -0
- package/backend/dist/esm-dev/status.d.ts +60 -0
- package/backend/dist/esm-dev/status.js +196 -0
- package/backend/dist/esm-dev/stream.d.ts +75 -0
- package/backend/dist/esm-dev/stream.js +947 -0
- package/backend/dist/esm-dev/users.d.ts +111 -0
- package/backend/dist/esm-dev/users.js +1908 -0
- package/backend/dist/esm-dev/utils.d.ts +27 -0
- package/backend/dist/esm-dev/utils.js +324 -0
- package/backend/dist/esm-dev/view.d.ts +52 -0
- package/backend/dist/esm-dev/view.js +561 -0
- package/backend/dist/esm-dev/vinc.d.ts +2 -0
- package/backend/dist/esm-dev/vinc.dev.d.ts +2 -0
- package/backend/dist/esm-dev/vinc.dev.js +6 -0
- package/backend/dist/esm-dev/vinc.js +6 -0
- package/backend/dist/esm-dev/volt.d.ts +15 -0
- package/backend/dist/esm-dev/volt.js +23 -0
- package/backend/src/blacklist.ts +69 -0
- package/backend/src/cli.js +245 -0
- package/backend/src/database.ts +2241 -0
- package/backend/src/endpoint.ts +494 -0
- package/backend/src/file_watcher.ts +359 -0
- package/backend/src/frontend.ts +35 -0
- package/backend/src/globals.d.ts +8 -0
- package/backend/src/image_endpoint.ts +258 -0
- package/backend/src/logger.ts +18 -0
- package/backend/src/meta.ts +202 -0
- package/backend/src/mutex.ts +51 -0
- package/backend/src/payments/paddle.ts +2659 -0
- package/backend/src/plugins/browser.ts +188 -0
- package/backend/src/plugins/communication.ts +204 -0
- package/backend/src/plugins/css.ts +84 -0
- package/backend/src/plugins/fonts/Menlo-Bold.ttf +0 -0
- package/backend/src/plugins/fonts/Menlo-Regular.ttf +0 -0
- package/backend/src/plugins/mail.ts +1720 -0
- package/backend/src/plugins/pdf.js +1932 -0
- package/backend/src/plugins/thread_monitor.ts +164 -0
- package/backend/src/plugins/ts/compiler.ts +1242 -0
- package/backend/src/plugins/ts/preprocessing.ts +812 -0
- package/backend/src/rate_limit.ts +503 -0
- package/backend/src/request.deprc.js +626 -0
- package/backend/src/response.deprc.js +354 -0
- package/backend/src/server.ts +4149 -0
- package/backend/src/splash_screen.ts +192 -0
- package/backend/src/status.ts +199 -0
- package/backend/src/stream.ts +1070 -0
- package/backend/src/users.ts +2077 -0
- package/backend/src/utils.ts +359 -0
- package/backend/src/view.ts +655 -0
- package/backend/src/vinc.dev.js +6 -0
- package/backend/src/vinc.ts +6 -0
- package/backend/src/volt.js +25 -0
- package/backend/tsconfig.cjs.json +29 -0
- package/backend/tsconfig.esm.dev.json +34 -0
- package/backend/tsconfig.esm.json +30 -0
- package/backend/tsconfig.json +2 -0
- package/frontend/compile.js +436 -0
- package/frontend/dist/elements/base.d.ts +9891 -0
- package/frontend/dist/elements/base.js +8818 -0
- package/frontend/dist/elements/module.d.ts +16 -0
- package/frontend/dist/elements/module.js +178 -0
- package/frontend/dist/modules/array.d.ts +37 -0
- package/frontend/dist/modules/array.js +284 -0
- package/frontend/dist/modules/auth.d.ts +45 -0
- package/frontend/dist/modules/auth.js +138 -0
- package/frontend/dist/modules/colors.d.ts +26 -0
- package/frontend/dist/modules/colors.js +340 -0
- package/frontend/dist/modules/compression.d.ts +6 -0
- package/frontend/dist/modules/compression.js +999 -0
- package/frontend/dist/modules/cookies.d.ts +17 -0
- package/frontend/dist/modules/cookies.js +166 -0
- package/frontend/dist/modules/date.d.ts +142 -0
- package/frontend/dist/modules/date.js +493 -0
- package/frontend/dist/modules/events.d.ts +7 -0
- package/frontend/dist/modules/events.js +90 -0
- package/frontend/dist/modules/google.d.ts +10 -0
- package/frontend/dist/modules/google.js +53 -0
- package/frontend/dist/modules/meta.d.ts +9 -0
- package/frontend/dist/modules/meta.js +45 -0
- package/frontend/dist/modules/mutex.d.ts +8 -0
- package/frontend/dist/modules/mutex.js +52 -0
- package/frontend/dist/modules/number.d.ts +12 -0
- package/frontend/dist/modules/number.js +8 -0
- package/frontend/dist/modules/object.d.ts +50 -0
- package/frontend/dist/modules/object.js +147 -0
- package/frontend/dist/modules/paddle.d.ts +1403 -0
- package/frontend/dist/modules/paddle.js +2641 -0
- package/frontend/dist/modules/scheme.d.ts +207 -0
- package/frontend/dist/modules/scheme.js +649 -0
- package/frontend/dist/modules/settings.d.ts +3 -0
- package/frontend/dist/modules/settings.js +4 -0
- package/frontend/dist/modules/statics.d.ts +4 -0
- package/frontend/dist/modules/statics.js +45 -0
- package/frontend/dist/modules/string.d.ts +163 -0
- package/frontend/dist/modules/string.js +291 -0
- package/frontend/dist/modules/support.d.ts +18 -0
- package/frontend/dist/modules/support.js +102 -0
- package/frontend/dist/modules/themes.d.ts +8 -0
- package/frontend/dist/modules/themes.js +17 -0
- package/frontend/dist/modules/user.d.ts +58 -0
- package/frontend/dist/modules/user.js +279 -0
- package/frontend/dist/modules/utils.d.ts +58 -0
- package/frontend/dist/modules/utils.js +1159 -0
- package/frontend/dist/types/gradient.d.ts +12 -0
- package/frontend/dist/types/gradient.js +79 -0
- package/frontend/dist/ui/border_button.d.ts +177 -0
- package/frontend/dist/ui/border_button.js +235 -0
- package/frontend/dist/ui/button.d.ts +42 -0
- package/frontend/dist/ui/button.js +114 -0
- package/frontend/dist/ui/canvas.d.ts +56 -0
- package/frontend/dist/ui/canvas.js +411 -0
- package/frontend/dist/ui/checkbox.d.ts +72 -0
- package/frontend/dist/ui/checkbox.js +277 -0
- package/frontend/dist/ui/code.d.ts +232 -0
- package/frontend/dist/ui/code.js +977 -0
- package/frontend/dist/ui/color.d.ts +1 -0
- package/frontend/dist/ui/color.js +110 -0
- package/frontend/dist/ui/context_menu.d.ts +30 -0
- package/frontend/dist/ui/context_menu.js +211 -0
- package/frontend/dist/ui/css.d.ts +10 -0
- package/frontend/dist/ui/css.js +44 -0
- package/frontend/dist/ui/divider.d.ts +18 -0
- package/frontend/dist/ui/divider.js +82 -0
- package/frontend/dist/ui/dropdown.d.ts +115 -0
- package/frontend/dist/ui/dropdown.js +446 -0
- package/frontend/dist/ui/for_each.d.ts +38 -0
- package/frontend/dist/ui/for_each.js +97 -0
- package/frontend/dist/ui/form.d.ts +25 -0
- package/frontend/dist/ui/form.js +227 -0
- package/frontend/dist/ui/frame_modes.d.ts +28 -0
- package/frontend/dist/ui/frame_modes.js +116 -0
- package/frontend/dist/ui/google_map.d.ts +31 -0
- package/frontend/dist/ui/google_map.js +111 -0
- package/frontend/dist/ui/gradient.d.ts +24 -0
- package/frontend/dist/ui/gradient.js +115 -0
- package/frontend/dist/ui/image.d.ts +138 -0
- package/frontend/dist/ui/image.js +570 -0
- package/frontend/dist/ui/input.d.ts +316 -0
- package/frontend/dist/ui/input.js +1187 -0
- package/frontend/dist/ui/link.d.ts +39 -0
- package/frontend/dist/ui/link.js +146 -0
- package/frontend/dist/ui/list.d.ts +33 -0
- package/frontend/dist/ui/list.js +161 -0
- package/frontend/dist/ui/loader_button.d.ts +108 -0
- package/frontend/dist/ui/loader_button.js +207 -0
- package/frontend/dist/ui/loaders.d.ts +60 -0
- package/frontend/dist/ui/loaders.js +150 -0
- package/frontend/dist/ui/popup.d.ts +84 -0
- package/frontend/dist/ui/popup.js +331 -0
- package/frontend/dist/ui/pseudo.d.ts +16 -0
- package/frontend/dist/ui/pseudo.js +81 -0
- package/frontend/dist/ui/scroller.d.ts +131 -0
- package/frontend/dist/ui/scroller.js +1251 -0
- package/frontend/dist/ui/slider.d.ts +35 -0
- package/frontend/dist/ui/slider.js +203 -0
- package/frontend/dist/ui/spacer.d.ts +20 -0
- package/frontend/dist/ui/spacer.js +83 -0
- package/frontend/dist/ui/span.d.ts +11 -0
- package/frontend/dist/ui/span.js +75 -0
- package/frontend/dist/ui/stack.d.ts +123 -0
- package/frontend/dist/ui/stack.js +344 -0
- package/frontend/dist/ui/steps.d.ts +72 -0
- package/frontend/dist/ui/steps.js +306 -0
- package/frontend/dist/ui/style.d.ts +12 -0
- package/frontend/dist/ui/style.js +78 -0
- package/frontend/dist/ui/switch.d.ts +44 -0
- package/frontend/dist/ui/switch.js +280 -0
- package/frontend/dist/ui/table.d.ts +118 -0
- package/frontend/dist/ui/table.js +411 -0
- package/frontend/dist/ui/tabs.d.ts +85 -0
- package/frontend/dist/ui/tabs.js +392 -0
- package/frontend/dist/ui/text.d.ts +19 -0
- package/frontend/dist/ui/text.js +88 -0
- package/frontend/dist/ui/theme.d.ts +25 -0
- package/frontend/dist/ui/theme.js +237 -0
- package/frontend/dist/ui/title.d.ts +36 -0
- package/frontend/dist/ui/title.js +127 -0
- package/frontend/dist/ui/ui.d.ts +38 -0
- package/frontend/dist/ui/ui.js +41 -0
- package/frontend/dist/ui/view.d.ts +25 -0
- package/frontend/dist/ui/view.js +93 -0
- package/frontend/dist/volt.d.ts +22 -0
- package/frontend/dist/volt.js +27 -0
- package/frontend/exports.json +1340 -0
- package/frontend/src/css/adyen.css +92 -0
- package/frontend/src/css/volt.css +65 -0
- package/frontend/src/elements/base.ts +16790 -0
- package/frontend/src/elements/module.ts +184 -0
- package/frontend/src/elements/types.d.ts +155 -0
- package/frontend/src/modules/array.ts +366 -0
- package/frontend/src/modules/auth.ts +188 -0
- package/frontend/src/modules/colors.ts +449 -0
- package/frontend/src/modules/compression.ts +67 -0
- package/frontend/src/modules/cookies.ts +182 -0
- package/frontend/src/modules/date.js +535 -0
- package/frontend/src/modules/date.ts +583 -0
- package/frontend/src/modules/events.ts +96 -0
- package/frontend/src/modules/google.ts +60 -0
- package/frontend/src/modules/meta.ts +59 -0
- package/frontend/src/modules/mutex.ts +59 -0
- package/frontend/src/modules/number.ts +20 -0
- package/frontend/src/modules/object.ts +212 -0
- package/frontend/src/modules/paddle.ts +2990 -0
- package/frontend/src/modules/scheme.ts +740 -0
- package/frontend/src/modules/settings.ts +5 -0
- package/frontend/src/modules/statics.ts +47 -0
- package/frontend/src/modules/string.ts +500 -0
- package/frontend/src/modules/support.ts +118 -0
- package/frontend/src/modules/themes.ts +24 -0
- package/frontend/src/modules/user.ts +321 -0
- package/frontend/src/modules/utils.ts +1260 -0
- package/frontend/src/static/admin/admin.png +0 -0
- package/frontend/src/static/admin/password.webp +0 -0
- package/frontend/src/static/icons/copy.webp +0 -0
- package/frontend/src/static/payments/arrow.long.webp +0 -0
- package/frontend/src/static/payments/arrow.long2.webp +0 -0
- package/frontend/src/static/payments/cancelled.webp +0 -0
- package/frontend/src/static/payments/check.sign.webp +0 -0
- package/frontend/src/static/payments/check.webp +0 -0
- package/frontend/src/static/payments/close.webp +0 -0
- package/frontend/src/static/payments/error.webp +0 -0
- package/frontend/src/static/payments/exclamation.webp +0 -0
- package/frontend/src/static/payments/minus.webp +0 -0
- package/frontend/src/static/payments/party.webp +0 -0
- package/frontend/src/static/payments/plus.webp +0 -0
- package/frontend/src/static/payments/shopping_cart.webp +0 -0
- package/frontend/src/static/payments/trash.webp +0 -0
- package/frontend/src/types/global.d.ts +4 -0
- package/frontend/src/types/gradient.ts +87 -0
- package/frontend/src/ui/any_element.d.ts +5 -0
- package/frontend/src/ui/border_button.ts +320 -0
- package/frontend/src/ui/button.ts +62 -0
- package/frontend/src/ui/canvas.ts +431 -0
- package/frontend/src/ui/checkbox.ts +284 -0
- package/frontend/src/ui/code.ts +1049 -0
- package/frontend/src/ui/color.ts +117 -0
- package/frontend/src/ui/context_menu.ts +194 -0
- package/frontend/src/ui/css.ts +57 -0
- package/frontend/src/ui/divider.ts +28 -0
- package/frontend/src/ui/dropdown.ts +503 -0
- package/frontend/src/ui/for_each.ts +71 -0
- package/frontend/src/ui/form.ts +208 -0
- package/frontend/src/ui/frame_modes.ts +140 -0
- package/frontend/src/ui/google_map.ts +70 -0
- package/frontend/src/ui/gradient.ts +73 -0
- package/frontend/src/ui/image.ts +587 -0
- package/frontend/src/ui/input.ts +1284 -0
- package/frontend/src/ui/link.ts +77 -0
- package/frontend/src/ui/list.ts +88 -0
- package/frontend/src/ui/loader_button.ts +192 -0
- package/frontend/src/ui/loaders.ts +126 -0
- package/frontend/src/ui/popup.ts +370 -0
- package/frontend/src/ui/pseudo.ts +33 -0
- package/frontend/src/ui/scroller.ts +1324 -0
- package/frontend/src/ui/slider.ts +215 -0
- package/frontend/src/ui/spacer.ts +29 -0
- package/frontend/src/ui/span.ts +23 -0
- package/frontend/src/ui/stack.ts +238 -0
- package/frontend/src/ui/steps.ts +334 -0
- package/frontend/src/ui/style.ts +26 -0
- package/frontend/src/ui/switch.ts +286 -0
- package/frontend/src/ui/table.ts +323 -0
- package/frontend/src/ui/tabs.ts +441 -0
- package/frontend/src/ui/text.ts +38 -0
- package/frontend/src/ui/theme.ts +279 -0
- package/frontend/src/ui/title.ts +64 -0
- package/frontend/src/ui/ui.ts +47 -0
- package/frontend/src/ui/view.ts +44 -0
- package/frontend/src/volt.ts +31 -0
- package/package.json +58 -0
|
@@ -0,0 +1,2241 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Author: Daan van den Bergh
|
|
3
|
+
* Copyright: © 2022 - 2024 Daan van den Bergh.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------
|
|
7
|
+
// Libraries.
|
|
8
|
+
|
|
9
|
+
import { ChildProcess, spawn } from "child_process";
|
|
10
|
+
import { deserialize, serialize } from "bson";
|
|
11
|
+
import { Transform } from 'stream';
|
|
12
|
+
import { MongoClient, Collection as MongoCollection, ObjectId } from 'mongodb';
|
|
13
|
+
import { logger, LogSource } from "./logger.js";
|
|
14
|
+
import { Status } from "./status.js";
|
|
15
|
+
import { vlib } from "@vinc";
|
|
16
|
+
|
|
17
|
+
const log_source = new LogSource("Database")
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------
|
|
20
|
+
// Collection.
|
|
21
|
+
// Path based collection, so "myfile", "mydir/myfile".
|
|
22
|
+
|
|
23
|
+
// @warning: The "path" param must always be allowed to be an object or string, also for the UIDCollection class.
|
|
24
|
+
// @warning: THE DATABASE COLLECTION SHOULD ALSO ACCEPT OBJECTS FOR PATHS.
|
|
25
|
+
/* @docs:
|
|
26
|
+
@nav: Backend
|
|
27
|
+
@chapter: Database
|
|
28
|
+
@title: Collection
|
|
29
|
+
@desc: The database collection class.
|
|
30
|
+
@note: The document attribute `_path` is a reserved index attribute for the path of the document.
|
|
31
|
+
@attribute:
|
|
32
|
+
@name: col
|
|
33
|
+
@desc: The native mongodb collection.
|
|
34
|
+
*/
|
|
35
|
+
class Collection {
|
|
36
|
+
|
|
37
|
+
// Static attributes.
|
|
38
|
+
static chunk_size: number = 1024 * 1024 * 4; // 4MB chunks, lower is better for frequent updates.
|
|
39
|
+
static constructor_scheme = {
|
|
40
|
+
name: "string",
|
|
41
|
+
uid_based: "boolean",
|
|
42
|
+
ttl: {type: "number", default: null},
|
|
43
|
+
indexes: {
|
|
44
|
+
type: "array",
|
|
45
|
+
default: [],
|
|
46
|
+
value_scheme: {
|
|
47
|
+
type: ["string", "object"],
|
|
48
|
+
scheme: {
|
|
49
|
+
key: {type: "string", required: (data) => data.key == null && data.keys == null },
|
|
50
|
+
keys: {
|
|
51
|
+
type: ["string", "array"],
|
|
52
|
+
required: (data) => data.key == null && data.keys == null, value_scheme: "string",
|
|
53
|
+
postprocess: (keys) => typeof keys === "string" ? [keys] : keys,
|
|
54
|
+
},
|
|
55
|
+
options: {type: "object", required: false},
|
|
56
|
+
commit_quorom: {type: "object", required: false},
|
|
57
|
+
forced: {type: "boolean", default: false},
|
|
58
|
+
},
|
|
59
|
+
postprocess: (info) => {
|
|
60
|
+
if (typeof info === "string") return {keys: [info]}
|
|
61
|
+
if (typeof info === "object" && info.key) {
|
|
62
|
+
info.keys = info.key;
|
|
63
|
+
delete info.key;
|
|
64
|
+
return info;
|
|
65
|
+
}
|
|
66
|
+
return info;
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
// Instance attributes
|
|
74
|
+
col: MongoCollection;
|
|
75
|
+
name: string;
|
|
76
|
+
uid_based: boolean;
|
|
77
|
+
ttl: number | null;
|
|
78
|
+
ttl_enabled: boolean;
|
|
79
|
+
|
|
80
|
+
constructor(
|
|
81
|
+
name: string,
|
|
82
|
+
collection: MongoCollection,
|
|
83
|
+
ttl: number | null = null,
|
|
84
|
+
indexes: Array<{
|
|
85
|
+
keys: string[] | string | Record<string, any>,
|
|
86
|
+
options?: Record<string, any>,
|
|
87
|
+
commit_quorum?: any,
|
|
88
|
+
forced?: boolean
|
|
89
|
+
}> = [],
|
|
90
|
+
uid_based: boolean = false,
|
|
91
|
+
) {
|
|
92
|
+
|
|
93
|
+
// Verify scheme.
|
|
94
|
+
({indexes, ttl} = vlib.Scheme.verify({
|
|
95
|
+
object: {
|
|
96
|
+
name,
|
|
97
|
+
indexes,
|
|
98
|
+
ttl,
|
|
99
|
+
uid_based,
|
|
100
|
+
},
|
|
101
|
+
check_unknown: true,
|
|
102
|
+
scheme: Collection.constructor_scheme,
|
|
103
|
+
}));
|
|
104
|
+
|
|
105
|
+
// Attributes.
|
|
106
|
+
this.name = name;
|
|
107
|
+
this.col = collection;
|
|
108
|
+
this.uid_based = uid_based;
|
|
109
|
+
this.ttl = ttl;
|
|
110
|
+
this.ttl_enabled = typeof ttl === "number";
|
|
111
|
+
|
|
112
|
+
// Create default indexes.
|
|
113
|
+
if (uid_based) {
|
|
114
|
+
this.col.createIndex({ _path: 1, _uid: 1 });
|
|
115
|
+
} else {
|
|
116
|
+
this.col.createIndex({_path: 1});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Create ttl index.
|
|
120
|
+
if (this.ttl_enabled) {
|
|
121
|
+
this.col.dropIndex("_ttl_timestamp_1")
|
|
122
|
+
.catch(err => {
|
|
123
|
+
if (err.codeName !== 'IndexNotFound') {
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
.then(() => {
|
|
128
|
+
this.col.createIndex(
|
|
129
|
+
{_ttl_timestamp: 1},
|
|
130
|
+
{expireAfterSeconds: parseInt(String(this.ttl! / 1000))}
|
|
131
|
+
);
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Create indexes.
|
|
136
|
+
if (Array.isArray(indexes) && indexes.length > 0) {
|
|
137
|
+
for (const index of indexes) {
|
|
138
|
+
this.create_index({
|
|
139
|
+
keys: index.keys,
|
|
140
|
+
options: index.options,
|
|
141
|
+
commit_quorum: index.commit_quorum,
|
|
142
|
+
forced: index.forced,
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Handle file response.
|
|
149
|
+
private _process_doc<T>(doc: T | null): T | null {
|
|
150
|
+
if (doc == null) { return null; }
|
|
151
|
+
else if ((doc as any)._content != null) {
|
|
152
|
+
return (doc as any)._content;
|
|
153
|
+
}
|
|
154
|
+
return doc;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Chunked methods.
|
|
158
|
+
private async _load_chunked(
|
|
159
|
+
path: string | Record<string, any>,
|
|
160
|
+
find_opts?: Record<string, any>
|
|
161
|
+
): Promise<any> {
|
|
162
|
+
let query = typeof path === "string" ?
|
|
163
|
+
{_path: path, chunk: {$gte: 0}} :
|
|
164
|
+
{...path, chunk: {$gte: 0}};
|
|
165
|
+
const chunks_cursor = this.col.find(query, find_opts).sort({chunk: 1});
|
|
166
|
+
const chunks = await chunks_cursor.toArray();
|
|
167
|
+
if (chunks.length === 0) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
const buffer = Buffer.concat(chunks.map(chunk => chunk.data.buffer));
|
|
171
|
+
return deserialize(buffer);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private async _save_chunked(
|
|
175
|
+
path: string | Record<string, any>,
|
|
176
|
+
content: any
|
|
177
|
+
): Promise<void> {
|
|
178
|
+
|
|
179
|
+
// Serialize.
|
|
180
|
+
const buffer = serialize(content);
|
|
181
|
+
const new_chunk_count = Math.ceil(buffer.length / Collection.chunk_size);
|
|
182
|
+
|
|
183
|
+
// Retrieve the old chunk count
|
|
184
|
+
const ref_query: any = typeof path === "string" ?
|
|
185
|
+
{_path: path, chunk: -1} :
|
|
186
|
+
{...path, chunk: -1};
|
|
187
|
+
const object_ref = await this.col.findOne(ref_query);
|
|
188
|
+
const old_chunk_count = object_ref ? object_ref.chunks : 0;
|
|
189
|
+
|
|
190
|
+
// Update chunks.
|
|
191
|
+
const bulk_ops: any[] = [];
|
|
192
|
+
for (let i = 0; i < buffer.length; i += Collection.chunk_size) {
|
|
193
|
+
let query: Record<string, any>, update: Record<string, any>;
|
|
194
|
+
if (typeof path === "string") {
|
|
195
|
+
query = {
|
|
196
|
+
_path: path,
|
|
197
|
+
chunk: i / Collection.chunk_size,
|
|
198
|
+
}
|
|
199
|
+
update = {
|
|
200
|
+
chunk: i / Collection.chunk_size,
|
|
201
|
+
data: buffer.slice(i, i + Collection.chunk_size)
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
query = {
|
|
205
|
+
...path,
|
|
206
|
+
chunk: i / Collection.chunk_size,
|
|
207
|
+
}
|
|
208
|
+
update = {
|
|
209
|
+
chunk: i / Collection.chunk_size,
|
|
210
|
+
data: buffer.slice(i, i + Collection.chunk_size)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const full_update: Record<string, any> = {
|
|
214
|
+
$set: update,
|
|
215
|
+
};
|
|
216
|
+
if (this.ttl_enabled) {
|
|
217
|
+
full_update["$setOnInsert"] = { _ttl_timestamp: new Date() };
|
|
218
|
+
}
|
|
219
|
+
bulk_ops.push({
|
|
220
|
+
updateOne: {
|
|
221
|
+
filter: query,
|
|
222
|
+
update: full_update,
|
|
223
|
+
upsert: true
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Update reference.
|
|
229
|
+
const full_update: Record<string, any> = {
|
|
230
|
+
$set: {
|
|
231
|
+
chunk: -1,
|
|
232
|
+
chunks: new_chunk_count,
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
if (this.ttl_enabled) {
|
|
236
|
+
full_update["$setOnInsert"] = { _ttl_timestamp: new Date() };
|
|
237
|
+
}
|
|
238
|
+
bulk_ops.push({
|
|
239
|
+
updateOne: {
|
|
240
|
+
filter: ref_query,
|
|
241
|
+
update: full_update,
|
|
242
|
+
upsert: true
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Write.
|
|
247
|
+
await this.col.bulkWrite(bulk_ops, {ordered: true});
|
|
248
|
+
|
|
249
|
+
// Delete any excess chunks if the new chunk count is less than the old chunk count
|
|
250
|
+
if (new_chunk_count < old_chunk_count) {
|
|
251
|
+
ref_query.chunk = {$gte: new_chunk_count};
|
|
252
|
+
await this.col.deleteMany(ref_query);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/* @docs:
|
|
257
|
+
@title: Create index
|
|
258
|
+
@description: Creates indexes on collections.
|
|
259
|
+
@return:
|
|
260
|
+
Returns the document that was found or `null` when no document is found.
|
|
261
|
+
@parameter:
|
|
262
|
+
@name: keys
|
|
263
|
+
@desc: The `keys` argument for the orignal mongodb `createIndex()` function.
|
|
264
|
+
@parameter:
|
|
265
|
+
@name: options
|
|
266
|
+
@desc: The `options` argument for the orignal mongodb `createIndex()` function.
|
|
267
|
+
@parameter:
|
|
268
|
+
@name: commitQuorum
|
|
269
|
+
@desc: The `commitQuorum` argument for the orignal mongodb `createIndex()` function.
|
|
270
|
+
*/
|
|
271
|
+
async create_index({
|
|
272
|
+
keys,
|
|
273
|
+
options = null,
|
|
274
|
+
commit_quorum = null,
|
|
275
|
+
forced = false
|
|
276
|
+
}: {
|
|
277
|
+
keys: string[] | string | Record<string, any>,
|
|
278
|
+
options?: Record<string, any> | null,
|
|
279
|
+
commit_quorum?: any,
|
|
280
|
+
forced?: boolean
|
|
281
|
+
}): Promise<string> {
|
|
282
|
+
let keys_obj: Record<string, number> = {};
|
|
283
|
+
if (typeof keys === "string") {
|
|
284
|
+
keys_obj = {};
|
|
285
|
+
keys_obj[keys] = 1;
|
|
286
|
+
}
|
|
287
|
+
else if (Array.isArray(keys)) {
|
|
288
|
+
keys_obj = {};
|
|
289
|
+
for (const key of keys) {
|
|
290
|
+
keys_obj[key] = 1;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
else if (keys == null || typeof keys !== "object") {
|
|
294
|
+
vlib.Scheme.throw_invalid_type("keys", keys, ["string", "string[]", "object"], true);
|
|
295
|
+
} else {
|
|
296
|
+
keys_obj = keys;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Drop index.
|
|
300
|
+
if (forced) {
|
|
301
|
+
try {
|
|
302
|
+
await this.col.dropIndex(
|
|
303
|
+
options?.name ??
|
|
304
|
+
Object.entries(keys_obj)
|
|
305
|
+
.map(([key, value]) => `${key}_${value}`)
|
|
306
|
+
.join('_')
|
|
307
|
+
)
|
|
308
|
+
} catch (err: any) {
|
|
309
|
+
if (err.codeName !== 'IndexNotFound') {
|
|
310
|
+
throw err;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Create index.
|
|
316
|
+
// @ts-ignore
|
|
317
|
+
return await this.col.createIndex(keys_obj, options || {}, commit_quorum)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/* @docs:
|
|
321
|
+
* @title: Find
|
|
322
|
+
* @description: Find a document by a query.
|
|
323
|
+
* @return:
|
|
324
|
+
* Returns the document that was found or `null` when no document is found.
|
|
325
|
+
* @parameter:
|
|
326
|
+
* @name: query
|
|
327
|
+
* @desc: The query options.
|
|
328
|
+
* @type: object
|
|
329
|
+
*/
|
|
330
|
+
async find(query: Record<string, any>): Promise<any> {
|
|
331
|
+
try {
|
|
332
|
+
return this._process_doc(await this.col.findOne(query));
|
|
333
|
+
} catch (error) {
|
|
334
|
+
console.error(error);
|
|
335
|
+
throw new Error('Encountered an error while finding the document.');
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/* @docs:
|
|
340
|
+
* @title: Exists
|
|
341
|
+
* @description: Check if a document exists.
|
|
342
|
+
* @parameter:
|
|
343
|
+
* @name: path
|
|
344
|
+
* @description: The database path to the document.
|
|
345
|
+
* @type: string
|
|
346
|
+
*/
|
|
347
|
+
async exists(path: string | Record<string, any>): Promise<boolean> {
|
|
348
|
+
if (typeof path !== "string" && (typeof path !== "object" || path == null)) {
|
|
349
|
+
throw Error(`Parameter "path" has an invalid type "${typeof path}", the valid type is "string".`);
|
|
350
|
+
}
|
|
351
|
+
try {
|
|
352
|
+
const doc = await this.col.findOne(
|
|
353
|
+
typeof path === "object" ? path : {_path: path},
|
|
354
|
+
{projection: { _id: 1 }}
|
|
355
|
+
);
|
|
356
|
+
return doc != null;
|
|
357
|
+
} catch (error) {
|
|
358
|
+
console.error(error);
|
|
359
|
+
throw new Error('Encountered an error while checking if the document exists.');
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/* @docs:
|
|
364
|
+
* @title: Load
|
|
365
|
+
* @description: Load data by path.
|
|
366
|
+
* @return:
|
|
367
|
+
* Returns the loaded document.
|
|
368
|
+
*
|
|
369
|
+
* Returns the `def` parameter when the data does not exist, keep in mind that when parameter `def` is an object it could be a reference to a defined variable.
|
|
370
|
+
* @parameter:
|
|
371
|
+
* @name: path
|
|
372
|
+
* @description: The database path to the document.
|
|
373
|
+
* @type: string
|
|
374
|
+
* @parameter:
|
|
375
|
+
* @name: opts
|
|
376
|
+
* @desc: Additional options.
|
|
377
|
+
* @type: null, object
|
|
378
|
+
* @attribute:
|
|
379
|
+
* @name: default
|
|
380
|
+
* @description:
|
|
381
|
+
* The default data to be returned when the data does not exist.
|
|
382
|
+
*
|
|
383
|
+
* When the type of attribute `default` is `object` then the keys that do not exist in the loaded object, but do exist in the default object will be inserted into the loaded object.
|
|
384
|
+
* @type: null, object
|
|
385
|
+
* @attribute:
|
|
386
|
+
* @name: chunked
|
|
387
|
+
* @description: Load a chunked document.
|
|
388
|
+
* @type: null, object
|
|
389
|
+
* @attribute:
|
|
390
|
+
* @name: attributes
|
|
391
|
+
* @description: The attributes to load.
|
|
392
|
+
* @type: null, string[]
|
|
393
|
+
*/
|
|
394
|
+
async load(
|
|
395
|
+
path: string | Record<string, any>,
|
|
396
|
+
opts: {
|
|
397
|
+
default?: any,
|
|
398
|
+
chunked?: boolean,
|
|
399
|
+
attributes?: string[],
|
|
400
|
+
projection?: Record<string, any>
|
|
401
|
+
} | null = null
|
|
402
|
+
): Promise<any> {
|
|
403
|
+
if (typeof path !== "string" && (typeof path !== "object" || path == null)) {
|
|
404
|
+
throw Error(`Parameter "path" has an invalid type "${typeof path}", the valid type is "string".`);
|
|
405
|
+
}
|
|
406
|
+
try {
|
|
407
|
+
|
|
408
|
+
// Get attributes.
|
|
409
|
+
let find_opts: {projection?: Record<string, any>} | undefined;
|
|
410
|
+
if (opts) {
|
|
411
|
+
if (opts.projection) {
|
|
412
|
+
find_opts = {projection: opts.projection};
|
|
413
|
+
}
|
|
414
|
+
else if (opts.attributes) {
|
|
415
|
+
find_opts = {projection: {
|
|
416
|
+
_id: 1,
|
|
417
|
+
_path: 1,
|
|
418
|
+
_uid: 1,
|
|
419
|
+
}};
|
|
420
|
+
opts.attributes.forEach((i) => {
|
|
421
|
+
if (find_opts?.projection) {
|
|
422
|
+
find_opts.projection[i] = 1;
|
|
423
|
+
}
|
|
424
|
+
})
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Load doc.
|
|
429
|
+
let doc;
|
|
430
|
+
if (opts != null && opts.chunked === true) {
|
|
431
|
+
doc = await this._load_chunked(path, find_opts);
|
|
432
|
+
} else {
|
|
433
|
+
|
|
434
|
+
// Load.
|
|
435
|
+
doc = await this.col.findOne(
|
|
436
|
+
typeof path === "object" ? path : {_path: path},
|
|
437
|
+
find_opts,
|
|
438
|
+
);
|
|
439
|
+
this.clean(doc);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Process doc.
|
|
443
|
+
doc = this._process_doc(doc);
|
|
444
|
+
|
|
445
|
+
// Handle default.
|
|
446
|
+
if (doc == null) {
|
|
447
|
+
if (opts != null && opts.default !== undefined) { return opts.default; }
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Insert default keys.
|
|
452
|
+
else if (opts != null && typeof opts.default === "object" && opts.default != null && Array.isArray(opts.default) === false) {
|
|
453
|
+
const set_defaults = (obj: Record<string, any>, defaults: Record<string, any>) => {
|
|
454
|
+
Object.keys(defaults).forEach((key) => {
|
|
455
|
+
if (obj[key] === undefined) {
|
|
456
|
+
obj[key] = defaults[key];
|
|
457
|
+
} else if (
|
|
458
|
+
typeof obj[key] === "object" && !Array.isArray(obj[key]) && obj[key] != null &&
|
|
459
|
+
typeof defaults[key] === "object" && !Array.isArray(defaults[key]) && defaults[key] != null
|
|
460
|
+
) {
|
|
461
|
+
set_defaults(obj[key], defaults[key])
|
|
462
|
+
}
|
|
463
|
+
})
|
|
464
|
+
}
|
|
465
|
+
set_defaults(doc, opts.default);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Response.
|
|
469
|
+
return doc;
|
|
470
|
+
|
|
471
|
+
} catch (error) {
|
|
472
|
+
console.error(error);
|
|
473
|
+
throw new Error('Encountered an error while loading the document.');
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/* @docs:
|
|
478
|
+
* @title: Save
|
|
479
|
+
* @description: Save data by path. When the document already exists this function only updates the specified content attributes.
|
|
480
|
+
* @return:
|
|
481
|
+
* Returns the updated document.
|
|
482
|
+
* @parameter:
|
|
483
|
+
* @name: path
|
|
484
|
+
* @description: The database path to the document.
|
|
485
|
+
* @type: string
|
|
486
|
+
* @parameter:
|
|
487
|
+
* @name: data
|
|
488
|
+
* @description: The data to save.
|
|
489
|
+
* @type: null, boolean, number, string, array, object
|
|
490
|
+
* @parameter:
|
|
491
|
+
* @name: opts
|
|
492
|
+
* @desc: Additional options.
|
|
493
|
+
* @type: null, object
|
|
494
|
+
* @attribute:
|
|
495
|
+
* @name: chunked
|
|
496
|
+
* @description: Chunk the document into multiple documents, therefore documents larger than 16MB are supported.
|
|
497
|
+
* @warning: Currently this option is only supported for types `object` and `array`.
|
|
498
|
+
* @default: false
|
|
499
|
+
* @type: boolean
|
|
500
|
+
* @attribute:
|
|
501
|
+
* @name: bulk
|
|
502
|
+
* @description: Get a bulk operation object, so several operations can be executed in bulk.
|
|
503
|
+
* @default: false
|
|
504
|
+
* @type: boolean
|
|
505
|
+
* @attribute:
|
|
506
|
+
* @name: set
|
|
507
|
+
* @description: By default the $set attribute is used for the content, with `opts.set` disabled you can create your own instructions. The `content` attribute must reflect this.
|
|
508
|
+
* @warning: This does not work in combination with `opts.chunked`.
|
|
509
|
+
* @default: true
|
|
510
|
+
* @type: boolean
|
|
511
|
+
*/
|
|
512
|
+
async save(
|
|
513
|
+
path: string | Record<string, any>,
|
|
514
|
+
content: any,
|
|
515
|
+
opts: {
|
|
516
|
+
chunked?: boolean,
|
|
517
|
+
bulk?: boolean,
|
|
518
|
+
set?: boolean
|
|
519
|
+
} | null = null
|
|
520
|
+
): Promise<any> {
|
|
521
|
+
if (typeof path !== "string" && (typeof path !== "object" || path == null)) {
|
|
522
|
+
throw Error(`Parameter "path" has an invalid type "${typeof path}", the valid type is "string".`);
|
|
523
|
+
}
|
|
524
|
+
try {
|
|
525
|
+
// Vars.
|
|
526
|
+
let doc, set;
|
|
527
|
+
|
|
528
|
+
// Create set.
|
|
529
|
+
if (typeof content === "object" && Array.isArray(content) == false && content != null) {
|
|
530
|
+
delete content._id;
|
|
531
|
+
delete content._path;
|
|
532
|
+
delete content._uid;
|
|
533
|
+
delete content._ttl_timestamp;
|
|
534
|
+
set = content;
|
|
535
|
+
} else {
|
|
536
|
+
set = {_content: content};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Save chunked.
|
|
540
|
+
if (opts != null && opts.chunked === true) {
|
|
541
|
+
await this._save_chunked(path, set);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Save as single doc.
|
|
545
|
+
else {
|
|
546
|
+
|
|
547
|
+
// Apply $set rules.
|
|
548
|
+
if (opts == null || (opts.set !== false)) {
|
|
549
|
+
set = {$set: set};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Apply TTL.
|
|
553
|
+
if (this.ttl_enabled) {
|
|
554
|
+
if (set["$setOnInsert"] === undefined) {
|
|
555
|
+
set["$setOnInsert"] = {}
|
|
556
|
+
set["$setOnInsert"]._ttl_timestamp = new Date();
|
|
557
|
+
}
|
|
558
|
+
else if (set["$setOnInsert"] != null && typeof set["$setOnInsert"] === "object") {
|
|
559
|
+
set["$setOnInsert"]._ttl_timestamp = new Date();
|
|
560
|
+
} else {
|
|
561
|
+
throw new Error(`Undefined behaviour: Unable to assign the $setOnInsert data for ttl control.`);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Bulk operation.
|
|
566
|
+
if (opts != null && opts.bulk) {
|
|
567
|
+
return {
|
|
568
|
+
updateOne: {
|
|
569
|
+
filter: typeof path === "object" ? path : {_path: path},
|
|
570
|
+
update: set,
|
|
571
|
+
upsert: true,
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Normal operation.
|
|
577
|
+
else {
|
|
578
|
+
await this.col.updateOne(
|
|
579
|
+
typeof path === "object" ? path : {_path: path},
|
|
580
|
+
set,
|
|
581
|
+
{upsert: true},
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Response.
|
|
587
|
+
return content;
|
|
588
|
+
|
|
589
|
+
} catch (error) {
|
|
590
|
+
console.error(error);
|
|
591
|
+
throw new Error('Encountered an error while updating the document.');
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// List.
|
|
596
|
+
/* @docs:
|
|
597
|
+
* @title: List
|
|
598
|
+
* @description: List all child documents of directory path.
|
|
599
|
+
* @parameter:
|
|
600
|
+
* @name: path
|
|
601
|
+
* @description: The database directory path.
|
|
602
|
+
* @type: string
|
|
603
|
+
* @parameter:
|
|
604
|
+
* @name: options
|
|
605
|
+
* @description: List options.
|
|
606
|
+
* @type: object
|
|
607
|
+
* @attribute:
|
|
608
|
+
* @name: process
|
|
609
|
+
* @description: Process the document. By default saved non object data will be stored under `_content`. Processing checks this attribute and uses that content instead when it is detected.
|
|
610
|
+
* @type: boolean
|
|
611
|
+
* @default: true
|
|
612
|
+
* @attribute:
|
|
613
|
+
* @name: projection
|
|
614
|
+
* @description: The data attributes to retrieve, when left undefined all attributes are retrieved.
|
|
615
|
+
* @type: object
|
|
616
|
+
* @default: undefined
|
|
617
|
+
*/
|
|
618
|
+
async list(
|
|
619
|
+
path: string | Record<string, any>,
|
|
620
|
+
options: {
|
|
621
|
+
process?: boolean,
|
|
622
|
+
projection?: Record<string, any>
|
|
623
|
+
} = {}
|
|
624
|
+
): Promise<any[]> {
|
|
625
|
+
if (typeof path !== "string" && (typeof path !== "object" || path == null)) {
|
|
626
|
+
throw Error(`Parameter "path" has an invalid type "${typeof path}", the valid type is "string".`);
|
|
627
|
+
}
|
|
628
|
+
if (typeof path === "string") {
|
|
629
|
+
while (path.length > 0 && path.charAt(path.length - 1) === "/") {
|
|
630
|
+
path = path.substr(0, path.length - 1);
|
|
631
|
+
}
|
|
632
|
+
if (path.length == 0) {
|
|
633
|
+
throw Error("Invalid path.");
|
|
634
|
+
}
|
|
635
|
+
path = {_path: {$regex: `^${path}/`}};
|
|
636
|
+
} else if ((path as Record<string, any>)._path) {
|
|
637
|
+
let _path = (path as Record<string, any>)._path;
|
|
638
|
+
while (_path.length > 0 && _path.charAt(_path.length - 1) === "/") {
|
|
639
|
+
_path = _path.substr(0, _path.length - 1);
|
|
640
|
+
}
|
|
641
|
+
if (_path.length == 0) {
|
|
642
|
+
throw Error("Invalid path.");
|
|
643
|
+
}
|
|
644
|
+
(path as Record<string, any>)._path = {$regex: `^${_path}/`};
|
|
645
|
+
}
|
|
646
|
+
try {
|
|
647
|
+
const docs = await this.col.find(path, {projection: options.projection}).toArray();
|
|
648
|
+
if (options.process === false) {
|
|
649
|
+
return docs;
|
|
650
|
+
}
|
|
651
|
+
return docs.map((doc) => this._process_doc(doc));
|
|
652
|
+
} catch (error) {
|
|
653
|
+
console.error(error);
|
|
654
|
+
throw new Error('Encountered an error while listing all documents.');
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/* @docs:
|
|
659
|
+
* @title: List Query
|
|
660
|
+
* @description: List all documents of the collection based on a query.
|
|
661
|
+
* @parameter:
|
|
662
|
+
* @name: query
|
|
663
|
+
* @desc: The query options.
|
|
664
|
+
* @type: object
|
|
665
|
+
* @parameter:
|
|
666
|
+
* @name: options
|
|
667
|
+
* @description: List options.
|
|
668
|
+
* @type: object
|
|
669
|
+
* @attribute:
|
|
670
|
+
* @name: process
|
|
671
|
+
* @description: Process the document. By default saved non object data will be stored under `_content`. Processing checks this attribute and uses that content instead when it is detected.
|
|
672
|
+
* @type: boolean
|
|
673
|
+
* @default: true
|
|
674
|
+
* @attribute:
|
|
675
|
+
* @name: projection
|
|
676
|
+
* @description: The data attributes to retrieve, when left undefined all attributes are retrieved.
|
|
677
|
+
* @type: object
|
|
678
|
+
* @default: undefined
|
|
679
|
+
*/
|
|
680
|
+
async list_query(
|
|
681
|
+
query: Record<string, any> = {},
|
|
682
|
+
options: {
|
|
683
|
+
process?: boolean,
|
|
684
|
+
projection?: Record<string, any>
|
|
685
|
+
} = {}
|
|
686
|
+
): Promise<any[]> {
|
|
687
|
+
try {
|
|
688
|
+
const docs = await this.col.find(query, {projection: options.projection}).toArray();
|
|
689
|
+
if (options.process === false) { return docs; }
|
|
690
|
+
return docs.map((doc) => this._process_doc(doc)) // list as array since the user might have used a path object width different attributes so dict is not reliable.
|
|
691
|
+
} catch (error) {
|
|
692
|
+
console.error(error);
|
|
693
|
+
throw new Error('Encountered an error while listing all documents.');
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/* @docs:
|
|
698
|
+
* @title: List All
|
|
699
|
+
* @description: List all documents of the collection, optionally per uid.
|
|
700
|
+
* @parameter:
|
|
701
|
+
* @name: query
|
|
702
|
+
* @ignore: true
|
|
703
|
+
* @parameter:
|
|
704
|
+
* @name: options
|
|
705
|
+
* @description: List options.
|
|
706
|
+
* @type: object
|
|
707
|
+
* @attribute:
|
|
708
|
+
* @name: process
|
|
709
|
+
* @description: Process the document. By default saved non object data will be stored under `_content`. Processing checks this attribute and uses that content instead when it is detected.
|
|
710
|
+
* @type: boolean
|
|
711
|
+
* @default: true
|
|
712
|
+
* @attribute:
|
|
713
|
+
* @name: projection
|
|
714
|
+
* @description: The data attributes to retrieve, when left undefined all attributes are retrieved.
|
|
715
|
+
* @type: object
|
|
716
|
+
* @default: undefined
|
|
717
|
+
*/
|
|
718
|
+
async list_all(
|
|
719
|
+
query: Record<string, any> = {},
|
|
720
|
+
options: {
|
|
721
|
+
process?: boolean,
|
|
722
|
+
projection?: Record<string, any>
|
|
723
|
+
} = {}
|
|
724
|
+
): Promise<any[]> {
|
|
725
|
+
let docs;
|
|
726
|
+
if (this.uid_based) {
|
|
727
|
+
docs = await this.col.find(query, {projection: options.projection}).toArray();
|
|
728
|
+
} else {
|
|
729
|
+
docs = await this.col.find(query, {projection: options.projection}).toArray();
|
|
730
|
+
}
|
|
731
|
+
if (options.process === false) {
|
|
732
|
+
return docs;
|
|
733
|
+
}
|
|
734
|
+
return docs.map((doc) => this._process_doc(doc)) // list as array since the user might have used a path object width different attributes so dict is not reliable.
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/* @docs:
|
|
738
|
+
* @title: Delete
|
|
739
|
+
* @description: Delete a document of the collection by path.
|
|
740
|
+
* @parameter:
|
|
741
|
+
* @name: path
|
|
742
|
+
* @description: The database path to the document.
|
|
743
|
+
* @type: string
|
|
744
|
+
* @parameter:
|
|
745
|
+
* @name: opts
|
|
746
|
+
* @desc: Additional options.
|
|
747
|
+
* @type: null, object
|
|
748
|
+
* @attribute:
|
|
749
|
+
* @name: chunked
|
|
750
|
+
* @description: Delete a chunked document.
|
|
751
|
+
* @default: false
|
|
752
|
+
* @type: boolean
|
|
753
|
+
* @attribute:
|
|
754
|
+
* @name: bulk
|
|
755
|
+
* @description: Get a bulk operation object, so several operations can be executed in bulk.
|
|
756
|
+
* @default: false
|
|
757
|
+
* @type: boolean
|
|
758
|
+
*/
|
|
759
|
+
async delete(
|
|
760
|
+
path: string | Record<string, any>,
|
|
761
|
+
opts: {
|
|
762
|
+
chunked?: boolean,
|
|
763
|
+
bulk?: boolean
|
|
764
|
+
} | null = null
|
|
765
|
+
): Promise<any> {
|
|
766
|
+
if (typeof path !== "string" && (typeof path !== "object" || path == null)) {
|
|
767
|
+
throw Error(`Parameter "path" has an invalid type "${typeof path}", the valid type is "string".`);
|
|
768
|
+
}
|
|
769
|
+
try {
|
|
770
|
+
if (opts != null && opts.chunked === true) {
|
|
771
|
+
if (opts.bulk) {
|
|
772
|
+
return {deleteMany: {filter: typeof path === "object" ? path : {_path: path}}};
|
|
773
|
+
} else {
|
|
774
|
+
await this.col.deleteMany(typeof path === "object" ? path : {_path: path});
|
|
775
|
+
}
|
|
776
|
+
} else {
|
|
777
|
+
if (opts != null && opts.bulk) {
|
|
778
|
+
return {deleteOne: {filter: typeof path === "object" ? path : {_path: path}}};
|
|
779
|
+
} else {
|
|
780
|
+
await this.col.deleteOne(typeof path === "object" ? path : {_path: path});
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
} catch (error) {
|
|
784
|
+
console.error(error);
|
|
785
|
+
throw new Error('Encountered an error while deleting.');
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/* @docs:
|
|
790
|
+
* @title: Delete Query
|
|
791
|
+
* @description: Delete a document of the collection by query.
|
|
792
|
+
* @parameter:
|
|
793
|
+
* @name: query
|
|
794
|
+
* @description: The query object.
|
|
795
|
+
* @type: object
|
|
796
|
+
*/
|
|
797
|
+
async delete_query(query: Record<string, any> = {}): Promise<any> {
|
|
798
|
+
if (typeof query !== "object" || query == null || Object.keys(query).length === 0) {
|
|
799
|
+
throw Error(`Parameter "query" has an invalid type "${typeof query}", the valid type is "object".`);
|
|
800
|
+
}
|
|
801
|
+
if (Object.keys(query).length === 0) {
|
|
802
|
+
throw Error(`Parameter "query" is an empty object.`);
|
|
803
|
+
}
|
|
804
|
+
return await this.col.deleteMany(query)
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Delete all.
|
|
808
|
+
async delete_all(path: string | Record<string, any>): Promise<void> {
|
|
809
|
+
if (typeof path !== "string" && (typeof path !== "object" || path == null)) {
|
|
810
|
+
throw Error(`Parameter "path" has an invalid type "${typeof path}", the valid type is "string".`);
|
|
811
|
+
}
|
|
812
|
+
try {
|
|
813
|
+
await this.col.deleteMany(typeof path === "object" ? path : {_path: path});
|
|
814
|
+
} catch (error) {
|
|
815
|
+
console.error(error);
|
|
816
|
+
throw new Error('Encountered an error while deleting.');
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/* @docs:
|
|
821
|
+
* @title: Delete Collection
|
|
822
|
+
* @description: Delete all documents of from the collection.
|
|
823
|
+
*/
|
|
824
|
+
async delete_collection(): Promise<void> {
|
|
825
|
+
await this.col.deleteMany()
|
|
826
|
+
await this.col.drop();
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/* @docs:
|
|
830
|
+
* @title: Clean document
|
|
831
|
+
* @description: Clean a document from all default system attributes.
|
|
832
|
+
*/
|
|
833
|
+
clean<T>(doc: T): T | null {
|
|
834
|
+
if (doc == null) { return doc; }
|
|
835
|
+
if (typeof doc === "object") {
|
|
836
|
+
delete (doc as any)._id;
|
|
837
|
+
delete (doc as any)._path;
|
|
838
|
+
if (this.uid_based) {
|
|
839
|
+
delete (doc as any)._uid;
|
|
840
|
+
}
|
|
841
|
+
if (this.ttl_enabled) {
|
|
842
|
+
delete (doc as any)._ttl_timestamp;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return doc;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/** Write bulk operations. */
|
|
849
|
+
async bulk_operations(operations: any[] = []): Promise<any> {
|
|
850
|
+
return await this.col.bulkWrite(operations, {ordered: true});
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// ---------------------------------------------------------
|
|
856
|
+
// UID based collection.
|
|
857
|
+
|
|
858
|
+
// @warning: The "path" param must always be allowed to be an object or string, also for the UIDCollection class.
|
|
859
|
+
// @warning: THE DATABASE COLLECTION SHOULD ALSO ACCEPT OBJECTS FOR PATHS.
|
|
860
|
+
/* @docs:
|
|
861
|
+
@nav: Backend
|
|
862
|
+
@chapter: Database
|
|
863
|
+
@title: UID Collection
|
|
864
|
+
@desc: The UID based database collection class.
|
|
865
|
+
@note: The document attribute `_uid` is a reserved index attribute for the user id of the document.
|
|
866
|
+
@note: The document attribute `_path` is a reserved index attribute for the path of the document.
|
|
867
|
+
@attribute:
|
|
868
|
+
@name: col
|
|
869
|
+
@desc: The native mongodb collection.
|
|
870
|
+
*/
|
|
871
|
+
class UIDCollection {
|
|
872
|
+
private _col: Collection;
|
|
873
|
+
col: MongoCollection;
|
|
874
|
+
|
|
875
|
+
constructor(
|
|
876
|
+
name: string,
|
|
877
|
+
collection: MongoCollection,
|
|
878
|
+
indexes: Array<{
|
|
879
|
+
keys: string[] | string | Record<string, any>,
|
|
880
|
+
options?: Record<string, any>,
|
|
881
|
+
commit_quorum?: any
|
|
882
|
+
}> = [],
|
|
883
|
+
ttl: number | null = null,
|
|
884
|
+
) {
|
|
885
|
+
this._col = new Collection(name, collection, ttl, indexes, true);
|
|
886
|
+
this.col = this._col.col;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/* @docs:
|
|
890
|
+
@title: Create index
|
|
891
|
+
@description: Creates indexes on collections.
|
|
892
|
+
@return:
|
|
893
|
+
Returns the document that was found or `null` when no document is found.
|
|
894
|
+
@parameter:
|
|
895
|
+
@name: keys
|
|
896
|
+
@desc: The `keys` argument for the orignal mongodb `createIndex()` function.
|
|
897
|
+
@parameter:
|
|
898
|
+
@name: options
|
|
899
|
+
@desc: The `options` argument for the orignal mongodb `createIndex()` function.
|
|
900
|
+
@parameter:
|
|
901
|
+
@name: commitQuorum
|
|
902
|
+
@desc: The `commitQuorum` argument for the orignal mongodb `createIndex()` function.
|
|
903
|
+
*/
|
|
904
|
+
async create_index({
|
|
905
|
+
keys,
|
|
906
|
+
options = null,
|
|
907
|
+
commit_quorum = null
|
|
908
|
+
}: {
|
|
909
|
+
keys: string[] | string | Record<string, any>,
|
|
910
|
+
options?: Record<string, any> | null,
|
|
911
|
+
commit_quorum?: any
|
|
912
|
+
}): Promise<string> {
|
|
913
|
+
return this._col.create_index({keys, options, commit_quorum});
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/* @docs:
|
|
917
|
+
* @title: Find
|
|
918
|
+
* @description: Find a document by a query.
|
|
919
|
+
* @return:
|
|
920
|
+
* Returns the document that was found or `null` when no document is found.
|
|
921
|
+
* @parameter:
|
|
922
|
+
* @name: uid
|
|
923
|
+
* @cached: Users:uid:param
|
|
924
|
+
* @required: false
|
|
925
|
+
* @parameter:
|
|
926
|
+
* @name: query
|
|
927
|
+
* @desc: The query options.
|
|
928
|
+
* @type: object
|
|
929
|
+
*/
|
|
930
|
+
async find(uid: string | null = null, query: Record<string, any> = {}): Promise<any> {
|
|
931
|
+
if (uid != null) {
|
|
932
|
+
query._uid = uid;
|
|
933
|
+
}
|
|
934
|
+
return await this._col.find(query);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/* @docs:
|
|
938
|
+
* @title: Exists
|
|
939
|
+
* @description: Check if a document exists.
|
|
940
|
+
* @parameter:
|
|
941
|
+
* @name: uid
|
|
942
|
+
* @cached: Users:uid:param
|
|
943
|
+
* @parameter:
|
|
944
|
+
* @name: path
|
|
945
|
+
* @description: The database path to the document.
|
|
946
|
+
* @type: string, object
|
|
947
|
+
*/
|
|
948
|
+
async exists(uid: string, path: string | Record<string, any>): Promise<boolean> {
|
|
949
|
+
if (typeof path !== "string" && (typeof path !== "object" || path == null)) {
|
|
950
|
+
throw Error(`Parameter "path" has an invalid type "${typeof path}", the valid type is "string".`);
|
|
951
|
+
}
|
|
952
|
+
if (typeof uid !== "string") {
|
|
953
|
+
throw Error(`Parameter "uid" has an invalid type "${typeof uid}", the valid type is "string".`);
|
|
954
|
+
}
|
|
955
|
+
if (typeof path === "object") {
|
|
956
|
+
return await this._col.exists({...path, _uid: uid});
|
|
957
|
+
} else {
|
|
958
|
+
return await this._col.exists({_path: path, _uid: uid});
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/* @docs:
|
|
963
|
+
* @title: Load
|
|
964
|
+
* @description: Load data by user id and path.
|
|
965
|
+
* @return:
|
|
966
|
+
* Returns the loaded document.
|
|
967
|
+
*
|
|
968
|
+
* Returns the `def` parameter when the data does not exist, keep in mind that when parameter `def` is an object it could be a reference to a defined variable.
|
|
969
|
+
* @parameter:
|
|
970
|
+
* @name: uid
|
|
971
|
+
* @cached: Users:uid:param
|
|
972
|
+
* @parameter:
|
|
973
|
+
* @name: path
|
|
974
|
+
* @description: The database path to the document.
|
|
975
|
+
* @type: string, object
|
|
976
|
+
* @parameter:
|
|
977
|
+
* @name: opts
|
|
978
|
+
* @desc: Additional options.
|
|
979
|
+
* @type: null, object
|
|
980
|
+
* @attribute:
|
|
981
|
+
* @name: default
|
|
982
|
+
* @description:
|
|
983
|
+
* The default data to be returned when the data does not exist.
|
|
984
|
+
*
|
|
985
|
+
* When the type of attribute `default` is `object` then the keys that do not exist in the loaded object, but do exist in the default object will be inserted into the loaded object.
|
|
986
|
+
* @type: null, object
|
|
987
|
+
*/
|
|
988
|
+
async load(
|
|
989
|
+
uid: string,
|
|
990
|
+
path: string | Record<string, any>,
|
|
991
|
+
opts: {
|
|
992
|
+
default?: any,
|
|
993
|
+
chunked?: boolean,
|
|
994
|
+
attributes?: string[],
|
|
995
|
+
projection?: Record<string, any>
|
|
996
|
+
} | null = null
|
|
997
|
+
): Promise<any> {
|
|
998
|
+
if (typeof path !== "string" && (typeof path !== "object" || path == null)) {
|
|
999
|
+
throw Error(`Parameter "path" has an invalid type "${typeof path}", the valid type is "string".`);
|
|
1000
|
+
}
|
|
1001
|
+
if (typeof uid !== "string") {
|
|
1002
|
+
throw Error(`Parameter "uid" has an invalid type "${typeof uid}", the valid type is "string".`);
|
|
1003
|
+
}
|
|
1004
|
+
if (typeof path === "object") {
|
|
1005
|
+
return await this._col.load({...path, _uid: uid}, opts);
|
|
1006
|
+
} else {
|
|
1007
|
+
return await this._col.load({_path: path, _uid: uid}, opts);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/* @docs:
|
|
1012
|
+
* @title: Save
|
|
1013
|
+
* @description: Save data by user id and path. When the document already exists this function only updates the specified content attributes.
|
|
1014
|
+
* @return:
|
|
1015
|
+
* Returns the updated document.
|
|
1016
|
+
* @parameter:
|
|
1017
|
+
* @name: uid
|
|
1018
|
+
* @cached: Users:uid:param
|
|
1019
|
+
* @parameter:
|
|
1020
|
+
* @name: path
|
|
1021
|
+
* @description: The database path to the document.
|
|
1022
|
+
* @type: string, object
|
|
1023
|
+
* @parameter:
|
|
1024
|
+
* @name: data
|
|
1025
|
+
* @description: The data to save.
|
|
1026
|
+
* @type: null, boolean, number, string, array, object
|
|
1027
|
+
* @parameter:
|
|
1028
|
+
* @name: opts
|
|
1029
|
+
* @desc: Additional options.
|
|
1030
|
+
* @type: null, object
|
|
1031
|
+
* @attribute:
|
|
1032
|
+
* @name: chunked
|
|
1033
|
+
* @description: Chunk the document into multiple documents, therefore documents larger than 16MB are supported.
|
|
1034
|
+
* @warning: Currently this option is only supported for types `object` and `array`.
|
|
1035
|
+
* @default: false
|
|
1036
|
+
* @type: boolean
|
|
1037
|
+
* @attribute:
|
|
1038
|
+
* @name: bulk
|
|
1039
|
+
* @description: Get a bulk operation object, so several operations can be executed in bulk.
|
|
1040
|
+
* @default: false
|
|
1041
|
+
* @type: boolean
|
|
1042
|
+
* @attribute:
|
|
1043
|
+
* @name: set
|
|
1044
|
+
* @description: By default the $set attribute is used for the content, with `opts.set` disabled you can create your own instructions. The `content` attribute must reflect this.
|
|
1045
|
+
* @warning: This does not work in combination with `opts.chunked`.
|
|
1046
|
+
* @default: true
|
|
1047
|
+
* @type: boolean
|
|
1048
|
+
*/
|
|
1049
|
+
async save(
|
|
1050
|
+
uid: string,
|
|
1051
|
+
path: string | Record<string, any>,
|
|
1052
|
+
content: any,
|
|
1053
|
+
opts: {
|
|
1054
|
+
chunked?: boolean,
|
|
1055
|
+
bulk?: boolean,
|
|
1056
|
+
set?: boolean
|
|
1057
|
+
} | null = null
|
|
1058
|
+
): Promise<any> {
|
|
1059
|
+
if (typeof path !== "string" && (typeof path !== "object" || path == null)) {
|
|
1060
|
+
throw Error(`Parameter "path" has an invalid type "${typeof path}", the valid type is "string".`);
|
|
1061
|
+
}
|
|
1062
|
+
if (typeof uid !== "string") {
|
|
1063
|
+
throw Error(`Parameter "uid" has an invalid type "${typeof uid}", the valid type is "string".`);
|
|
1064
|
+
}
|
|
1065
|
+
if (typeof path === "object") {
|
|
1066
|
+
return await this._col.save({...path, _uid: uid}, content, opts);
|
|
1067
|
+
} else {
|
|
1068
|
+
return await this._col.save({_path: path, _uid: uid}, content, opts);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/* @docs:
|
|
1073
|
+
* @title: List
|
|
1074
|
+
* @description: List all child documents of directory path.
|
|
1075
|
+
* @parameter:
|
|
1076
|
+
* @name: uid
|
|
1077
|
+
* @cached: Users:uid:param
|
|
1078
|
+
* @parameter:
|
|
1079
|
+
* @name: path
|
|
1080
|
+
* @description: The database directory path.
|
|
1081
|
+
* @type: string, object
|
|
1082
|
+
* @parameter:
|
|
1083
|
+
* @name: options
|
|
1084
|
+
* @description: List options.
|
|
1085
|
+
* @type: object
|
|
1086
|
+
* @attribute:
|
|
1087
|
+
* @name: process
|
|
1088
|
+
* @description: Process the document. By default saved non object data will be stored under `_content`. Processing checks this attribute and uses that content instead when it is detected.
|
|
1089
|
+
* @type: boolean
|
|
1090
|
+
* @default: true
|
|
1091
|
+
* @attribute:
|
|
1092
|
+
* @name: projection
|
|
1093
|
+
* @description: The data attributes to retrieve, when left undefined all attributes are retrieved.
|
|
1094
|
+
* @type: object
|
|
1095
|
+
* @default: undefined
|
|
1096
|
+
*/
|
|
1097
|
+
async list(
|
|
1098
|
+
uid: string,
|
|
1099
|
+
path: string | Record<string, any>,
|
|
1100
|
+
options: {
|
|
1101
|
+
process?: boolean,
|
|
1102
|
+
projection?: Record<string, any>
|
|
1103
|
+
} = {}
|
|
1104
|
+
): Promise<any[]> {
|
|
1105
|
+
if (typeof path !== "string" && (typeof path !== "object" || path == null)) {
|
|
1106
|
+
throw Error(`Parameter "path" has an invalid type "${typeof path}", the valid type is "string".`);
|
|
1107
|
+
}
|
|
1108
|
+
if (typeof uid !== "string") {
|
|
1109
|
+
throw Error(`Parameter "uid" has an invalid type "${typeof uid}", the valid type is "string".`);
|
|
1110
|
+
}
|
|
1111
|
+
if (typeof path === "object") {
|
|
1112
|
+
return await this._col.list({...path, _uid: uid}, options)
|
|
1113
|
+
} else {
|
|
1114
|
+
return await this._col.list({_path: path, _uid: uid}, options)
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
/* @docs:
|
|
1119
|
+
* @title: List Query
|
|
1120
|
+
* @description: List all documents of the collection based on a query.
|
|
1121
|
+
* @parameter:
|
|
1122
|
+
* @name: query
|
|
1123
|
+
* @desc: The query options.
|
|
1124
|
+
* @type: object
|
|
1125
|
+
* @parameter:
|
|
1126
|
+
* @name: options
|
|
1127
|
+
* @description: List options.
|
|
1128
|
+
* @type: object
|
|
1129
|
+
* @attribute:
|
|
1130
|
+
* @name: process
|
|
1131
|
+
* @description: Process the document. By default saved non object data will be stored under `_content`. Processing checks this attribute and uses that content instead when it is detected.
|
|
1132
|
+
* @type: boolean
|
|
1133
|
+
* @default: true
|
|
1134
|
+
* @attribute:
|
|
1135
|
+
* @name: projection
|
|
1136
|
+
* @description: The data attributes to retrieve, when left undefined all attributes are retrieved.
|
|
1137
|
+
* @type: object
|
|
1138
|
+
* @default: undefined
|
|
1139
|
+
*/
|
|
1140
|
+
async list_query(
|
|
1141
|
+
query: Record<string, any> = {},
|
|
1142
|
+
options: {
|
|
1143
|
+
process?: boolean,
|
|
1144
|
+
projection?: Record<string, any>
|
|
1145
|
+
} = {}
|
|
1146
|
+
): Promise<any[]> {
|
|
1147
|
+
return await this._col.list_query(query, options);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
/* @docs:
|
|
1151
|
+
* @title: List All
|
|
1152
|
+
* @description: List all documents of the collection, optionally per uid.
|
|
1153
|
+
* @parameter:
|
|
1154
|
+
* @name: uid
|
|
1155
|
+
* @cached: Users:uid:param
|
|
1156
|
+
* @parameter:
|
|
1157
|
+
* @name: options
|
|
1158
|
+
* @description: List options.
|
|
1159
|
+
* @type: object
|
|
1160
|
+
* @attribute:
|
|
1161
|
+
* @name: process
|
|
1162
|
+
* @description: Process the document. By default saved non object data will be stored under `_content`. Processing checks this attribute and uses that content instead when it is detected.
|
|
1163
|
+
* @type: boolean
|
|
1164
|
+
* @default: true
|
|
1165
|
+
* @attribute:
|
|
1166
|
+
* @name: projection
|
|
1167
|
+
* @description: The data attributes to retrieve, when left undefined all attributes are retrieved.
|
|
1168
|
+
* @type: object
|
|
1169
|
+
* @default: undefined
|
|
1170
|
+
*/
|
|
1171
|
+
async list_all(
|
|
1172
|
+
uid: string | null = null,
|
|
1173
|
+
options: {
|
|
1174
|
+
process?: boolean,
|
|
1175
|
+
projection?: Record<string, any>
|
|
1176
|
+
} = {}
|
|
1177
|
+
): Promise<any[]> {
|
|
1178
|
+
if (uid == null) {
|
|
1179
|
+
return await this._col.list_all({}, options)
|
|
1180
|
+
} else {
|
|
1181
|
+
return await this._col.list_all({_uid: uid}, options);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/* @docs:
|
|
1186
|
+
* @title: Delete
|
|
1187
|
+
* @description: Delete a document of the collection by uid and path.
|
|
1188
|
+
* @parameter:
|
|
1189
|
+
* @name: uid
|
|
1190
|
+
* @cached: Users:uid:param
|
|
1191
|
+
* @parameter:
|
|
1192
|
+
* @name: path
|
|
1193
|
+
* @description: The database path to the document.
|
|
1194
|
+
* @type: string, object
|
|
1195
|
+
* @parameter:
|
|
1196
|
+
* @name: opts
|
|
1197
|
+
* @desc: Additional options.
|
|
1198
|
+
* @type: null, object
|
|
1199
|
+
* @attribute:
|
|
1200
|
+
* @name: chunked
|
|
1201
|
+
* @description: Delete a chunked document.
|
|
1202
|
+
* @default: false
|
|
1203
|
+
* @type: boolean
|
|
1204
|
+
* @attribute:
|
|
1205
|
+
* @name: bulk
|
|
1206
|
+
* @description: Get a bulk operation object, so several operations can be executed in bulk.
|
|
1207
|
+
* @default: false
|
|
1208
|
+
* @type: boolean
|
|
1209
|
+
*/
|
|
1210
|
+
async delete(
|
|
1211
|
+
uid: string,
|
|
1212
|
+
path: string | Record<string, any>,
|
|
1213
|
+
opts: {
|
|
1214
|
+
chunked?: boolean,
|
|
1215
|
+
bulk?: boolean
|
|
1216
|
+
} | null = null
|
|
1217
|
+
): Promise<any> {
|
|
1218
|
+
if (typeof path !== "string" && (typeof path !== "object" || path == null)) {
|
|
1219
|
+
throw Error(`Parameter "path" has an invalid type "${typeof path}", the valid type is "string".`);
|
|
1220
|
+
}
|
|
1221
|
+
if (typeof uid !== "string") {
|
|
1222
|
+
throw Error(`Parameter "uid" has an invalid type "${typeof uid}", the valid type is "string".`);
|
|
1223
|
+
}
|
|
1224
|
+
if (typeof path === "object") {
|
|
1225
|
+
return await this._col.delete({...path, _uid: uid}, opts)
|
|
1226
|
+
} else {
|
|
1227
|
+
return await this._col.delete({_path: path, _uid: uid}, opts)
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
/* @docs:
|
|
1232
|
+
* @title: Delete Query
|
|
1233
|
+
* @description: Delete a document of the collection by query.
|
|
1234
|
+
* @parameter:
|
|
1235
|
+
* @name: query
|
|
1236
|
+
* @description: The query object.
|
|
1237
|
+
* @type: object
|
|
1238
|
+
*/
|
|
1239
|
+
async delete_query(query: Record<string, any>): Promise<any> {
|
|
1240
|
+
if (typeof query !== "object" || query == null || Object.keys(query).length === 0) {
|
|
1241
|
+
throw Error(`Parameter "query" has an invalid type "${typeof query}", the valid type is "object".`);
|
|
1242
|
+
}
|
|
1243
|
+
if (Object.keys(query).length === 0) {
|
|
1244
|
+
throw Error(`Parameter "query" is an empty object.`);
|
|
1245
|
+
}
|
|
1246
|
+
return await this._col.delete_query(query)
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// Delete all.
|
|
1250
|
+
async delete_all(
|
|
1251
|
+
uid: string,
|
|
1252
|
+
path: string | Record<string, any> | null = null
|
|
1253
|
+
): Promise<void> {
|
|
1254
|
+
if (typeof uid !== "string") {
|
|
1255
|
+
throw Error(`Parameter "uid" has an invalid type "${typeof uid}", the valid type is "string".`);
|
|
1256
|
+
}
|
|
1257
|
+
if (path == null) {
|
|
1258
|
+
return await this._col.delete_all({_uid: uid})
|
|
1259
|
+
}
|
|
1260
|
+
else if (typeof path === "object") {
|
|
1261
|
+
return await this._col.delete_all({...path, _uid: uid})
|
|
1262
|
+
} else {
|
|
1263
|
+
return await this._col.delete_all({_path: path, _uid: uid})
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
/* @docs:
|
|
1268
|
+
* @title: Delete Collection
|
|
1269
|
+
* @description: Delete all documents of from the collection.
|
|
1270
|
+
*/
|
|
1271
|
+
async delete_collection(): Promise<void> {
|
|
1272
|
+
await this._col.delete_collection()
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/* @docs:
|
|
1276
|
+
* @title: Clean document
|
|
1277
|
+
* @description: Clean a document from all default system attributes.
|
|
1278
|
+
*/
|
|
1279
|
+
clean<T>(doc: T): T | null {
|
|
1280
|
+
return this._col.clean(doc);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/** Write bulk operations. */
|
|
1284
|
+
async bulk_operations(operations: any[] = []): Promise<any> {
|
|
1285
|
+
return await this.col.bulkWrite(operations, {ordered: true});
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// ---------------------------------------------------------
|
|
1290
|
+
// Database.
|
|
1291
|
+
|
|
1292
|
+
/* @docs:
|
|
1293
|
+
@nav: Backend
|
|
1294
|
+
@chapter: Database
|
|
1295
|
+
@title: Database
|
|
1296
|
+
@desc:
|
|
1297
|
+
The MongoDB database class, accessable under `Server.db`.
|
|
1298
|
+
|
|
1299
|
+
The database class can be utilized in two ways.
|
|
1300
|
+
|
|
1301
|
+
1. You only provide the `uri` parameter to access an already running mongodb database.
|
|
1302
|
+
|
|
1303
|
+
2. You provide parameters `config` and `start_args` to start and optionally create the database.
|
|
1304
|
+
|
|
1305
|
+
@warning:
|
|
1306
|
+
Do not forget to enable TLS when using the `config` parameter.
|
|
1307
|
+
@param:
|
|
1308
|
+
@name: uri
|
|
1309
|
+
@desc: The mongodb server uri.
|
|
1310
|
+
@type: string
|
|
1311
|
+
@param:
|
|
1312
|
+
@name: source
|
|
1313
|
+
@desc: The source path of the database directory, by default path `$server_source/.db` will be used.
|
|
1314
|
+
@type: null, string
|
|
1315
|
+
@param:
|
|
1316
|
+
@name: config
|
|
1317
|
+
@desc: The json data for the mongodb config file.
|
|
1318
|
+
@type: null, string
|
|
1319
|
+
@param:
|
|
1320
|
+
@name: start_args
|
|
1321
|
+
@desc: The mongod database start command arguments.
|
|
1322
|
+
@type: null, array[string]
|
|
1323
|
+
@param:
|
|
1324
|
+
@name: client
|
|
1325
|
+
@desc: The MongoClient options.
|
|
1326
|
+
@type: null, object
|
|
1327
|
+
*/
|
|
1328
|
+
class Database {
|
|
1329
|
+
static constructor_scheme = {
|
|
1330
|
+
uri: {type: "string", default: null},
|
|
1331
|
+
source: {type: "string", default: null},
|
|
1332
|
+
config: {type: "object", default: {}},
|
|
1333
|
+
start_args: {type: "array", default: []},
|
|
1334
|
+
client: {type: "object", default: {}},
|
|
1335
|
+
collections: {type: "array", default: [], value_scheme: {
|
|
1336
|
+
type: ["string", "object"],
|
|
1337
|
+
preprocess: (info: string | Record<string, any>) => typeof info === "string" ? {name: info} : info,
|
|
1338
|
+
scheme: {
|
|
1339
|
+
name: Collection.constructor_scheme.name,
|
|
1340
|
+
ttl: Collection.constructor_scheme.ttl,
|
|
1341
|
+
indexes: Collection.constructor_scheme.indexes,
|
|
1342
|
+
},
|
|
1343
|
+
}},
|
|
1344
|
+
uid_collections: {type: "array", default: [], value_scheme: {
|
|
1345
|
+
type: ["string", "object"],
|
|
1346
|
+
preprocess: (info: string | Record<string, any>) => typeof info === "string" ? {name: info} : info,
|
|
1347
|
+
scheme: {
|
|
1348
|
+
name: Collection.constructor_scheme.name,
|
|
1349
|
+
ttl: Collection.constructor_scheme.ttl,
|
|
1350
|
+
indexes: Collection.constructor_scheme.indexes,
|
|
1351
|
+
},
|
|
1352
|
+
}},
|
|
1353
|
+
preview: {type: "boolean", default: true},
|
|
1354
|
+
preview_ip_whitelist: {type: "array", default: []},
|
|
1355
|
+
daemon: {type: ["object", "boolean"], default: {}},
|
|
1356
|
+
_server: "object",
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
uri: string | null;
|
|
1360
|
+
preview: boolean;
|
|
1361
|
+
preview_ip_whitelist: string[];
|
|
1362
|
+
client_opts: Record<string, any> | null;
|
|
1363
|
+
config: Record<string, any>;
|
|
1364
|
+
source: any; // Using vlib.Path type
|
|
1365
|
+
start_args: string[];
|
|
1366
|
+
_collections: Array<{
|
|
1367
|
+
name: string,
|
|
1368
|
+
ttl?: number | null,
|
|
1369
|
+
indexes?: Array<{
|
|
1370
|
+
keys: string[] | string | Record<string, any>,
|
|
1371
|
+
options?: Record<string, any>,
|
|
1372
|
+
commit_quorum?: any
|
|
1373
|
+
}>,
|
|
1374
|
+
}>;
|
|
1375
|
+
_uid_collections: Array<{
|
|
1376
|
+
name: string,
|
|
1377
|
+
ttl?: number | null,
|
|
1378
|
+
indexes?: Array<{
|
|
1379
|
+
keys: string[] | string | Record<string, any>,
|
|
1380
|
+
options?: Record<string, any>,
|
|
1381
|
+
commit_quorum?: any
|
|
1382
|
+
}>,
|
|
1383
|
+
}>;
|
|
1384
|
+
server: any;
|
|
1385
|
+
client: MongoClient | null;
|
|
1386
|
+
collections: Record<string, Collection | UIDCollection>;
|
|
1387
|
+
proc?: ChildProcess;
|
|
1388
|
+
daemon?: any;
|
|
1389
|
+
db?: any;
|
|
1390
|
+
|
|
1391
|
+
constructor({
|
|
1392
|
+
uri = null,
|
|
1393
|
+
source = null,
|
|
1394
|
+
config = null,
|
|
1395
|
+
start_args = [],
|
|
1396
|
+
client = null,
|
|
1397
|
+
collections = [],
|
|
1398
|
+
uid_collections = [],
|
|
1399
|
+
preview = true,
|
|
1400
|
+
preview_ip_whitelist = [],
|
|
1401
|
+
daemon = {},
|
|
1402
|
+
_server,
|
|
1403
|
+
}: {
|
|
1404
|
+
uri?: string | null,
|
|
1405
|
+
source?: string | null,
|
|
1406
|
+
config?: Record<string, any> | null,
|
|
1407
|
+
start_args?: string[],
|
|
1408
|
+
client?: Record<string, any> | null,
|
|
1409
|
+
collections?: Array<{
|
|
1410
|
+
name: string,
|
|
1411
|
+
ttl?: number | null,
|
|
1412
|
+
indexes?: Array<{
|
|
1413
|
+
keys: string[] | string | Record<string, any>,
|
|
1414
|
+
options?: Record<string, any>,
|
|
1415
|
+
commit_quorum?: any
|
|
1416
|
+
}>,
|
|
1417
|
+
}>,
|
|
1418
|
+
uid_collections?: Array<{
|
|
1419
|
+
name: string,
|
|
1420
|
+
ttl?: number | null,
|
|
1421
|
+
indexes?: Array<{
|
|
1422
|
+
keys: string[] | string | Record<string, any>,
|
|
1423
|
+
options?: Record<string, any>,
|
|
1424
|
+
commit_quorum?: any
|
|
1425
|
+
}>,
|
|
1426
|
+
}>,
|
|
1427
|
+
preview?: boolean,
|
|
1428
|
+
preview_ip_whitelist?: string[],
|
|
1429
|
+
daemon?: Record<string, any> | boolean,
|
|
1430
|
+
_server: any,
|
|
1431
|
+
}) {
|
|
1432
|
+
// Checks.
|
|
1433
|
+
if (_server.is_primary && uri == null) {
|
|
1434
|
+
({uri, config, start_args, config, client} = vlib.Scheme.verify({
|
|
1435
|
+
object: arguments[0],
|
|
1436
|
+
check_unknown: true,
|
|
1437
|
+
scheme: Database.constructor_scheme
|
|
1438
|
+
}));
|
|
1439
|
+
}
|
|
1440
|
+
// Arguments.
|
|
1441
|
+
this.uri = uri;
|
|
1442
|
+
this.preview = preview;
|
|
1443
|
+
this.preview_ip_whitelist = preview_ip_whitelist;
|
|
1444
|
+
this.client_opts = client;
|
|
1445
|
+
this.config = config || {};
|
|
1446
|
+
this.source = source != null ? new vlib.Path(source) : _server.source.join(".db");
|
|
1447
|
+
this.start_args = start_args;
|
|
1448
|
+
this._collections = collections;
|
|
1449
|
+
this._uid_collections = uid_collections;
|
|
1450
|
+
this.server = _server;
|
|
1451
|
+
|
|
1452
|
+
// Attributes.
|
|
1453
|
+
this.client = null;
|
|
1454
|
+
this.collections = {};
|
|
1455
|
+
|
|
1456
|
+
// Initialize the service daemon.
|
|
1457
|
+
if (this.server.daemon && daemon !== false) {
|
|
1458
|
+
const log_source = this.server.source.join(".logs");
|
|
1459
|
+
if (!log_source.exists()) {
|
|
1460
|
+
log_source.mkdir_sync();
|
|
1461
|
+
}
|
|
1462
|
+
this.daemon = new vlib.Daemon({
|
|
1463
|
+
name: this.server.daemon.name + ".mongodb",
|
|
1464
|
+
user: (daemon as Record<string, any>).user || this.server.daemon.user,
|
|
1465
|
+
group: (daemon as Record<string, any>).group || this.server.daemon.group,
|
|
1466
|
+
command: "mongod",
|
|
1467
|
+
cwd: this.server.daemon.cwd,
|
|
1468
|
+
args: ["--config", this.source.join("mongod.json").str(), ...this.start_args],
|
|
1469
|
+
env: (daemon as Record<string, any>).env || this.server.daemon.env,
|
|
1470
|
+
description: (daemon as Record<string, any>).description || `Service daemon for the mongo database of website ${this.server.domain}.`,
|
|
1471
|
+
auto_restart: true,
|
|
1472
|
+
logs: (daemon as Record<string, any>).logs || log_source.join("logs.mongodb").str(),
|
|
1473
|
+
errors: (daemon as Record<string, any>).errors || log_source.join("errors.mongodb").str(),
|
|
1474
|
+
})
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
// Database preview.
|
|
1479
|
+
_initialize_db_preview(): void {
|
|
1480
|
+
/*
|
|
1481
|
+
if (this.preview && this.server.production === false) {
|
|
1482
|
+
this.server.endpoint(
|
|
1483
|
+
|
|
1484
|
+
// Database preview.
|
|
1485
|
+
{
|
|
1486
|
+
method: "GET" ,
|
|
1487
|
+
endpoint: "/volt/db/preview",
|
|
1488
|
+
view: {
|
|
1489
|
+
callback: () => {
|
|
1490
|
+
volt.utils.on_load(async () => {
|
|
1491
|
+
|
|
1492
|
+
// Style theme.
|
|
1493
|
+
const style = {
|
|
1494
|
+
// bg: "#151721",
|
|
1495
|
+
// sub_bg: "#191B28",
|
|
1496
|
+
// tag_bg: "#1C203A",
|
|
1497
|
+
// div_bg: "#282B40",
|
|
1498
|
+
// fg: "#FFFFFF",
|
|
1499
|
+
// sub_fg: "#FFFFFF99",
|
|
1500
|
+
// tag_fg: "#FFFFFF",
|
|
1501
|
+
|
|
1502
|
+
bg: "#F6F8F8",
|
|
1503
|
+
sub_bg: "#FFFFFF",
|
|
1504
|
+
tag_bg: "#F6F8F8",
|
|
1505
|
+
div_bg: "#00000010",
|
|
1506
|
+
fg: "#32334F",
|
|
1507
|
+
sub_fg: "#31344599",
|
|
1508
|
+
tag_fg: "#313445",
|
|
1509
|
+
};
|
|
1510
|
+
|
|
1511
|
+
// List all collections.
|
|
1512
|
+
const collections = (await volt.utils.request({url: "/volt/db/collections"})).collections;
|
|
1513
|
+
|
|
1514
|
+
// Render a list.
|
|
1515
|
+
const prev_lists = [];
|
|
1516
|
+
function RenderList ({
|
|
1517
|
+
title,
|
|
1518
|
+
list,
|
|
1519
|
+
doc = null,
|
|
1520
|
+
add_prev = true,
|
|
1521
|
+
}) {
|
|
1522
|
+
if (add_prev) {
|
|
1523
|
+
prev_lists.append({title, list, doc});
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
// Object view.
|
|
1527
|
+
const obj_view = VStack();
|
|
1528
|
+
const refresh_obj_view = () => {
|
|
1529
|
+
obj_view.inner_html("");
|
|
1530
|
+
let index = 0;
|
|
1531
|
+
obj_view.append(
|
|
1532
|
+
ForEach(list, (key, value) => {
|
|
1533
|
+
++index;
|
|
1534
|
+
let current_key = key;
|
|
1535
|
+
let value_type = Array.isArray(value) ? "array" : value == null ? "null" : typeof value;
|
|
1536
|
+
if (Array.isArray(value)) {
|
|
1537
|
+
value = JSON.stringify(value, null, 4)
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
// Key input.
|
|
1541
|
+
const key_input = Input("key")
|
|
1542
|
+
.value(key)
|
|
1543
|
+
.font_family("'Menlo', 'Consolas', monospace")
|
|
1544
|
+
.color(style.sub_fg)
|
|
1545
|
+
.font_size(14)
|
|
1546
|
+
.padding(0)
|
|
1547
|
+
.readonly(key === "_path" || key === "_uid" || key === "uid")
|
|
1548
|
+
.on_mouse_over(e => e.color(style.fg))
|
|
1549
|
+
.on_mouse_out(e => e.color(style.sub_fg))
|
|
1550
|
+
.on_render((e) => e.width(e.text_width(key)))
|
|
1551
|
+
.on_input((e) => {
|
|
1552
|
+
if (key != current_key) {
|
|
1553
|
+
list[e.value()] = list[current_key];
|
|
1554
|
+
delete list[current_key];
|
|
1555
|
+
current_key = e.value();
|
|
1556
|
+
}
|
|
1557
|
+
e.width(e.text_width(current_key));
|
|
1558
|
+
});
|
|
1559
|
+
|
|
1560
|
+
// Value input.
|
|
1561
|
+
const value_input = Input("value")
|
|
1562
|
+
.value(value == null ? "null" : value)
|
|
1563
|
+
.font_family("'Menlo', 'Consolas', monospace")
|
|
1564
|
+
.color(style.sub_fg)
|
|
1565
|
+
.display("inline-block")
|
|
1566
|
+
.width("fit-content")
|
|
1567
|
+
.font_size(14)
|
|
1568
|
+
.readonly(key === "_path" || key === "_uid")
|
|
1569
|
+
.stretch(true)
|
|
1570
|
+
.on_mouse_over(e => e.color(style.fg))
|
|
1571
|
+
.on_mouse_out(e => e.color(style.sub_fg))
|
|
1572
|
+
.on_input(() => {
|
|
1573
|
+
clearTimeout(value_input.timeout)
|
|
1574
|
+
value_input.timeout = setTimeout(update_value, 500);
|
|
1575
|
+
})
|
|
1576
|
+
|
|
1577
|
+
// Type select.
|
|
1578
|
+
const type_select = ExtendedSelect({items: ["null", "boolean", "number", "string", "array", "object"]})
|
|
1579
|
+
.center()
|
|
1580
|
+
.margin(0)
|
|
1581
|
+
.max_width(73)
|
|
1582
|
+
.color(style.sub_fg)
|
|
1583
|
+
.font_size(14)
|
|
1584
|
+
.border_radius(10)
|
|
1585
|
+
.background(style.tag_bg)
|
|
1586
|
+
.border_color(style.div_bg)
|
|
1587
|
+
.value(value_type)
|
|
1588
|
+
.container
|
|
1589
|
+
.padding(2.5, 5)
|
|
1590
|
+
.parent();
|
|
1591
|
+
|
|
1592
|
+
// Update the list after edits.
|
|
1593
|
+
const update_value = () => {
|
|
1594
|
+
const type = type_select.value();
|
|
1595
|
+
const value = value_input.value();
|
|
1596
|
+
if (type === "null") {
|
|
1597
|
+
list[current_key] = null;
|
|
1598
|
+
value_input.value(list[current_key].toString());
|
|
1599
|
+
}
|
|
1600
|
+
else if (type === "boolean") {
|
|
1601
|
+
list[current_key] = value == "true" || value == "True" || value == "TRUE" || value == "1";
|
|
1602
|
+
value_input.value(list[current_key].toString());
|
|
1603
|
+
}
|
|
1604
|
+
else if (type === "number") {
|
|
1605
|
+
if (value.indexOf(".") === -1) {
|
|
1606
|
+
list[current_key] = paseInt(value);
|
|
1607
|
+
} else {
|
|
1608
|
+
list[current_key] = paseFloat(value);
|
|
1609
|
+
}
|
|
1610
|
+
if (isNaN(list[key_input.key])) {
|
|
1611
|
+
list[current_key] = 0;
|
|
1612
|
+
}
|
|
1613
|
+
value_input.value(list[current_key].toString());
|
|
1614
|
+
}
|
|
1615
|
+
else if (type === "string") {
|
|
1616
|
+
list[current_key] = value;
|
|
1617
|
+
}
|
|
1618
|
+
else if (type === "object") {
|
|
1619
|
+
list[current_key] = JSON.parse(value);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
// Row.
|
|
1624
|
+
const row = HStack(
|
|
1625
|
+
key_input,
|
|
1626
|
+
Text(" : ")
|
|
1627
|
+
.white_space("pre")
|
|
1628
|
+
.font_family("'Menlo', 'Consolas', monospace")
|
|
1629
|
+
.color(style.sub_fg)
|
|
1630
|
+
.font_size(14),
|
|
1631
|
+
value_input,
|
|
1632
|
+
type_select,
|
|
1633
|
+
|
|
1634
|
+
index < Object.keys(list).length ? null : VStack("add")
|
|
1635
|
+
.background(style.tag_bg)
|
|
1636
|
+
.padding(5, 12.5)
|
|
1637
|
+
.border_radius(10)
|
|
1638
|
+
.font_size(13)
|
|
1639
|
+
.color("#3B8553")
|
|
1640
|
+
.margin_left(10)
|
|
1641
|
+
.border(1, style.div_bg)
|
|
1642
|
+
.on_click(() => {
|
|
1643
|
+
list["_new"] = "";
|
|
1644
|
+
refresh_obj_view();
|
|
1645
|
+
}),
|
|
1646
|
+
|
|
1647
|
+
VStack("delete")
|
|
1648
|
+
.background(style.tag_bg)
|
|
1649
|
+
.color("#B2321E")
|
|
1650
|
+
.padding(5, 12.5)
|
|
1651
|
+
.border_radius(10)
|
|
1652
|
+
.font_size(13)
|
|
1653
|
+
.margin_left(10)
|
|
1654
|
+
.border(1, style.div_bg)
|
|
1655
|
+
.on_click(async () => {
|
|
1656
|
+
}),
|
|
1657
|
+
)
|
|
1658
|
+
.center_vertical()
|
|
1659
|
+
.padding(7.5, 0)
|
|
1660
|
+
if (volt.utils.is_obj(value)) {
|
|
1661
|
+
row.on_click(() => RenderList({title: `${title}.${key}`, list: value}));
|
|
1662
|
+
}
|
|
1663
|
+
return [
|
|
1664
|
+
row,
|
|
1665
|
+
Divider().background(style.div_bg),
|
|
1666
|
+
]
|
|
1667
|
+
})
|
|
1668
|
+
);
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
// Add.
|
|
1672
|
+
preview.inner_html("");
|
|
1673
|
+
preview.append(
|
|
1674
|
+
Scroller(
|
|
1675
|
+
VStack(
|
|
1676
|
+
HStack(
|
|
1677
|
+
Title("Database")
|
|
1678
|
+
.font_family("'Menlo', 'Consolas', monospace")
|
|
1679
|
+
.font_size(12)
|
|
1680
|
+
.color(style.tag_fg)
|
|
1681
|
+
.background(style.tag_bg)
|
|
1682
|
+
.padding(5, 12.5)
|
|
1683
|
+
.border_radius(10)
|
|
1684
|
+
.margin(0, 0, 0, 0)
|
|
1685
|
+
.border(1, style.div_bg)
|
|
1686
|
+
.width("fit-content"),
|
|
1687
|
+
|
|
1688
|
+
Spacer(),
|
|
1689
|
+
|
|
1690
|
+
doc == null ? null : Button("Update")
|
|
1691
|
+
.background(style.tag_bg)
|
|
1692
|
+
.color("#3B8553")
|
|
1693
|
+
.padding(5, 12.5)
|
|
1694
|
+
.margin_right(10)
|
|
1695
|
+
.border_radius(10)
|
|
1696
|
+
.border(1, style.div_bg)
|
|
1697
|
+
.on_click(() => {
|
|
1698
|
+
// --prev_lists.length;
|
|
1699
|
+
// const last = prev_lists[prev_lists.length - 1];
|
|
1700
|
+
// RenderList({...last, add_prev: false})
|
|
1701
|
+
}),
|
|
1702
|
+
|
|
1703
|
+
doc == null ? null : Button("Delete")
|
|
1704
|
+
.background(style.tag_bg)
|
|
1705
|
+
.color("#B2321E")
|
|
1706
|
+
.padding(5, 12.5)
|
|
1707
|
+
.margin_right(10)
|
|
1708
|
+
.border_radius(10)
|
|
1709
|
+
.border(1, style.div_bg)
|
|
1710
|
+
.on_click(async () => {
|
|
1711
|
+
volt.utils.request({
|
|
1712
|
+
method: "DELETE",
|
|
1713
|
+
url: "/volt/db/document",
|
|
1714
|
+
data: doc,
|
|
1715
|
+
})
|
|
1716
|
+
--prev_lists.length;
|
|
1717
|
+
const last = prev_lists[prev_lists.length - 1];
|
|
1718
|
+
|
|
1719
|
+
const __name = doc.uid != null ? `${doc.uid}:${doc.id}` : doc.id
|
|
1720
|
+
const filtered_list = [];
|
|
1721
|
+
last.list.iterate((item) => {
|
|
1722
|
+
if (item.__name !== __name) {
|
|
1723
|
+
filtered_list.append(item);
|
|
1724
|
+
}
|
|
1725
|
+
})
|
|
1726
|
+
last.list = filtered_list;
|
|
1727
|
+
RenderList({...last, add_prev: false})
|
|
1728
|
+
}),
|
|
1729
|
+
|
|
1730
|
+
prev_lists.length == 1 ? null : Button("Prev")
|
|
1731
|
+
.background(style.tag_bg)
|
|
1732
|
+
.color(style.tag_fg)
|
|
1733
|
+
.padding(5, 12.5)
|
|
1734
|
+
.border_radius(10)
|
|
1735
|
+
.border(1, style.div_bg)
|
|
1736
|
+
.on_click(() => {
|
|
1737
|
+
--prev_lists.length;
|
|
1738
|
+
const last = prev_lists[prev_lists.length - 1];
|
|
1739
|
+
RenderList({...last, add_prev: false})
|
|
1740
|
+
}),
|
|
1741
|
+
),
|
|
1742
|
+
|
|
1743
|
+
Title(title)
|
|
1744
|
+
.font_family("'Menlo', 'Consolas', monospace")
|
|
1745
|
+
.font_size(18)
|
|
1746
|
+
.color(style.fg)
|
|
1747
|
+
.margin(15, 0),
|
|
1748
|
+
|
|
1749
|
+
Divider().background(style.div_bg),
|
|
1750
|
+
|
|
1751
|
+
Array.isArray(list)
|
|
1752
|
+
? ForEach(list, (item) => {
|
|
1753
|
+
return [
|
|
1754
|
+
VStack(
|
|
1755
|
+
Text(item.__name)
|
|
1756
|
+
.font_family("'Menlo', 'Consolas', monospace")
|
|
1757
|
+
.color(style.sub_fg)
|
|
1758
|
+
.font_size(14)
|
|
1759
|
+
.on_mouse_over(e => e.color(style.fg))
|
|
1760
|
+
.on_mouse_out(e => e.color(style.sub_fg))
|
|
1761
|
+
)
|
|
1762
|
+
.padding(7.5, 0)
|
|
1763
|
+
.on_click(item.__click),
|
|
1764
|
+
|
|
1765
|
+
Divider().background(style.div_bg),
|
|
1766
|
+
]
|
|
1767
|
+
})
|
|
1768
|
+
: () => {refresh_obj_view(); return obj_view}
|
|
1769
|
+
)
|
|
1770
|
+
.margin(25, 50)
|
|
1771
|
+
.padding(25, 25)
|
|
1772
|
+
.background(style.sub_bg)
|
|
1773
|
+
.border_radius(10)
|
|
1774
|
+
.box_shadow("0px 0px 5px #00000090")
|
|
1775
|
+
)
|
|
1776
|
+
.font_family("Helvetica, sans-serif")
|
|
1777
|
+
.background(style.bg)
|
|
1778
|
+
.frame("100%", "100%")
|
|
1779
|
+
)
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
// Render the collections.
|
|
1783
|
+
const RenderCollections = () => {
|
|
1784
|
+
RenderList({title: "/", list: collections.iterate_append((item) => {
|
|
1785
|
+
return {
|
|
1786
|
+
__name: `${item}/`,
|
|
1787
|
+
__click: () => RenderCollection(item),
|
|
1788
|
+
}
|
|
1789
|
+
})})
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
// Render a collection.
|
|
1793
|
+
const RenderCollection = async (collection) => {
|
|
1794
|
+
const documents = (await volt.utils.request({url: "/volt/db/documents", data: {collection}})).documents;
|
|
1795
|
+
RenderList({title: `${collection}/`, list: documents.iterate_append((item) => {
|
|
1796
|
+
return {
|
|
1797
|
+
__name: item._uid != null ? `${item._uid}:${item._path}` : item._path,
|
|
1798
|
+
__click: () => RenderDocument(collection, item._path, item._uid),
|
|
1799
|
+
}
|
|
1800
|
+
})})
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
// Render a document.
|
|
1804
|
+
const RenderDocument = async (collection, path, uid = null) => {
|
|
1805
|
+
let doc = (await volt.utils.request({url: "/volt/db/document", data: {collection, path, uid}})).document
|
|
1806
|
+
if (Array.isArray(doc)) {
|
|
1807
|
+
doc = {_content: doc};
|
|
1808
|
+
}
|
|
1809
|
+
RenderList({
|
|
1810
|
+
title: uid != null ? `${collection}/${uid}:${path}` : `${collection}/${path}`,
|
|
1811
|
+
list: doc,
|
|
1812
|
+
doc: {collection, uid, path},
|
|
1813
|
+
})
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
// Stack.
|
|
1817
|
+
const preview = VStack()
|
|
1818
|
+
.position(0, 0, 0, 0);
|
|
1819
|
+
|
|
1820
|
+
// Render all collections.
|
|
1821
|
+
RenderCollections();
|
|
1822
|
+
|
|
1823
|
+
// Response.
|
|
1824
|
+
return preview;
|
|
1825
|
+
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
},
|
|
1830
|
+
|
|
1831
|
+
// Get collections.
|
|
1832
|
+
{
|
|
1833
|
+
method: "GET",
|
|
1834
|
+
endpoint: "/volt/db/collections",
|
|
1835
|
+
content_type: "application/json",
|
|
1836
|
+
rate_limit: "global",
|
|
1837
|
+
callback: async (stream) => {
|
|
1838
|
+
|
|
1839
|
+
// Check ip whitelist.
|
|
1840
|
+
if (!this.preview_ip_whitelist.includes(stream.ip)) {
|
|
1841
|
+
return stream.error({status: Status.forbidden});
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
// Sign in.
|
|
1845
|
+
return stream.success({data: {
|
|
1846
|
+
message: "Successfully retrieved all collections.",
|
|
1847
|
+
collections: await this.get_collections(),
|
|
1848
|
+
}});
|
|
1849
|
+
}
|
|
1850
|
+
},
|
|
1851
|
+
|
|
1852
|
+
// Get collection documents.
|
|
1853
|
+
{
|
|
1854
|
+
method: "GET",
|
|
1855
|
+
endpoint: "/volt/db/documents",
|
|
1856
|
+
content_type: "application/json",
|
|
1857
|
+
rate_limit: "global",
|
|
1858
|
+
params: {
|
|
1859
|
+
collection: "string",
|
|
1860
|
+
},
|
|
1861
|
+
callback: async (stream, params) => {
|
|
1862
|
+
|
|
1863
|
+
// Check ip whitelist.
|
|
1864
|
+
if (!this.preview_ip_whitelist.includes(stream.ip)) {
|
|
1865
|
+
return stream.error({status: Status.forbidden});
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
// Check collection.
|
|
1869
|
+
let col;
|
|
1870
|
+
if ((col = this.collections[params.collection]) == null) {
|
|
1871
|
+
return stream.error({data: {error: `Invalid collection "${params.collection}".`}})
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
// Load docs.
|
|
1875
|
+
let docs = await col.list_all();
|
|
1876
|
+
|
|
1877
|
+
// Sign in.
|
|
1878
|
+
return stream.success({data: {
|
|
1879
|
+
message: "Successfully loaded the document.",
|
|
1880
|
+
documents: docs,
|
|
1881
|
+
}});
|
|
1882
|
+
}
|
|
1883
|
+
},
|
|
1884
|
+
|
|
1885
|
+
// Get document.
|
|
1886
|
+
{
|
|
1887
|
+
method: "GET",
|
|
1888
|
+
endpoint: "/volt/db/document",
|
|
1889
|
+
content_type: "application/json",
|
|
1890
|
+
rate_limit: "global",
|
|
1891
|
+
params: {
|
|
1892
|
+
collection: "string",
|
|
1893
|
+
path: ["string", "object"],
|
|
1894
|
+
uid: {type: ["string", "null"], default: null},
|
|
1895
|
+
},
|
|
1896
|
+
callback: async (stream, params) => {
|
|
1897
|
+
|
|
1898
|
+
// Check ip whitelist.
|
|
1899
|
+
if (!this.preview_ip_whitelist.includes(stream.ip)) {
|
|
1900
|
+
return stream.error({status: Status.forbidden});
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
// Check collection.
|
|
1904
|
+
let col;
|
|
1905
|
+
if ((col = this.collections[params.collection]) == null) {
|
|
1906
|
+
return stream.error({data: {error: `Invalid collection "${params.collection}".`}})
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
// Load doc.
|
|
1910
|
+
let doc;
|
|
1911
|
+
if (params.uid == null) {
|
|
1912
|
+
doc = await col.load(params.path);
|
|
1913
|
+
} else {
|
|
1914
|
+
doc = await col.load(params.uid, params.path);
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
// Sign in.
|
|
1918
|
+
return stream.success({data: {
|
|
1919
|
+
message: "Successfully loaded the document.",
|
|
1920
|
+
document: doc,
|
|
1921
|
+
}});
|
|
1922
|
+
}
|
|
1923
|
+
},
|
|
1924
|
+
|
|
1925
|
+
// Delete document.
|
|
1926
|
+
{
|
|
1927
|
+
method: "DELETE",
|
|
1928
|
+
endpoint: "/volt/db/document",
|
|
1929
|
+
content_type: "application/json",
|
|
1930
|
+
rate_limit: "global",
|
|
1931
|
+
params: {
|
|
1932
|
+
collection: "string",
|
|
1933
|
+
path: ["string", "object"],
|
|
1934
|
+
uid: {type: ["string", "null"], default: null},
|
|
1935
|
+
},
|
|
1936
|
+
callback: async (stream, params) => {
|
|
1937
|
+
|
|
1938
|
+
// Check ip whitelist.
|
|
1939
|
+
if (!this.preview_ip_whitelist.includes(stream.ip)) {
|
|
1940
|
+
return stream.error({status: Status.forbidden});
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
// Check collection.
|
|
1944
|
+
let col;
|
|
1945
|
+
if ((col = this.collections[params.collection]) == null) {
|
|
1946
|
+
return stream.error({data: {error: `Invalid collection "${params.collection}".`}})
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
// Load doc.
|
|
1950
|
+
let doc;
|
|
1951
|
+
if (params.uid == null) {
|
|
1952
|
+
doc = await col.delete(params.path);
|
|
1953
|
+
} else {
|
|
1954
|
+
doc = await col.delete(params.uid, params.path);
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
// Sign in.
|
|
1958
|
+
return stream.success({data: {
|
|
1959
|
+
message: "Successfully deleted the document.",
|
|
1960
|
+
}});
|
|
1961
|
+
}
|
|
1962
|
+
},
|
|
1963
|
+
|
|
1964
|
+
// Update document.
|
|
1965
|
+
{
|
|
1966
|
+
method: "PATCH",
|
|
1967
|
+
endpoint: "/volt/db/document",
|
|
1968
|
+
content_type: "application/json",
|
|
1969
|
+
rate_limit: "global",
|
|
1970
|
+
params: {
|
|
1971
|
+
collection: "string",
|
|
1972
|
+
path: ["string", "object"],
|
|
1973
|
+
uid: {type: ["string", "null"], default: null},
|
|
1974
|
+
content: "object",
|
|
1975
|
+
},
|
|
1976
|
+
callback: async (stream, params) => {
|
|
1977
|
+
|
|
1978
|
+
// Check ip whitelist.
|
|
1979
|
+
if (!this.preview_ip_whitelist.includes(stream.ip)) {
|
|
1980
|
+
return stream.error({status: Status.forbidden});
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
// Check collection.
|
|
1984
|
+
let col;
|
|
1985
|
+
if ((col = this.collections[params.collection]) == null) {
|
|
1986
|
+
return stream.error({data: {error: `Invalid collection "${params.collection}".`}})
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
// Load doc.
|
|
1990
|
+
let doc;
|
|
1991
|
+
if (params.uid == null) {
|
|
1992
|
+
doc = await col.save(params.path, params.content);
|
|
1993
|
+
} else {
|
|
1994
|
+
doc = await col.save(params.uid, params.path, params.content);
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
// Sign in.
|
|
1998
|
+
return stream.success({data: {
|
|
1999
|
+
message: "Successfully updated the document.",
|
|
2000
|
+
}});
|
|
2001
|
+
}
|
|
2002
|
+
},
|
|
2003
|
+
)
|
|
2004
|
+
}
|
|
2005
|
+
*/
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
// Connect.
|
|
2009
|
+
async connect(): Promise<void> {
|
|
2010
|
+
try {
|
|
2011
|
+
await this.client?.connect();
|
|
2012
|
+
if (this.client) {
|
|
2013
|
+
this.db = this.client.db();
|
|
2014
|
+
}
|
|
2015
|
+
} catch (error) {
|
|
2016
|
+
console.error(error);
|
|
2017
|
+
throw new Error('Error connecting to the database');
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
// Initialize.
|
|
2022
|
+
async initialize(): Promise<void> {
|
|
2023
|
+
// Set default config.
|
|
2024
|
+
if (this.config.systemLog === undefined) { this.config.systemLog = {}; }
|
|
2025
|
+
|
|
2026
|
+
this.config.systemLog.path = this.source.join("mongod.log").str()
|
|
2027
|
+
|
|
2028
|
+
if (this.config.systemLog.destination === undefined) {
|
|
2029
|
+
this.config.systemLog.destination = "file";
|
|
2030
|
+
}
|
|
2031
|
+
if (this.config.systemLog.logAppend === undefined) {
|
|
2032
|
+
this.config.systemLog.logAppend = true;
|
|
2033
|
+
}
|
|
2034
|
+
if (this.config.systemLog.logRotate === undefined) {
|
|
2035
|
+
this.config.systemLog.logRotate = "reopen";
|
|
2036
|
+
}
|
|
2037
|
+
if (this.config.systemLog.verbosity === undefined) {
|
|
2038
|
+
this.config.systemLog.verbosity = this.server.production ? 0 : 1;
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
if (this.config.storage === undefined) { this.config.storage = {}; }
|
|
2042
|
+
|
|
2043
|
+
const db_path = this.source.join("db");
|
|
2044
|
+
this.config.storage.dbPath = db_path.str()
|
|
2045
|
+
if (!db_path.exists()) {
|
|
2046
|
+
db_path.mkdir_sync();
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
if (this.config.processManagement === undefined) { this.config.processManagement = {}; }
|
|
2050
|
+
this.config.processManagement.pidFilePath = this.source.join("mongod.pid").str()
|
|
2051
|
+
|
|
2052
|
+
if (this.config.net === undefined) { this.config.net = {}; }
|
|
2053
|
+
if (this.config.net.port === undefined) { this.config.net.port = 27017; }
|
|
2054
|
+
if (this.config.net.bindIp === undefined) { this.config.net.bindIp = "127.0.0.1"; }
|
|
2055
|
+
|
|
2056
|
+
// Mode 2: Start database.
|
|
2057
|
+
if (this.server.is_primary && this.uri == null) {
|
|
2058
|
+
// Create the database.
|
|
2059
|
+
if (!this.source.exists()) {
|
|
2060
|
+
this.source.mkdir_sync();
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
// Set the uri.
|
|
2064
|
+
if (this.uri == null) {
|
|
2065
|
+
this.uri = `mongodb://${this.config.net.bindIp}:${this.config.net.port}/main`
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
// Save the config.
|
|
2069
|
+
const config_path = this.source.join("mongod.json");
|
|
2070
|
+
config_path.save_sync(JSON.stringify(this.config));
|
|
2071
|
+
|
|
2072
|
+
// Start the database.
|
|
2073
|
+
this.proc = spawn(
|
|
2074
|
+
"mongod",
|
|
2075
|
+
["--config", config_path.str(), ...this.start_args],
|
|
2076
|
+
{
|
|
2077
|
+
stdio: "pipe",
|
|
2078
|
+
detached: true,
|
|
2079
|
+
env: {...process.env},
|
|
2080
|
+
},
|
|
2081
|
+
)
|
|
2082
|
+
this.proc.stdout?.on('data', (data) => {
|
|
2083
|
+
console.log(data.toString());
|
|
2084
|
+
})
|
|
2085
|
+
this.proc.stderr?.on('data', (data) => {
|
|
2086
|
+
console.error(data.toString());
|
|
2087
|
+
})
|
|
2088
|
+
this.proc.on("error", (code, signal) => {
|
|
2089
|
+
console.error(`MongoDB crashed with error signal ${signal}.`);
|
|
2090
|
+
process.exit(code);
|
|
2091
|
+
})
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
// Assign URI.
|
|
2095
|
+
else if (!this.server.is_primary && this.uri == null) {
|
|
2096
|
+
this.uri = `mongodb://${this.config.net.bindIp}:${this.config.net.port}/main`
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
// Initialize client.
|
|
2100
|
+
if (this.uri) {
|
|
2101
|
+
this.client = new MongoClient(this.uri, this.client_opts || {});
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
// Connect.
|
|
2105
|
+
await this.connect();
|
|
2106
|
+
|
|
2107
|
+
// Create collections.
|
|
2108
|
+
this._collections.forEach((info) => {
|
|
2109
|
+
if (this[info.name as keyof this] !== undefined) {
|
|
2110
|
+
throw Error(`Unable to initialize database collection "${info.name}", this attribute name is already used.`);
|
|
2111
|
+
}
|
|
2112
|
+
if (Array.isArray(info.indexes)) {
|
|
2113
|
+
for (const index of info.indexes) {
|
|
2114
|
+
if (index != null && typeof index === "object") {
|
|
2115
|
+
(index as any).forced = true;
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
this[info.name] = this.create_collection(info);
|
|
2120
|
+
})
|
|
2121
|
+
this._uid_collections.forEach((info) => {
|
|
2122
|
+
if (this[info.name as keyof this] !== undefined) {
|
|
2123
|
+
throw Error(`Unable to initialize database collection "${info.name}", this attribute name is already used.`);
|
|
2124
|
+
}
|
|
2125
|
+
if (Array.isArray(info.indexes)) {
|
|
2126
|
+
for (const index of info.indexes) {
|
|
2127
|
+
if (index != null && typeof index === "object") {
|
|
2128
|
+
(index as any).forced = true;
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
this[info.name] = this.create_uid_collection(info);
|
|
2133
|
+
})
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
// Close.
|
|
2137
|
+
async close(): Promise<void> {
|
|
2138
|
+
logger.log(0, log_source, "Stopping the database.");
|
|
2139
|
+
await this.client?.close();
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
/* @docs:
|
|
2143
|
+
* @title: Create Collection
|
|
2144
|
+
* @description: Create a database collection.
|
|
2145
|
+
*/
|
|
2146
|
+
create_collection(info: {
|
|
2147
|
+
name: string,
|
|
2148
|
+
indexes?: Array<{
|
|
2149
|
+
keys: string[] | string | Record<string, any>,
|
|
2150
|
+
options?: Record<string, any>,
|
|
2151
|
+
commit_quorum?: any
|
|
2152
|
+
}>,
|
|
2153
|
+
ttl?: number | null
|
|
2154
|
+
} | string): Collection {
|
|
2155
|
+
// Set name by single string argument.
|
|
2156
|
+
let name: string;
|
|
2157
|
+
let indexes: Array<any> = [];
|
|
2158
|
+
let ttl: number | null = null;
|
|
2159
|
+
|
|
2160
|
+
if (typeof info === "string") {
|
|
2161
|
+
name = info;
|
|
2162
|
+
} else {
|
|
2163
|
+
name = info.name;
|
|
2164
|
+
indexes = info.indexes || [];
|
|
2165
|
+
ttl = info.ttl || null;
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
// Check collection.
|
|
2169
|
+
if (name in this.collections) {
|
|
2170
|
+
throw Error(`Collection "${name}" is already initialized.`)
|
|
2171
|
+
}
|
|
2172
|
+
const col = new Collection(
|
|
2173
|
+
name,
|
|
2174
|
+
this.db.collection(name),
|
|
2175
|
+
ttl,
|
|
2176
|
+
indexes,
|
|
2177
|
+
);
|
|
2178
|
+
this.collections[name] = col;
|
|
2179
|
+
return col;
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
/* @docs:
|
|
2183
|
+
* @title: Create UID Based Collection
|
|
2184
|
+
* @description: Create a UID based database collection.
|
|
2185
|
+
*/
|
|
2186
|
+
create_uid_collection(info: {
|
|
2187
|
+
name: string,
|
|
2188
|
+
indexes?: Array<{
|
|
2189
|
+
keys: string[] | string | Record<string, any>,
|
|
2190
|
+
options?: Record<string, any>,
|
|
2191
|
+
commit_quorum?: any
|
|
2192
|
+
}>,
|
|
2193
|
+
ttl?: number | null
|
|
2194
|
+
} | string): UIDCollection {
|
|
2195
|
+
// Set name by single string argument.
|
|
2196
|
+
let name: string;
|
|
2197
|
+
let indexes: Array<any> = [];
|
|
2198
|
+
let ttl: number | null = null;
|
|
2199
|
+
|
|
2200
|
+
if (typeof info === "string") {
|
|
2201
|
+
name = info;
|
|
2202
|
+
} else {
|
|
2203
|
+
name = info.name;
|
|
2204
|
+
indexes = info.indexes || [];
|
|
2205
|
+
ttl = info.ttl || null;
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
// Check collection.
|
|
2209
|
+
if (name in this.collections) {
|
|
2210
|
+
throw Error(`Collection "${name}" is already initialized.`)
|
|
2211
|
+
}
|
|
2212
|
+
const col = new UIDCollection(
|
|
2213
|
+
name,
|
|
2214
|
+
this.db.collection(name),
|
|
2215
|
+
indexes,
|
|
2216
|
+
ttl,
|
|
2217
|
+
);
|
|
2218
|
+
this.collections[name] = col;
|
|
2219
|
+
return col;
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
/* @docs:
|
|
2223
|
+
* @title: Get Collections
|
|
2224
|
+
* @description: Get the names of the initializated database collections.
|
|
2225
|
+
*/
|
|
2226
|
+
async get_collections(): Promise<string[]> {
|
|
2227
|
+
const created = Object.keys(this.collections);
|
|
2228
|
+
const database = (await this.db.listCollections().toArray()).map((item: any) => item.name);
|
|
2229
|
+
return created.concat(database)
|
|
2230
|
+
.filter((value, index, self) => self.indexOf(value) === index)
|
|
2231
|
+
.sort((a, b) => {
|
|
2232
|
+
const result = a.toLowerCase().localeCompare(b.toLowerCase());
|
|
2233
|
+
if (a.startsWith('_') && b.startsWith('_')) { return result; }
|
|
2234
|
+
if (a.startsWith('_')) { return 1; }
|
|
2235
|
+
if (b.startsWith('_')) { return -1; }
|
|
2236
|
+
return result;
|
|
2237
|
+
});
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
export { Collection, UIDCollection, Database };
|