dobo 2.30.7 → 2.32.0

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.
Files changed (123) hide show
  1. package/.jsdoc.conf.json +1 -1
  2. package/docs/Dobo.html +1 -24
  3. package/docs/DoboAction.html +13 -0
  4. package/docs/DoboAdapter.html +3 -0
  5. package/docs/DoboConnection.html +3 -0
  6. package/docs/DoboFeature.html +3 -0
  7. package/docs/DoboModel.html +51 -0
  8. package/docs/data/search.json +1 -1
  9. package/docs/extend_bajo_hook-docs.js.html +241 -0
  10. package/docs/external-Tools.html +3 -0
  11. package/docs/global.html +1 -5
  12. package/docs/index.html +1 -1
  13. package/docs/index.js.html +434 -380
  14. package/docs/lib_factory_action.js.html +290 -0
  15. package/docs/lib_factory_adapter.js.html +1057 -0
  16. package/docs/lib_factory_connection.js.html +112 -0
  17. package/docs/lib_factory_feature.js.html +55 -0
  18. package/docs/lib_factory_model.js.html +503 -0
  19. package/docs/lib_factory_model_build.js.html +28 -0
  20. package/docs/lib_factory_model_clear-record.js.html +33 -0
  21. package/docs/lib_factory_model_count-record.js.html +45 -0
  22. package/docs/lib_factory_model_create-record.js.html +52 -0
  23. package/docs/lib_factory_model_drop.js.html +29 -0
  24. package/docs/lib_factory_model_exists.js.html +29 -0
  25. package/docs/lib_factory_model_find-all-record.js.html +115 -0
  26. package/docs/lib_factory_model_find-one-record.js.html +41 -0
  27. package/docs/lib_factory_model_find-record.js.html +85 -0
  28. package/docs/lib_factory_model_get-record.js.html +55 -0
  29. package/docs/lib_factory_model_helper.js.html +579 -0
  30. package/docs/lib_factory_model_remove-record.js.html +47 -0
  31. package/docs/lib_factory_model_sanitize-body.js.html +60 -0
  32. package/docs/lib_factory_model_sanitize-fixture.js.html +59 -0
  33. package/docs/lib_factory_model_sanitize-record.js.html +73 -0
  34. package/docs/lib_factory_model_update-record.js.html +61 -0
  35. package/docs/lib_factory_model_upsert-record.js.html +74 -0
  36. package/docs/{method_validate.js.html → lib_factory_model_validate.js.html} +109 -73
  37. package/docs/lib_helper.js.html +466 -0
  38. package/docs/module-Helper.html +3 -0
  39. package/docs/module-Helper_Model.html +3 -0
  40. package/docs/module-Hook.html +15 -0
  41. package/docs/scripts/core.js +477 -476
  42. package/docs/scripts/resize.js +36 -36
  43. package/docs/scripts/search.js +105 -105
  44. package/docs/scripts/third-party/fuse.js +1 -1
  45. package/docs/scripts/third-party/hljs-line-num-original.js +285 -282
  46. package/docs/scripts/third-party/hljs-line-num.js +1 -1
  47. package/docs/scripts/third-party/hljs-original.js +1202 -1195
  48. package/docs/scripts/third-party/hljs.js +1 -1
  49. package/docs/scripts/third-party/popper.js +1 -1
  50. package/docs/scripts/third-party/tippy.js +1 -1
  51. package/docs/scripts/third-party/tocbot.js +509 -508
  52. package/docs/static/home.md +3 -1
  53. package/extend/bajo/hook-docs.js +238 -0
  54. package/extend/bajo/intl/en-US.json +9 -9
  55. package/extend/bajo/intl/id.json +9 -9
  56. package/extend/bajoCli/applet/rebuild-model.js +2 -2
  57. package/extend/dobo/{driver → adapter}/memory.js +6 -6
  58. package/extend/dobo/feature/immutable.js +2 -2
  59. package/extend/dobo/feature/removed-at.js +1 -1
  60. package/index.js +33 -30
  61. package/lib/factory/action.js +145 -18
  62. package/lib/factory/adapter.js +1054 -0
  63. package/lib/factory/connection.js +46 -22
  64. package/lib/factory/feature.js +23 -3
  65. package/lib/factory/model/build.js +11 -2
  66. package/lib/factory/model/bulk-create-record.js +2 -2
  67. package/lib/factory/model/clear-record.js +13 -3
  68. package/lib/factory/model/count-record.js +13 -2
  69. package/lib/factory/model/create-aggregate.js +2 -2
  70. package/lib/factory/model/create-attachment.js +1 -1
  71. package/lib/factory/model/create-histogram.js +2 -2
  72. package/lib/factory/model/create-record.js +15 -2
  73. package/lib/factory/model/drop.js +12 -2
  74. package/lib/factory/model/exists.js +10 -6
  75. package/lib/factory/model/find-all-record.js +14 -3
  76. package/lib/factory/model/find-attachment.js +1 -1
  77. package/lib/factory/model/find-one-record.js +13 -1
  78. package/lib/factory/model/find-record.js +17 -53
  79. package/lib/factory/model/get-record.js +11 -39
  80. package/lib/factory/model/{_util.js → helper.js} +176 -16
  81. package/lib/factory/model/remove-attachment.js +1 -1
  82. package/lib/factory/model/remove-record.js +11 -28
  83. package/lib/factory/model/sanitize-body.js +14 -12
  84. package/lib/factory/model/sanitize-fixture.js +17 -4
  85. package/lib/factory/model/sanitize-record.js +9 -6
  86. package/lib/factory/model/transaction.js +2 -2
  87. package/lib/factory/model/update-record.js +12 -40
  88. package/lib/factory/model/upsert-record.js +13 -42
  89. package/lib/factory/model/validate.js +47 -14
  90. package/lib/factory/model.js +301 -23
  91. package/lib/{collect-models.js → helper.js} +463 -364
  92. package/package.json +1 -1
  93. package/wiki/CHANGES.md +17 -7
  94. package/wiki/ECOSYSTEM.md +7 -7
  95. package/wiki/GETTING-STARTED.md +3 -3
  96. package/wiki/QUERY-LANGUAGE.md +1 -1
  97. package/docs/lib_collect-connections.js.html +0 -39
  98. package/docs/lib_collect-drivers.js.html +0 -52
  99. package/docs/lib_collect-features.js.html +0 -36
  100. package/docs/lib_collect-schemas.js.html +0 -94
  101. package/docs/lib_index.js.html +0 -6
  102. package/docs/method_model_create.js.html +0 -35
  103. package/docs/method_model_drop.js.html +0 -34
  104. package/docs/method_model_exists.js.html +0 -40
  105. package/docs/method_record_count.js.html +0 -69
  106. package/docs/method_record_create.js.html +0 -114
  107. package/docs/method_record_find-all.js.html +0 -44
  108. package/docs/method_record_find-one.js.html +0 -73
  109. package/docs/method_record_find.js.html +0 -118
  110. package/docs/method_record_get.js.html +0 -92
  111. package/docs/method_record_remove.js.html +0 -75
  112. package/docs/method_record_update.js.html +0 -107
  113. package/docs/method_record_upsert.js.html +0 -54
  114. package/docs/method_sanitize_body.js.html +0 -88
  115. package/docs/method_sanitize_date.js.html +0 -30
  116. package/docs/method_sanitize_id.js.html +0 -20
  117. package/docs/module-Lib.html +0 -3
  118. package/lib/collect-connections.js +0 -56
  119. package/lib/collect-drivers.js +0 -35
  120. package/lib/collect-features.js +0 -40
  121. package/lib/factory/driver.js +0 -574
  122. package/lib/factory/model/sanitize-id.js +0 -7
  123. package/lib/index.js +0 -3
@@ -1,44 +1,13 @@
1
1
  <!DOCTYPE html><html lang="en" style="font-size:16px"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Source: index.js</title><!--[if lt IE 9]>
2
2
  <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
3
- <![endif]--><script src="scripts/third-party/hljs.js" defer="defer"></script><script src="scripts/third-party/hljs-line-num.js" defer="defer"></script><script src="scripts/third-party/popper.js" defer="defer"></script><script src="scripts/third-party/tippy.js" defer="defer"></script><script src="scripts/third-party/tocbot.min.js"></script><script>var baseURL="/",locationPathname="";baseURL=(locationPathname=document.location.pathname).substr(0,locationPathname.lastIndexOf("/")+1)</script><link rel="stylesheet" href="styles/clean-jsdoc-theme.min.css"><svg aria-hidden="true" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="display:none"><defs><symbol id="copy-icon" viewbox="0 0 488.3 488.3"><g><path d="M314.25,85.4h-227c-21.3,0-38.6,17.3-38.6,38.6v325.7c0,21.3,17.3,38.6,38.6,38.6h227c21.3,0,38.6-17.3,38.6-38.6V124 C352.75,102.7,335.45,85.4,314.25,85.4z M325.75,449.6c0,6.4-5.2,11.6-11.6,11.6h-227c-6.4,0-11.6-5.2-11.6-11.6V124 c0-6.4,5.2-11.6,11.6-11.6h227c6.4,0,11.6,5.2,11.6,11.6V449.6z"/><path d="M401.05,0h-227c-21.3,0-38.6,17.3-38.6,38.6c0,7.5,6,13.5,13.5,13.5s13.5-6,13.5-13.5c0-6.4,5.2-11.6,11.6-11.6h227 c6.4,0,11.6,5.2,11.6,11.6v325.7c0,6.4-5.2,11.6-11.6,11.6c-7.5,0-13.5,6-13.5,13.5s6,13.5,13.5,13.5c21.3,0,38.6-17.3,38.6-38.6 V38.6C439.65,17.3,422.35,0,401.05,0z"/></g></symbol><symbol id="search-icon" viewBox="0 0 512 512"><g><g><path d="M225.474,0C101.151,0,0,101.151,0,225.474c0,124.33,101.151,225.474,225.474,225.474 c124.33,0,225.474-101.144,225.474-225.474C450.948,101.151,349.804,0,225.474,0z M225.474,409.323 c-101.373,0-183.848-82.475-183.848-183.848S124.101,41.626,225.474,41.626s183.848,82.475,183.848,183.848 S326.847,409.323,225.474,409.323z"/></g></g><g><g><path d="M505.902,476.472L386.574,357.144c-8.131-8.131-21.299-8.131-29.43,0c-8.131,8.124-8.131,21.306,0,29.43l119.328,119.328 c4.065,4.065,9.387,6.098,14.715,6.098c5.321,0,10.649-2.033,14.715-6.098C514.033,497.778,514.033,484.596,505.902,476.472z"/></g></g></symbol><symbol id="font-size-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.246 15H4.754l-2 5H.6L7 4h2l6.4 16h-2.154l-2-5zm-.8-2L8 6.885 5.554 13h4.892zM21 12.535V12h2v8h-2v-.535a4 4 0 1 1 0-6.93zM19 18a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></symbol><symbol id="add-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"/></symbol><symbol id="minus-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M5 11h14v2H5z"/></symbol><symbol id="dark-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 7a7 7 0 0 0 12 4.9v.1c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2h.1A6.979 6.979 0 0 0 10 7zm-6 5a8 8 0 0 0 15.062 3.762A9 9 0 0 1 8.238 4.938 7.999 7.999 0 0 0 4 12z"/></symbol><symbol id="light-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05 3.515 4.93zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414-2.121-2.121zm2.121-14.85l1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414 2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z"/></symbol><symbol id="reset-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M18.537 19.567A9.961 9.961 0 0 1 12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10c0 2.136-.67 4.116-1.81 5.74L17 12h3a8 8 0 1 0-2.46 5.772l.997 1.795z"/></symbol><symbol id="down-icon" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M12.7803 6.21967C13.0732 6.51256 13.0732 6.98744 12.7803 7.28033L8.53033 11.5303C8.23744 11.8232 7.76256 11.8232 7.46967 11.5303L3.21967 7.28033C2.92678 6.98744 2.92678 6.51256 3.21967 6.21967C3.51256 5.92678 3.98744 5.92678 4.28033 6.21967L8 9.93934L11.7197 6.21967C12.0126 5.92678 12.4874 5.92678 12.7803 6.21967Z"></path></symbol><symbol id="codepen-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M16.5 13.202L13 15.535v3.596L19.197 15 16.5 13.202zM14.697 12L12 10.202 9.303 12 12 13.798 14.697 12zM20 10.869L18.303 12 20 13.131V10.87zM19.197 9L13 4.869v3.596l3.5 2.333L19.197 9zM7.5 10.798L11 8.465V4.869L4.803 9 7.5 10.798zM4.803 15L11 19.131v-3.596l-3.5-2.333L4.803 15zM4 13.131L5.697 12 4 10.869v2.262zM2 9a1 1 0 0 1 .445-.832l9-6a1 1 0 0 1 1.11 0l9 6A1 1 0 0 1 22 9v6a1 1 0 0 1-.445.832l-9 6a1 1 0 0 1-1.11 0l-9-6A1 1 0 0 1 2 15V9z"/></symbol><symbol id="close-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z"/></symbol><symbol id="menu-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M3 4h18v2H3V4zm0 7h18v2H3v-2zm0 7h18v2H3v-2z"/></symbol></defs></svg></head><body data-theme="light"><div class="sidebar-container"><div class="sidebar" id="sidebar"><a href="/" class="sidebar-title sidebar-title-anchor">Dobo API</a><div class="sidebar-items-container"><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-classes"><div>Classes</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="Dobo.html">Dobo</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-modules"><div>Modules</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="module-Lib.html">Lib</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-global"><div>Global</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="global.html#TPropType">TPropType</a></div><div class="sidebar-section-children"><a href="global.html#TRecordCountOptions">TRecordCountOptions</a></div><div class="sidebar-section-children"><a href="global.html#TRecordCreateOptions">TRecordCreateOptions</a></div><div class="sidebar-section-children"><a href="global.html#TRecordFilter">TRecordFilter</a></div><div class="sidebar-section-children"><a href="global.html#TRecordFindOptions">TRecordFindOptions</a></div><div class="sidebar-section-children"><a href="global.html#TRecordFindResult">TRecordFindResult</a></div><div class="sidebar-section-children"><a href="global.html#TRecordGetOptions">TRecordGetOptions</a></div><div class="sidebar-section-children"><a href="global.html#TRecordGetResult">TRecordGetResult</a></div><div class="sidebar-section-children"><a href="global.html#TRecordPagination">TRecordPagination</a></div><div class="sidebar-section-children"><a href="global.html#TRecordRemoveOptions">TRecordRemoveOptions</a></div><div class="sidebar-section-children"><a href="global.html#TRecordSort">TRecordSort</a></div><div class="sidebar-section-children"><a href="global.html#TRecordSortKey">TRecordSortKey</a></div><div class="sidebar-section-children"><a href="global.html#TRecordUpdateOptions">TRecordUpdateOptions</a></div><div class="sidebar-section-children"><a href="global.html#TValidator">TValidator</a></div><div class="sidebar-section-children"><a href="global.html#TValidatorBoolean">TValidatorBoolean</a></div><div class="sidebar-section-children"><a href="global.html#TValidatorDate">TValidatorDate</a></div><div class="sidebar-section-children"><a href="global.html#TValidatorNumber">TValidatorNumber</a></div><div class="sidebar-section-children"><a href="global.html#TValidatorString">TValidatorString</a></div><div class="sidebar-section-children"><a href="global.html#TValidatorTimestamp">TValidatorTimestamp</a></div><div class="sidebar-section-children"><a href="global.html#factory">factory</a></div></div></div></div></div><div class="navbar-container" id="VuAckcnZhf"><nav class="navbar"><div class="navbar-left-items"><div class="navbar-item"><a id="" href="https://www.npmjs.com/package/dobo" target="">NPM</a></div><div class="navbar-item"><a id="" href="https://github.com/ardhi/dobo" target="">Github</a></div><div class="navbar-item"><a id="" href="https://dobo.bajo.app/" target="">Dobo</a></div><div class="navbar-item"><a id="" href="https://bajo.app/" target="">Bajo</a></div></div><div class="navbar-right-items"><div class="navbar-right-item"><button class="icon-button search-button" aria-label="open-search"><svg><use xlink:href="#search-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button theme-toggle" aria-label="toggle-theme"><svg><use class="theme-svg-use" xlink:href="#dark-theme-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button font-size" aria-label="change-font-size"><svg><use xlink:href="#font-size-icon"></use></svg></button></div></div><nav></nav></nav></div><div class="toc-container"><div class="toc-content"><span class="bold">On this page</span><div id="eed4d2a0bfd64539bb9df78095dec881"></div></div></div><div class="body-wrapper"><div class="main-content"><div class="main-wrapper"><section id="source-page" class="source-page"><header><h1 id="title" class="has-anchor">index.js</h1></header><article><pre class="prettyprint source lang-js"><code>import collectConnections from './lib/collect-connections.js'
4
- import collectDrivers from './lib/collect-drivers.js'
5
- import collectFeatures from './lib/collect-features.js'
6
- import collectSchemas from './lib/collect-schemas.js'
7
- import memDbStart from './lib/mem-db/start.js'
8
- import memDbInstantiate from './lib/mem-db/instantiate.js'
9
- import nql from '@tryghost/nql'
10
- import path from 'path'
3
+ <![endif]--><script src="scripts/third-party/hljs.js" defer="defer"></script><script src="scripts/third-party/hljs-line-num.js" defer="defer"></script><script src="scripts/third-party/popper.js" defer="defer"></script><script src="scripts/third-party/tippy.js" defer="defer"></script><script src="scripts/third-party/tocbot.min.js"></script><script>var baseURL="/",locationPathname="";baseURL=(locationPathname=document.location.pathname).substr(0,locationPathname.lastIndexOf("/")+1)</script><link rel="stylesheet" href="styles/clean-jsdoc-theme.min.css"><svg aria-hidden="true" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="display:none"><defs><symbol id="copy-icon" viewbox="0 0 488.3 488.3"><g><path d="M314.25,85.4h-227c-21.3,0-38.6,17.3-38.6,38.6v325.7c0,21.3,17.3,38.6,38.6,38.6h227c21.3,0,38.6-17.3,38.6-38.6V124 C352.75,102.7,335.45,85.4,314.25,85.4z M325.75,449.6c0,6.4-5.2,11.6-11.6,11.6h-227c-6.4,0-11.6-5.2-11.6-11.6V124 c0-6.4,5.2-11.6,11.6-11.6h227c6.4,0,11.6,5.2,11.6,11.6V449.6z"/><path d="M401.05,0h-227c-21.3,0-38.6,17.3-38.6,38.6c0,7.5,6,13.5,13.5,13.5s13.5-6,13.5-13.5c0-6.4,5.2-11.6,11.6-11.6h227 c6.4,0,11.6,5.2,11.6,11.6v325.7c0,6.4-5.2,11.6-11.6,11.6c-7.5,0-13.5,6-13.5,13.5s6,13.5,13.5,13.5c21.3,0,38.6-17.3,38.6-38.6 V38.6C439.65,17.3,422.35,0,401.05,0z"/></g></symbol><symbol id="search-icon" viewBox="0 0 512 512"><g><g><path d="M225.474,0C101.151,0,0,101.151,0,225.474c0,124.33,101.151,225.474,225.474,225.474 c124.33,0,225.474-101.144,225.474-225.474C450.948,101.151,349.804,0,225.474,0z M225.474,409.323 c-101.373,0-183.848-82.475-183.848-183.848S124.101,41.626,225.474,41.626s183.848,82.475,183.848,183.848 S326.847,409.323,225.474,409.323z"/></g></g><g><g><path d="M505.902,476.472L386.574,357.144c-8.131-8.131-21.299-8.131-29.43,0c-8.131,8.124-8.131,21.306,0,29.43l119.328,119.328 c4.065,4.065,9.387,6.098,14.715,6.098c5.321,0,10.649-2.033,14.715-6.098C514.033,497.778,514.033,484.596,505.902,476.472z"/></g></g></symbol><symbol id="font-size-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.246 15H4.754l-2 5H.6L7 4h2l6.4 16h-2.154l-2-5zm-.8-2L8 6.885 5.554 13h4.892zM21 12.535V12h2v8h-2v-.535a4 4 0 1 1 0-6.93zM19 18a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></symbol><symbol id="add-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"/></symbol><symbol id="minus-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M5 11h14v2H5z"/></symbol><symbol id="dark-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 7a7 7 0 0 0 12 4.9v.1c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2h.1A6.979 6.979 0 0 0 10 7zm-6 5a8 8 0 0 0 15.062 3.762A9 9 0 0 1 8.238 4.938 7.999 7.999 0 0 0 4 12z"/></symbol><symbol id="light-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05 3.515 4.93zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414-2.121-2.121zm2.121-14.85l1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414 2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z"/></symbol><symbol id="reset-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M18.537 19.567A9.961 9.961 0 0 1 12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10c0 2.136-.67 4.116-1.81 5.74L17 12h3a8 8 0 1 0-2.46 5.772l.997 1.795z"/></symbol><symbol id="down-icon" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M12.7803 6.21967C13.0732 6.51256 13.0732 6.98744 12.7803 7.28033L8.53033 11.5303C8.23744 11.8232 7.76256 11.8232 7.46967 11.5303L3.21967 7.28033C2.92678 6.98744 2.92678 6.51256 3.21967 6.21967C3.51256 5.92678 3.98744 5.92678 4.28033 6.21967L8 9.93934L11.7197 6.21967C12.0126 5.92678 12.4874 5.92678 12.7803 6.21967Z"></path></symbol><symbol id="codepen-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M16.5 13.202L13 15.535v3.596L19.197 15 16.5 13.202zM14.697 12L12 10.202 9.303 12 12 13.798 14.697 12zM20 10.869L18.303 12 20 13.131V10.87zM19.197 9L13 4.869v3.596l3.5 2.333L19.197 9zM7.5 10.798L11 8.465V4.869L4.803 9 7.5 10.798zM4.803 15L11 19.131v-3.596l-3.5-2.333L4.803 15zM4 13.131L5.697 12 4 10.869v2.262zM2 9a1 1 0 0 1 .445-.832l9-6a1 1 0 0 1 1.11 0l9 6A1 1 0 0 1 22 9v6a1 1 0 0 1-.445.832l-9 6a1 1 0 0 1-1.11 0l-9-6A1 1 0 0 1 2 15V9z"/></symbol><symbol id="close-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z"/></symbol><symbol id="menu-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M3 4h18v2H3V4zm0 7h18v2H3v-2zm0 7h18v2H3v-2z"/></symbol></defs></svg></head><body data-theme="light"><div class="sidebar-container"><div class="sidebar" id="sidebar"><a href="/" class="sidebar-title sidebar-title-anchor">Dobo API</a><div class="sidebar-items-container"><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-classes"><div>Classes</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="Dobo.html">Dobo</a></div><div class="sidebar-section-children"><a href="DoboAction.html">DoboAction</a></div><div class="sidebar-section-children"><a href="DoboAdapter.html">DoboAdapter</a></div><div class="sidebar-section-children"><a href="DoboConnection.html">DoboConnection</a></div><div class="sidebar-section-children"><a href="DoboFeature.html">DoboFeature</a></div><div class="sidebar-section-children"><a href="DoboModel.html">DoboModel</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-modules"><div>Modules</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="module-Helper.html">Helper</a></div><div class="sidebar-section-children"><a href="module-Helper_Model.html">Helper/Model</a></div><div class="sidebar-section-children"><a href="module-Hook.html">Hook</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-externals"><div>Externals</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="external-Tools.html">Tools</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-global"><div>Global</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="global.html#actionFactory">actionFactory</a></div><div class="sidebar-section-children"><a href="global.html#adapterFactory">adapterFactory</a></div><div class="sidebar-section-children"><a href="global.html#connectionFactory">connectionFactory</a></div><div class="sidebar-section-children"><a href="global.html#featureFactory">featureFactory</a></div><div class="sidebar-section-children"><a href="global.html#modelFactory">modelFactory</a></div><div class="sidebar-section-children"><a href="global.html#pluginFactory">pluginFactory</a></div></div></div></div></div><div class="navbar-container" id="VuAckcnZhf"><nav class="navbar"><div class="navbar-left-items"><div class="navbar-item"><a id="" href="https://www.npmjs.com/package/dobo" target="">NPM</a></div><div class="navbar-item"><a id="" href="https://github.com/ardhi/dobo" target="">Github</a></div><div class="navbar-item"><a id="" href="https://dobo.bajo.app/" target="">Dobo</a></div><div class="navbar-item"><a id="" href="https://bajo.app/" target="">Bajo</a></div></div><div class="navbar-right-items"><div class="navbar-right-item"><button class="icon-button search-button" aria-label="open-search"><svg><use xlink:href="#search-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button theme-toggle" aria-label="toggle-theme"><svg><use class="theme-svg-use" xlink:href="#dark-theme-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button font-size" aria-label="change-font-size"><svg><use xlink:href="#font-size-icon"></use></svg></button></div></div><nav></nav></nav></div><div class="toc-container"><div class="toc-content"><span class="bold">On this page</span><div id="eed4d2a0bfd64539bb9df78095dec881"></div></div></div><div class="body-wrapper"><div class="main-content"><div class="main-wrapper"><section id="source-page" class="source-page"><header><h1 id="title" class="has-anchor">index.js</h1></header><article><pre class="prettyprint source lang-js"><code>import nql from '@tryghost/nql'
4
+ import {
5
+ collectConnections, collectAdapters, collectFeatures, collectModels
6
+ } from './lib/helper.js'
11
7
 
12
8
  /**
13
- * @typedef {string} TRecordSortKey
14
- */
15
-
16
- /**
17
- * Key value pairs used as sort information:
18
- * - Key represent model's field name
19
- * - value represent its sort order: ```1``` for ascending order, and ```-1``` for descending order
20
- *
21
- * Example: to sort by firstName (ascending) and lastName (descending)
22
- * ```javascript
23
- * const sort = {
24
- * firstName: 1,
25
- * lastName: -1
26
- * }
27
- * ```
28
- *
29
- * @typedef {Object.&lt;string, TRecordSortKey>} TRecordSort
30
- */
31
-
32
- /**
33
- * @typedef {Object} TRecordPagination
34
- * @property {number} limit - Number of records per page
35
- * @property {number} page - Page number
36
- * @property {number} skip - Records to skip
37
- * @property {TRecordSort} sort - Sort order
38
- */
39
-
40
- /**
41
- * @typedef {Object} TPropType
9
+ * @typedef {Object} TPropertyType
10
+ * @memberof DoboModel
42
11
  * @property {Object} integer
43
12
  * @property {string} [integer.validator=number]
44
13
  * @property {Object} smallint
@@ -68,83 +37,111 @@ import path from 'path'
68
37
  * @property {Object} object={}
69
38
  * @property {Object} array={}
70
39
  */
71
- const propType = {
40
+ const propertyType = {
72
41
  integer: {
73
- validator: 'number'
42
+ validator: 'number',
43
+ rules: []
74
44
  },
75
45
  smallint: {
76
- validator: 'number'
46
+ validator: 'number',
47
+ rules: []
77
48
  },
78
49
  text: {
79
50
  validator: 'string',
80
51
  textType: 'text',
81
- values: ['text', 'mediumtext', 'longtext']
52
+ // values: ['text', 'mediumtext', 'longtext'], TODO: should check against textType, NOT value
53
+ rules: []
82
54
  },
83
55
  string: {
84
56
  validator: 'string',
85
- maxLength: 255,
86
- minLength: 0
57
+ maxLength: 50,
58
+ minLength: 0,
59
+ rules: []
87
60
  },
88
61
  float: {
89
- validator: 'number'
62
+ validator: 'number',
63
+ rules: []
90
64
  },
91
65
  double: {
92
- validator: 'number'
66
+ validator: 'number',
67
+ rules: []
93
68
  },
94
69
  boolean: {
95
- validator: 'boolean'
96
- },
97
- date: {
98
- validator: 'date'
70
+ validator: 'boolean',
71
+ rules: []
99
72
  },
100
73
  datetime: {
101
- validator: 'date'
102
- },
103
- time: {
104
- validator: 'date'
74
+ validator: 'date',
75
+ rules: []
105
76
  },
106
- timestamp: {
107
- validator: 'timestamp'
77
+ object: {
78
+ validator: 'object',
79
+ rules: []
108
80
  },
109
- object: {},
110
- array: {}
81
+ array: {
82
+ validator: 'array',
83
+ rules: []
84
+ }
111
85
  }
112
86
 
87
+ const commonPropertyTypes = ['name', 'type', 'required', 'rules', 'validator', 'ref', 'default', 'values', 'rulesMsg', 'immutable', 'feature', 'format', 'getValue', 'virtual']
88
+
113
89
  /**
114
90
  * Plugin factory
115
91
  *
92
+ * @name pluginFactory
93
+ * @async
94
+ * @method
116
95
  * @param {string} pkgName - NPM package name
117
96
  * @returns {class}
118
97
  */
119
98
  async function factory (pkgName) {
120
99
  const me = this
100
+ const { breakNsPath } = this.app.bajo
101
+
102
+ const { find, filter, isString, map, pick, groupBy, isEmpty } = this.app.lib._
121
103
 
122
104
  /**
123
105
  * Dobo Database Framework for {@link https://github.com/ardhi/bajo|Bajo}.
124
106
  *
125
- * See {@tutorial ecosystem} for available drivers &amp; tools
107
+ * See {@tutorial ecosystem} for available adapters &amp; tools
126
108
  *
127
109
  * @class
128
110
  */
129
- class Dobo extends this.app.pluginClass.base {
111
+ class Dobo extends this.app.baseClass.Base {
130
112
  /**
131
- * @constant {string}
113
+ * @constant {string[]}
132
114
  * @memberof Dobo
133
- * @default 'db'
115
+ * @default ['count', 'avg', 'min', 'max', 'sum']
134
116
  */
135
- static alias = 'db'
117
+ static aggregateTypes = ['count', 'avg', 'min', 'max', 'sum']
136
118
 
137
119
  /**
138
120
  * @constant {string[]}
139
121
  * @memberof Dobo
140
- * @default ['count', 'avg', 'min', 'max', 'sum']
122
+ * @default ['string', 'integer', 'smallint']
141
123
  */
142
- static aggregateTypes = ['count', 'avg', 'min', 'max', 'sum']
124
+ static idTypes = ['string', 'integer', 'smallint']
125
+
126
+ /**
127
+ * @constant {string[]}
128
+ * @memberof Dobo
129
+ * @default ['daily', 'monthly', 'annually']
130
+ */
131
+ static histogramTypes = ['daily', 'monthly', 'yearly']
132
+
133
+ /**
134
+ * @constant {TPropertyType}
135
+ * @memberof Dobo
136
+ */
137
+ static propertyType = propertyType
138
+
143
139
  /**
144
- * @constant {TPropType}
140
+ * @constant {string[]}
145
141
  * @memberof Dobo
142
+ * @default ['index', 'unique', 'primary', 'fulltext']
146
143
  */
147
- static propType = propType
144
+ static indexTypes = ['index', 'unique', 'primary', 'fulltext']
148
145
 
149
146
  constructor () {
150
147
  super(pkgName, me.app)
@@ -156,32 +153,23 @@ async function factory (pkgName) {
156
153
  allowUnknown: true
157
154
  },
158
155
  default: {
159
- property: {
160
- text: {
161
- textType: 'text'
162
- },
163
- string: {
164
- length: 50
165
- }
166
- },
167
156
  filter: {
168
- limit: 25,
169
- maxLimit: 200,
170
- hardLimit: 10000,
171
- sort: ['dt:-1', 'updatedAt:-1', 'updated_at:-1', 'createdAt:-1', 'createdAt:-1', 'ts:-1', 'username', 'name']
157
+ limit: 25, // num of records per page
158
+ maxLimit: 200, // max num of records per page
159
+ maxPage: 400, // max allowed page number
160
+ sort: ['dt:-1', 'updatedAt:-1', 'createdAt:-1', 'ts:-1', 'username', 'name']
172
161
  },
173
- idField: {
174
- type: 'string',
175
- maxLength: 50,
176
- required: true,
177
- index: { type: 'primary' }
162
+ cache: {
163
+ ttlDur: '10s'
164
+ },
165
+ hardCap: 10000, // max returned records
166
+ warnings: true,
167
+ attachment: {
168
+ thumbSizes: ['s', 'm', 'l']
178
169
  }
179
170
  },
180
171
  memDb: {
181
- createDefConnAtStart: true,
182
- persistence: {
183
- syncPeriodDur: '1s'
184
- }
172
+ persistenceDur: '1s'
185
173
  },
186
174
  applet: {
187
175
  confirmation: false
@@ -191,7 +179,7 @@ async function factory (pkgName) {
191
179
  /**
192
180
  * @type {Object[]}
193
181
  */
194
- this.drivers = []
182
+ this.adapters = []
195
183
 
196
184
  /**
197
185
  * @type {Object[]}
@@ -206,39 +194,57 @@ async function factory (pkgName) {
206
194
  /**
207
195
  * @type {Object[]}
208
196
  */
209
- this.schemas = []
197
+ this.models = []
198
+ }
199
+
200
+ /**
201
+ * Get allowed property keys by field type
202
+ *
203
+ * @param {string} type
204
+ * @returns {string[]}
205
+ */
206
+ getPropertyKeysByType = (type) => {
207
+ const keys = [...commonPropertyTypes]
208
+ if (['string'].includes(type)) keys.push('minLength', 'maxLength', 'values')
209
+ if (['text'].includes(type)) keys.push('textType')
210
+ if (['smallint', 'integer'].includes(type)) keys.push('autoInc', 'values')
211
+ if (['float', 'double'].includes(type)) keys.push('values')
212
+ return keys
213
+ }
214
+
215
+ /**
216
+ * Get all allowed property keys
217
+ *
218
+ * @returns {string[]}
219
+ */
220
+
221
+ getAllPropertyKeys = (adapter) => {
222
+ const { uniq, isEmpty } = this.app.lib._
223
+ const keys = [...commonPropertyTypes]
224
+ for (const type in propertyType) {
225
+ keys.push(...Object.keys(propertyType[type]))
226
+ }
227
+ if (adapter &amp;&amp; !isEmpty(adapter.constructor.propertyKeys)) keys.push(...adapter.constructor.propertyKeys)
228
+ return uniq(keys)
210
229
  }
211
230
 
212
231
  /**
213
232
  * Initialize plugin and performing the following tasks:
214
- * - {@link module:Lib.collectDrivers|Collecting all drivers}
233
+ * - {@link module:Lib.collectAdapters|Collecting all adapters}
215
234
  * - {@link module:Lib.collectConnections|Collecting all connections}
216
235
  * - {@link module:Lib.collectFeatures|Collecting all features}
217
- * - {@link module:Lib.collectSchemas|Collecting all schemas}
236
+ * - {@link module:Lib.collectModels|Collecting all models}
218
237
  * @method
219
238
  * @async
220
239
  */
221
240
  init = async () => {
222
- const { buildCollections } = this.app.bajo
223
241
  const { fs } = this.app.lib
224
- const checkType = async (item, items) => {
225
- const { filter } = this.app.lib._
226
- const existing = filter(items, { type: 'dobo:memory' })
227
- if (existing.length > 1) this.fatal('onlyOneConnType%s', item.type)
228
- }
229
-
230
- fs.ensureDirSync(`${this.dir.data}/attachment`)
231
- await collectDrivers.call(this)
232
- if (this.config.memDb.createDefConnAtStart) {
233
- this.config.connections.push({
234
- type: 'dobo:memory',
235
- name: 'memory'
236
- })
237
- }
238
- this.connections = await buildCollections({ ns: this.ns, container: 'connections', handler: collectConnections, dupChecks: ['name', checkType] })
239
- if (this.connections.length === 0) this.log.warn('notFound%s', this.t('connection'))
242
+ await collectAdapters.call(this)
243
+ await collectConnections.call(this)
240
244
  await collectFeatures.call(this)
241
- await collectSchemas.call(this)
245
+ await collectModels.call(this)
246
+ const attDir = `${this.app.getPluginDataDir('dobo')}/attachment`
247
+ fs.ensureDirSync(attDir)
242
248
  }
243
249
 
244
250
  /**
@@ -247,327 +253,375 @@ async function factory (pkgName) {
247
253
  * @method
248
254
  * @async
249
255
  * @param {(string|Array)} [conns=all] - Which connections should be run on start
250
- * @param {boolean} [noRebuild=false] - Set ```true``` to ALWAYS rebuild model on start. Yes, only set it to ```true``` if you REALLY know what you're doing!!!
256
+ * @param {boolean} [noRebuild=true] - Set ```false``` to not rebuild model on start. Yes, only set it to ```false``` if you REALLY know what you're doing!!!
251
257
  */
252
258
  start = async (conns = 'all', noRebuild = true) => {
253
- const { importModule, breakNsPath } = this.app.bajo
254
- const { find, filter, isString, map } = this.app.lib._
255
259
  if (conns === 'all') conns = this.connections
256
260
  else if (isString(conns)) conns = filter(this.connections, { name: conns })
257
261
  else conns = map(conns, c => find(this.connections, { name: c }))
258
- for (const c of conns) {
259
- const { ns } = breakNsPath(c.type)
260
- const schemas = filter(this.schemas, { connection: c.name })
261
- const mod = c.type === 'dobo:memory' ? memDbInstantiate : await importModule(`${ns}:/extend/${this.ns}/boot/instantiate.js`)
262
- await mod.call(this.app[ns], { connection: c, noRebuild, schemas })
263
- this.log.trace('driverInstantiated%s%s', c.driver, c.name)
262
+ this.log.debug('dbInit')
263
+ for (const connection of conns) {
264
+ await connection.connect(noRebuild)
265
+ this.log.trace('dbInit%s%s%s', connection.adapter.plugin.ns, connection.adapter.name, connection.name)
264
266
  }
265
- await memDbStart.call(this)
266
267
  }
267
268
 
268
269
  /**
269
- * Pick only fields defined from a record
270
+ * Get connection by name. It returns the first connection named after "{name}"
270
271
  *
271
- * @method
272
- * @async
273
- * @param {Object} [options={}] - Options object
274
- * @param {Object} options.record - Record to pick fields from
275
- * @param {Array} options.fields - Array of field names to be picked
276
- * @param {Object} options.schema - Associated record's schema
277
- * @param {Object} [options.hidden=[]] - Additional fields to be hidden in addition the one defined in schema
278
- * @param {boolean} [options.forceNoHidden] - Force ALL fields to be picked, thus ignoring hidden fields
279
- * @returns {Object}
272
+ * @param {string} name - Connection name
273
+ * @param {boolean} [silent] - If ```true``` and connection is not found, it won't throw error
274
+ * @returns {Adapter} Return connection instance or ```undefined``` if silent is ```true```
280
275
  */
281
- pickRecord = async ({ record, fields, schema = {}, hidden = [], forceNoHidden } = {}) => {
282
- const { isArray, pick, clone, isEmpty, omit } = this.app.lib._
283
- const { dayjs } = this.app.lib
276
+ getConnection = (name, silent) => {
277
+ const conn = find(this.connections, { name })
278
+ if (!conn &amp;&amp; !silent) throw this.error('unknown%s%s', this.t('connection'), name)
279
+ return conn
280
+ }
284
281
 
285
- const transform = async ({ record, schema, hidden = [], forceNoHidden } = {}) => {
286
- if (record._id) {
287
- record.id = record._id
288
- delete record._id
289
- }
290
- const defHidden = [...schema.hidden, ...hidden]
291
- let result = {}
292
- for (const p of schema.properties) {
293
- if (!forceNoHidden &amp;&amp; defHidden.includes(p.name)) continue
294
- result[p.name] = record[p.name] ?? null
295
- if (record[p.name] === null) continue
296
- switch (p.type) {
297
- case 'boolean': result[p.name] = !!result[p.name]; break
298
- case 'time': result[p.name] = dayjs(record[p.name]).format('HH:mm:ss'); break
299
- case 'date': result[p.name] = dayjs(record[p.name]).format('YYYY-MM-DD'); break
300
- case 'datetime': result[p.name] = dayjs(record[p.name]).toISOString(); break
301
- }
302
- }
303
- result = await this.sanitizeBody({ body: result, schema, partial: true, ignoreNull: true })
304
- if (record._rel) result._rel = record._rel
305
- return result
282
+ /**
283
+ * Get adapter by name. It returns the first adapter named after "{name}"
284
+ *
285
+ * Also support NsPath format for those who load the same named adapter but from different provider/plugin.
286
+ *
287
+ * @param {string} name - Adapter name
288
+ * @param {boolean} [silent] - If ```true``` and adapter is not found, it won't throw error
289
+ * @returns {Adapter} Returns adapter instance or ```undefined``` if silent is ```true```
290
+ */
291
+ getAdapter = (name, silent) => {
292
+ const { breakNsPath } = this.app.bajo
293
+ let adapter
294
+ if (!name.includes(':')) {
295
+ adapter = find(this.adapters, { name })
296
+ if (adapter) return adapter
297
+ adapter = filter(this.adapters, d => d.plugin.ns === name)
298
+ if (adapter.length === 1) return adapter[0]
299
+ if (!silent) throw this.error('unknown%s%s', this.t('adapter'), name)
300
+ return
306
301
  }
302
+ const { ns, path } = breakNsPath(name)
303
+ adapter = find(this.adapters, d => d.name === path &amp;&amp; d.plugin.ns === ns)
304
+ if (!adapter &amp;&amp; !silent) throw this.error('unknown%s%s', this.t('adapter'), name)
305
+ return adapter
306
+ }
307
307
 
308
- if (isEmpty(record)) return record
309
- if (hidden.length > 0) record = omit(record, hidden)
310
- if (!isArray(fields)) return await transform.call(this, { record, schema, hidden, forceNoHidden })
311
- const fl = clone(fields)
312
- if (!fl.includes('id')) fl.unshift('id')
313
- if (record._rel) fl.push('_rel')
314
- return pick(await transform.call(this, { record, schema, hidden, forceNoHidden }), fl)
308
+ /**
309
+ * Get feature by name. It returns the first adapter named after "{name}"
310
+ *
311
+ * Also support NsPath format for those who load the same named adapter but from different provider/plugin.
312
+ *
313
+ * @param {string} - Feature name
314
+ * @returns {Feature} Return feature instance
315
+ */
316
+ getFeature = name => {
317
+ if (!name.includes(':')) return find(this.features, { name })
318
+ const { ns, path } = breakNsPath(name)
319
+ const feat = find(this.features, d => d.name === path &amp;&amp; d.plugin.ns === ns)
320
+ if (!feat) throw this.error('unknown%s%s', this.t('feature'), name)
321
+ return feat
315
322
  }
316
323
 
317
324
  /**
318
- * Prepare records pagination:
319
- * - making sure records limit is obeyed
320
- * - making sure page is a positive value
321
- * - if skip is given, recalculate limit to use skip instead of page number
322
- * - Build sort info
325
+ * Get model by name
323
326
  *
324
- * @method
325
- * @async
326
- * @param {Object} [filter={}] - Filter object
327
- * @param {Object} schema - Model's schema
328
- * @param {Object} options - Options
329
- * @returns {TRecordPagination}
327
+ * @param {string} name - Model name
328
+ * @param {boolean} [silent] - If ```true``` and model is not found, it won't throw error
329
+ * @retuns {Model} Returns model instance or ```undefined``` if silent is ```true```
330
330
  */
331
- prepPagination = async (filter = {}, schema, options = {}) => {
332
- const buildPageSkipLimit = (filter) => {
333
- let limit = parseInt(filter.limit) || this.config.default.filter.limit
334
- if (limit === -1) limit = this.config.default.filter.maxLimit
335
- if (limit > this.config.default.filter.maxLimit) limit = this.config.default.filter.maxLimit
336
- if (limit &lt; 1) limit = 1
337
- let page = parseInt(filter.page) || 1
338
- if (page &lt; 1) page = 1
339
- let skip = (page - 1) * limit
340
- if (filter.skip) {
341
- skip = parseInt(filter.skip) || skip
342
- page = undefined
343
- }
344
- if (skip &lt; 0) skip = 0
345
- return { page, skip, limit }
346
- }
331
+ getModel = (name, silent) => {
332
+ const { pascalCase } = this.app.lib.aneka
333
+ let model = find(this.models, { name })
334
+ if (!model) model = find(this.models, { name: pascalCase(name) })
335
+ if (!model &amp;&amp; !silent) throw this.error('unknown%s%s', this.t('model'), name)
336
+ return model
337
+ }
347
338
 
348
- const buildSort = (input, schema, allowSortUnindexed) => {
349
- const { isEmpty, map, each, isPlainObject, isString, trim, keys } = this.app.lib._
350
- let sort
351
- if (schema &amp;&amp; isEmpty(input)) {
352
- const columns = map(schema.properties, 'name')
353
- each(this.config.default.filter.sort, s => {
354
- const [col] = s.split(':')
355
- if (columns.includes(col)) {
356
- input = s
357
- return false
358
- }
359
- })
360
- }
361
- if (!isEmpty(input)) {
362
- if (isPlainObject(input)) sort = input
363
- else if (isString(input)) {
364
- const item = {}
365
- each(input.split('+'), text => {
366
- let [col, dir] = map(trim(text).split(':'), i => trim(i))
367
- dir = (dir ?? '').toUpperCase()
368
- dir = dir === 'DESC' ? -1 : parseInt(dir) || 1
369
- item[col] = dir / Math.abs(dir)
370
- })
371
- sort = item
372
- }
373
- if (schema) {
374
- const items = keys(sort)
375
- each(items, i => {
376
- if (!schema.sortables.includes(i) &amp;&amp; !allowSortUnindexed) throw this.error('sortOnUnindexedField%s%s', i, schema.name)
377
- // if (schema.fullText.fields.includes(i)) throw this.error('Can\'t sort on full-text index: \'%s@%s\'', i, schema.name)
378
- })
379
- }
380
- }
381
- return sort
382
- }
339
+ /**
340
+ * Get all models that bound to connection ```name```
341
+ *
342
+ * @param {string} name - Connection name
343
+ * @returns {Array}
344
+ */
345
+ getModelsByConnection = name => {
346
+ const conn = this.getConnection(name)
347
+ return this.models.filter(s => s.connection.name === conn.name)
348
+ }
383
349
 
384
- const { page, skip, limit } = buildPageSkipLimit.call(this, filter)
385
- let sortInput = filter.sort
386
- try {
387
- sortInput = JSON.parse(sortInput)
388
- } catch (err) {}
389
- const sort = buildSort.call(this, sortInput, schema, options.allowSortUnindexed)
390
- return { limit, page, skip, sort }
391
- }
392
-
393
- buildMatch = ({ input = '', schema, options }) => {
394
- const { isPlainObject, trim } = this.app.lib._
395
- if (isPlainObject(input)) return input
396
- const split = (value, schema) => {
397
- let [field, val] = value.split(':').map(i => i.trim())
398
- if (!val) {
399
- val = field
400
- field = '*'
401
- }
402
- return { field, value: val }
403
- }
404
- input = trim(input)
405
- let items = {}
406
- if (isPlainObject(input)) items = input
407
- else if (input[0] === '{') items = JSON.parse(input)
408
- else {
409
- for (const item of input.split('+').map(i => i.trim())) {
410
- const part = split(item, schema)
411
- if (!items[part.field]) items[part.field] = []
412
- items[part.field].push(...part.value.split(' ').filter(v => ![''].includes(v)))
413
- }
350
+ validationErrorMessage = (err) => {
351
+ let text = err.message
352
+ if (err.details) {
353
+ text += ' -> '
354
+ text += this.app.bajo.join(err.details.map((d, idx) => {
355
+ return `${d.field}@${err.model}: ${d.error} (${d.value})`
356
+ }))
414
357
  }
415
- const matcher = {}
416
- for (const f of schema.fullText.fields) {
417
- const value = []
418
- if (typeof items[f] === 'string') items[f] = [items[f]]
419
- if (Object.prototype.hasOwnProperty.call(items, f)) value.push(...items[f])
420
- matcher[f] = value
358
+ return text
359
+ }
360
+
361
+ /**
362
+ * Sanitize value as a date/time value. Parse/format string using {@link https://day.js.org/docs/en/display/format|dayjs format}
363
+ *
364
+ * @method
365
+ * @memberof Dobo
366
+ * @param {(number|string)} value - Value to sanitize
367
+ * @param {Object} [options={}] - Options object
368
+ * @param {boolean} [options.silent=true] - If ```true``` (default) and value isn't valid, returns empty
369
+ * @param {string} [options.inputFormat] - If provided, parse value using this option
370
+ * @param {string} [options.outputFormat] - If not provided or ```native```, returns Javascript Date. Otherwise returns formatted date/time string
371
+ * @returns {(string|Date)}
372
+ */
373
+ sanitizeDate = (value, { inputFormat, outputFormat, silent = true } = {}) => {
374
+ const { dayjs } = this.app.lib
375
+ if (value === 0) return null
376
+ const dt = dayjs(value, inputFormat)
377
+ if (!dt.isValid()) {
378
+ if (silent) return null
379
+ throw this.error('invalidDate')
421
380
  }
422
- if (Object.prototype.hasOwnProperty.call(items, '*')) matcher['*'] = items['*']
423
- return matcher
381
+ if (outputFormat === 'native' || !outputFormat) return dt.toDate()
382
+ return dt.format(outputFormat)
383
+ }
384
+
385
+ sanitizeFloat = (value, { strict = false } = {}) => {
386
+ const { isNumber } = this.app.lib._
387
+ if (isNumber(value)) return value
388
+ if (strict) return Number(value)
389
+ return parseFloat(value) || null
390
+ }
391
+
392
+ sanitizeInt = (value, { strict = false } = {}) => {
393
+ const { isNumber } = this.app.lib._
394
+ if (isNumber(value)) return value
395
+ if (strict) return Number(value)
396
+ return parseInt(value) || null
424
397
  }
425
398
 
426
- buildQuery = ({ filter, schema, options = {} } = {}) => {
427
- const { trim, find, isString, isPlainObject } = this.app.lib._
428
- let query = {}
429
- if (isString(filter.query)) {
399
+ sanitizeObject = (value, { strict = false, action, model } = {}) => {
400
+ const { isString } = this.app.lib._
401
+ let result = null
402
+ if (isString(value)) {
430
403
  try {
431
- filter.query = trim(filter.query)
432
- filter.orgQuery = filter.query
433
- if (trim(filter.query).startsWith('{')) query = JSON.parse(filter.query)
434
- else if (filter.query.includes(':')) query = nql(filter.query).parse()
435
- else {
436
- const fields = schema.sortables.filter(f => {
437
- const field = find(schema.properties, { name: f, type: 'string' })
438
- return !!field
439
- })
440
- const parts = fields.map(f => {
441
- if (filter.query[0] === '*') return `${f}:~$'${filter.query.replaceAll('*', '')}'`
442
- if (filter.query[filter.length - 1] === '*') return `${f}:~^'${filter.query.replaceAll('*', '')}'`
443
- return `${f}:~'${filter.query.replaceAll('*', '')}'`
444
- })
445
- if (parts.length === 1) query = nql(parts[0]).parse()
446
- else if (parts.length > 1) query = nql(parts.join(',')).parse()
447
- }
404
+ result = JSON.parse(value)
448
405
  } catch (err) {
449
- this.error('invalidQuery', { orgMessage: err.message })
406
+ if (strict) throw err
450
407
  }
451
- } else if (isPlainObject(filter.query)) query = filter.query
452
- return this.sanitizeQuery(query, schema)
408
+ } else {
409
+ try {
410
+ result = JSON.parse(JSON.stringify(value))
411
+ } catch (err) {
412
+ if (strict) throw err
413
+ }
414
+ }
415
+ return result
453
416
  }
454
417
 
455
- sanitizeQuery = (query, schema, parent) => {
456
- const { cloneDeep, isPlainObject, isArray, find } = this.app.lib._
457
- const { isSet } = this.app.lib.aneka
418
+ sanitizeBoolean = (value) => {
419
+ return value === null ? null : (['true', true].includes(value))
420
+ }
421
+
422
+ sanitizeTimestamp = (value) => {
423
+ const { isNumber } = this.app.lib._
458
424
  const { dayjs } = this.app.lib
459
- const obj = cloneDeep(query)
460
- const keys = Object.keys(obj)
461
- const sanitize = (key, val, p) => {
462
- if (!isSet(val)) return val
463
- const prop = find(schema.properties, { name: key.startsWith('$') ? p : key })
464
- if (!prop) return val
465
- if (['datetime', 'date', 'time'].includes(prop.type)) {
466
- const dt = dayjs(val)
467
- return dt.isValid() ? dt.toDate() : val
468
- } else if (['smallint', 'integer'].includes(prop.type)) return parseInt(val) || val
469
- else if (['float', 'double'].includes(prop.type)) return parseFloat(val) || val
470
- else if (['boolean'].includes(prop.type)) return !!val
471
- return val
425
+ if (!isNumber(value)) return -1
426
+ const dt = dayjs.unix(value)
427
+ return dt.isValid() ? dt.unix() : -1
428
+ }
429
+
430
+ sanitizeString = (value) => {
431
+ return value + ''
432
+ }
433
+
434
+ sanitizeByType = (value, type, opts = {}) => {
435
+ const { isNaN } = this.app.lib._
436
+ let result = value
437
+ if (['object', 'array'].includes(type)) result = this.sanitizeObject(value, opts)
438
+ else if (type === 'boolean') result = this.sanitizeBoolean(value)
439
+ else if (['float', 'double'].includes(type)) result = this.sanitizeFloat(value, opts)
440
+ else if (['integer', 'smallint'].includes(type)) result = this.sanitizeInt(value, opts)
441
+ else if (['string', 'text'].includes(type)) result = this.sanitizeString(result, opts)
442
+ else if (['datetime'].includes(type)) result = this.sanitizeDate(result, opts)
443
+ if (!opts.strict &amp;&amp; isNaN(result)) result = null
444
+ return result
445
+ }
446
+
447
+ _calcStats = (items, field, aggregates) => {
448
+ const { generateId, isSet } = this.app.lib.aneka
449
+ const result = { id: generateId, count: 0, avg: null, min: null, max: null }
450
+ let sum = 0
451
+ for (const item of items) {
452
+ const value = Number(item[field]) ?? 0
453
+ if (aggregates.includes('count')) result.count++
454
+ if (aggregates.includes('avg')) sum = sum + value
455
+ if (aggregates.includes('min') &amp;&amp; (!isSet(result.min) || value &lt; result.min)) result.min = value
456
+ if (aggregates.includes('max') &amp;&amp; (!isSet(result.max) || value > result.max)) result.max = value
472
457
  }
473
- keys.forEach(k => {
474
- const v = obj[k]
475
- if (isPlainObject(v)) obj[k] = this.sanitizeQuery(v, schema, k)
476
- else if (isArray(v)) {
477
- v.forEach((i, idx) => {
478
- if (isPlainObject(i)) obj[k][idx] = this.sanitizeQuery(i, schema, k)
479
- })
480
- } else obj[k] = sanitize(k, v, parent)
481
- })
482
- return obj
458
+ result.avg = sum / items.length
459
+ return result
483
460
  }
484
461
 
485
- validationErrorMessage = (err) => {
486
- let text = err.message
487
- if (err.details) {
488
- text += ' -> '
489
- text += this.app.bajo.join(err.details.map((d, idx) => {
490
- return `${d.field}@${err.model}: ${d.error} (${d.value})`
491
- }))
462
+ calcAggregate = ({ data = [], group = '', field = '', aggregates = ['count'] } = {}) => {
463
+ this.checkAggregateParams({ group, field, aggregates })
464
+ const { pick, groupBy } = this.app.lib._
465
+ const grouped = groupBy(data, group)
466
+ const all = []
467
+ for (const key in grouped) {
468
+ const items = grouped[key]
469
+ const result = this._calcStats(items, field, aggregates)
470
+ result[group] = key
471
+ all.push(pick(result, ['id', group, ...aggregates]))
492
472
  }
493
- return text
473
+ return all
494
474
  }
495
475
 
496
- getConnection = (name) => {
497
- const { find } = this.app.lib._
498
- return find(this.connections, { name })
476
+ calcHistogram = ({ data = [], type = '', group = '', field = '', aggregates = ['count'] } = {}) => {
477
+ this.checkHistogramParams({ group, field, type, aggregates })
478
+ const { dayjs } = this.app.lib
479
+ const pattern = { daily: ['YYYY-MM-DD', 'date'], monthly: ['YYYY-MM', 'month'], yearly: ['YYYY', 'year'] }
480
+ for (const d of data) {
481
+ d._group = dayjs(d[group]).format(pattern[type][0])
482
+ }
483
+ const grouped = groupBy(data, '_group')
484
+ const all = []
485
+ for (const key in grouped) {
486
+ const items = grouped[key]
487
+ const result = this._calcStats(items, field, aggregates)
488
+ const title = pattern[type][1]
489
+ result[title] = key
490
+ all.push(pick(result, ['id', title, ...aggregates]))
491
+ }
492
+ return all
499
493
  }
500
494
 
501
- getInfo = (name) => {
502
- const { breakNsPath } = this.app.bajo
503
- const { find, map, isEmpty } = this.app.lib._
504
- const schema = this.getSchema(name)
505
- const conn = this.getConnection(schema.connection)
506
- let { ns, path: type } = breakNsPath(conn.type)
507
- if (isEmpty(type)) type = conn.type
508
- const driver = find(this.drivers, { type, ns, driver: conn.driver })
509
- const instance = find(this.app[driver.ns].instances, { name: schema.connection })
510
- const opts = conn.type === 'mssql' ? { includeTriggerModifications: true } : undefined
511
- const returning = [map(schema.properties, 'name'), opts]
512
- return { instance, driver, connection: conn, returning, schema }
513
- }
514
-
515
- getSchema = (input, cloned = true) => {
516
- const { find, isPlainObject, cloneDeep } = this.app.lib._
517
- const { pascalCase } = this.app.lib.aneka
518
- let name = isPlainObject(input) ? input.name : input
519
- name = pascalCase(name)
520
- const schema = find(this.schemas, { name })
521
- if (!schema) throw this.error('unknownModelSchema%s', name)
522
- return cloned ? cloneDeep(schema) : schema
495
+ checkAggregateParams = (params = {}) => {
496
+ let { group, field, aggregates } = params
497
+ if (isString(aggregates)) aggregates = [aggregates]
498
+ params.aggregates = aggregates
499
+ if (isEmpty(group)) throw this.error('fieldGroupMissing')
500
+ if (isEmpty(field)) throw this.error('fieldCalcMissing')
501
+ for (const agg of aggregates) {
502
+ if (!this.constructor.aggregateTypes.includes(agg)) throw this.error('unsupportedAggregateType%s', agg)
503
+ }
523
504
  }
524
505
 
525
- getField = (name, model) => {
526
- const { getInfo } = this.app.dobo
527
- const { find } = this.app.lib._
528
- const { schema } = getInfo(model)
506
+ checkHistogramParams = (params = {}) => {
507
+ this.checkAggregateParams(params)
508
+ const { type } = params
509
+ if (isEmpty(type)) throw this.error('histogramTypeMissing')
510
+ if (!this.constructor.histogramTypes.includes(type)) throw this.error('unsupportedHistogramType%s', type)
511
+ }
529
512
 
530
- return find(schema.properties, { name })
513
+ runModelHook = async (model, hookName, ...args) => {
514
+ const { orderBy } = this.app.lib._
515
+ const hooks = orderBy(model.hooks.filter(hook => hook.name === hookName), ['level'])
516
+ for (const hook of hooks) {
517
+ if (hook.noWait) hook.handler.call(model, ...args)
518
+ else await hook.handler.call(model, ...args)
519
+ }
531
520
  }
532
521
 
533
- hasField = (name, model) => {
534
- return !!this.getField(name, model)
522
+ getDefaultValues = (options = {}) => {
523
+ const { defaultsDeep } = this.app.lib.aneka
524
+ const key = 'default.filter'
525
+ let config = this.getConfig(key)
526
+ config.hardCap = this.config.default.hardCap
527
+ config.warnings = this.config.default.warnings
528
+ if (options.req) config = defaultsDeep({}, options.req.getSetting('dobo:default'), config)
529
+ const { limit, maxLimit, maxPage, hardCap, warnings } = config
530
+ const t = options.req ? options.req.t : this.t
531
+ return { limit, maxLimit, hardCap, maxPage, warnings, t }
535
532
  }
536
533
 
537
- getMemdbStorage = (name, fields = []) => {
538
- const { map, pick } = this.app.lib._
539
- const all = this.memDb.storage[name] ?? []
540
- if (fields.length === 0) return all
541
- return map(all, item => pick(item, fields))
534
+ handleLastPage = (params = {}, options = {}) => {
535
+ const { count, limit, page } = params
536
+ const { hardCap } = this.getDefaultValues(options)
537
+ if (count > hardCap) {
538
+ const lastPage = Math.floor(hardCap / limit)
539
+ if (page > lastPage) {
540
+ const warnings = [this.t('hardCapWarning%s%s', count, hardCap)]
541
+ return { data: [], count: hardCap, warnings }
542
+ }
543
+ }
542
544
  }
543
545
 
544
- listAttachments = async ({ model, id = '*', field = '*', file = '*' } = {}, { uriEncoded = true } = {}) => {
545
- const { map, kebabCase } = this.app.lib._
546
- const { pascalCase } = this.app.lib.aneka
547
- const { importPkg, getPluginDataDir } = this.app.bajo
548
- const mime = await importPkg('waibu:mime')
549
- const { fastGlob } = this.app.lib
550
- const root = `${getPluginDataDir('dobo')}/attachment`
551
- model = pascalCase(model)
552
- let pattern = `${root}/${model}/${id}/${field}/${file}`
553
- if (uriEncoded) pattern = pattern.split('/').map(p => decodeURI(p)).join('/')
554
- return map(await fastGlob(pattern), f => {
555
- const mimeType = mime.getType(path.extname(f)) ?? ''
556
- const fullPath = f.replace(root, '')
557
- const row = {
558
- file: f,
559
- fileName: path.basename(fullPath),
560
- fullPath,
561
- mimeType,
562
- params: { model, id, field, file }
546
+ parseNql = (text) => {
547
+ const sanitized = text.split('+').map(item => {
548
+ const [key, ...rest] = item.split(':').map(i => i.trim())
549
+ let value = rest.join(':')
550
+ const neg = value[1] === '-' ? '-' : ''
551
+ if ((value[0] === '{' || value[1] === '{') &amp;&amp; value[value.length - 1] === '}') {
552
+ if (value[0] === '-') value = value.slice(1)
553
+ const items = value.slice(1, -1).split(',')
554
+ value = `${neg}>=${items[0]}+${key}:${neg}&lt;=${items[1]}`
563
555
  }
564
- if (this.app.waibuMpa) {
565
- const { routePath } = this.app.waibu
566
- const [, _model, _id, _field, _file] = fullPath.split('/')
567
- row.url = routePath(`dobo:/attachment/${kebabCase(_model)}/${_id}/${_field}/${_file}`)
556
+ return `${key}:${value}`
557
+ }).join('+')
558
+ return nql(sanitized).parse()
559
+ }
560
+
561
+ parseAny = (query, model) => {
562
+ const { isEmpty } = this.app.lib._
563
+ let q = {}
564
+ if (!model) throw this.error('invalidQuery')
565
+ let scanables = [...model.scanables]
566
+ if (scanables.length === 0) scanables = [...model.sortables]
567
+ const fields = scanables.filter(f => {
568
+ const field = find(model.properties, { name: f, type: 'string' })
569
+ return !!field
570
+ })
571
+ const parts = fields.map(f => {
572
+ if (query[0] === '*') return `${f}:~$'${query.replaceAll('*', '')}'`
573
+ if (query[query.length - 1] === '*') return `${f}:~^'${query.replaceAll('*', '')}'`
574
+ return `${f}:~'${query.replaceAll('*', '')}'`
575
+ })
576
+ if (parts.length === 1) q = this.parseNql(parts[0])
577
+ else if (parts.length > 1) q = this.parseNql(parts.join(','))
578
+ if (isEmpty(q)) throw this.error('invalidQuery')
579
+ return q
580
+ }
581
+
582
+ parseQuery = (query, model, silent = true) => {
583
+ const { isEmpty, isPlainObject, trim } = this.app.lib._
584
+ let result = {}
585
+ if (isPlainObject(query)) result = query
586
+ else {
587
+ query = trim(query)
588
+ if (isEmpty(query)) return result
589
+ try {
590
+ if (query.startsWith('{')) result = JSON.parse(query)
591
+ else if (query.includes(':')) result = this.parseNql(query)
592
+ else result = this.parseAny(query, model)
593
+ } catch (err) {
594
+ if (silent) return {}
595
+ throw err
568
596
  }
569
- return row
597
+ }
598
+ let strQ = this.replaceRegexInJson(result)
599
+ if (model &amp;&amp; model.adapter.idField.name !== 'id') strQ = strQ.replaceAll('"id"', `"${model.adapter.idField.name}"`)
600
+ try {
601
+ result = this.reviveRegexInJson(strQ)
602
+ } catch (err) {}
603
+ return result
604
+ }
605
+
606
+ replaceRegexInJson = (input = {}, returnString = true) => {
607
+ const { isString } = this.app.lib._
608
+ if (isString(input)) input = JSON.parse(input) ?? {}
609
+ const result = JSON.stringify(input, (key, value) => {
610
+ if (value instanceof RegExp) return ['__REGEXP__', value.source, value.flags]
611
+ return value
612
+ })
613
+ if (returnString) return result
614
+ return JSON.parse(result)
615
+ }
616
+
617
+ reviveRegexInJson = (input, returnObject = true) => {
618
+ const { isPlainObject } = this.app.lib._
619
+ if (isPlainObject(input)) input = JSON.stringify(input)
620
+ const result = JSON.parse(input, (key, value) => {
621
+ if (Array.isArray(value) &amp;&amp; value[0] === '__REGEXP__') return new RegExp(value[1], value[2])
622
+ return value
570
623
  })
624
+ return returnObject ? result : JSON.stringify(result)
571
625
  }
572
626
  }
573
627
 
@@ -575,4 +629,4 @@ async function factory (pkgName) {
575
629
  }
576
630
 
577
631
  export default factory
578
- </code></pre></article></section></div></div></div><div class="search-container" id="PkfLWpAbet" style="display:none"><div class="wrapper" id="iCxFxjkHbP"><button class="icon-button search-close-button" id="VjLlGakifb" aria-label="close search"><svg><use xlink:href="#close-icon"></use></svg></button><div class="search-box-c"><svg><use xlink:href="#search-icon"></use></svg> <input type="text" id="vpcKVYIppa" class="search-input" placeholder="Search..." autofocus></div><div class="search-result-c" id="fWwVHRuDuN"><span class="search-result-c-text">Type anything to view search result</span></div></div></div><div class="mobile-menu-icon-container"><button class="icon-button" id="mobile-menu" data-isopen="false" aria-label="menu"><svg><use xlink:href="#menu-icon"></use></svg></button></div><div id="mobile-sidebar" class="mobile-sidebar-container"><div class="mobile-sidebar-wrapper"><a href="/" class="sidebar-title sidebar-title-anchor">Dobo API</a><div class="mobile-nav-links"><div class="navbar-item"><a id="" href="https://www.npmjs.com/package/dobo" target="">NPM</a></div><div class="navbar-item"><a id="" href="https://github.com/ardhi/dobo" target="">Github</a></div><div class="navbar-item"><a id="" href="https://dobo.bajo.app/" target="">Dobo</a></div><div class="navbar-item"><a id="" href="https://bajo.app/" target="">Bajo</a></div></div><div class="mobile-sidebar-items-c"><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-classes"><div>Classes</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="Dobo.html">Dobo</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-modules"><div>Modules</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="module-Lib.html">Lib</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-global"><div>Global</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="global.html#TPropType">TPropType</a></div><div class="sidebar-section-children"><a href="global.html#TRecordCountOptions">TRecordCountOptions</a></div><div class="sidebar-section-children"><a href="global.html#TRecordCreateOptions">TRecordCreateOptions</a></div><div class="sidebar-section-children"><a href="global.html#TRecordFilter">TRecordFilter</a></div><div class="sidebar-section-children"><a href="global.html#TRecordFindOptions">TRecordFindOptions</a></div><div class="sidebar-section-children"><a href="global.html#TRecordFindResult">TRecordFindResult</a></div><div class="sidebar-section-children"><a href="global.html#TRecordGetOptions">TRecordGetOptions</a></div><div class="sidebar-section-children"><a href="global.html#TRecordGetResult">TRecordGetResult</a></div><div class="sidebar-section-children"><a href="global.html#TRecordPagination">TRecordPagination</a></div><div class="sidebar-section-children"><a href="global.html#TRecordRemoveOptions">TRecordRemoveOptions</a></div><div class="sidebar-section-children"><a href="global.html#TRecordSort">TRecordSort</a></div><div class="sidebar-section-children"><a href="global.html#TRecordSortKey">TRecordSortKey</a></div><div class="sidebar-section-children"><a href="global.html#TRecordUpdateOptions">TRecordUpdateOptions</a></div><div class="sidebar-section-children"><a href="global.html#TValidator">TValidator</a></div><div class="sidebar-section-children"><a href="global.html#TValidatorBoolean">TValidatorBoolean</a></div><div class="sidebar-section-children"><a href="global.html#TValidatorDate">TValidatorDate</a></div><div class="sidebar-section-children"><a href="global.html#TValidatorNumber">TValidatorNumber</a></div><div class="sidebar-section-children"><a href="global.html#TValidatorString">TValidatorString</a></div><div class="sidebar-section-children"><a href="global.html#TValidatorTimestamp">TValidatorTimestamp</a></div><div class="sidebar-section-children"><a href="global.html#factory">factory</a></div></div></div><div class="mobile-navbar-actions"><div class="navbar-right-item"><button class="icon-button search-button" aria-label="open-search"><svg><use xlink:href="#search-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button theme-toggle" aria-label="toggle-theme"><svg><use class="theme-svg-use" xlink:href="#dark-theme-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button font-size" aria-label="change-font-size"><svg><use xlink:href="#font-size-icon"></use></svg></button></div></div></div></div><script type="text/javascript" src="scripts/core.min.js"></script><script src="scripts/search.min.js" defer="defer"></script><script src="scripts/third-party/fuse.js" defer="defer"></script><script type="text/javascript">var tocbotInstance=tocbot.init({tocSelector:"#eed4d2a0bfd64539bb9df78095dec881",contentSelector:".main-content",headingSelector:"h1, h2, h3",hasInnerContainers:!0,scrollContainer:".main-content",headingsOffset:130,onClick:bringLinkToView})</script></body></html>
632
+ </code></pre></article></section></div></div></div><div class="search-container" id="PkfLWpAbet" style="display:none"><div class="wrapper" id="iCxFxjkHbP"><button class="icon-button search-close-button" id="VjLlGakifb" aria-label="close search"><svg><use xlink:href="#close-icon"></use></svg></button><div class="search-box-c"><svg><use xlink:href="#search-icon"></use></svg> <input type="text" id="vpcKVYIppa" class="search-input" placeholder="Search..." autofocus></div><div class="search-result-c" id="fWwVHRuDuN"><span class="search-result-c-text">Type anything to view search result</span></div></div></div><div class="mobile-menu-icon-container"><button class="icon-button" id="mobile-menu" data-isopen="false" aria-label="menu"><svg><use xlink:href="#menu-icon"></use></svg></button></div><div id="mobile-sidebar" class="mobile-sidebar-container"><div class="mobile-sidebar-wrapper"><a href="/" class="sidebar-title sidebar-title-anchor">Dobo API</a><div class="mobile-nav-links"><div class="navbar-item"><a id="" href="https://www.npmjs.com/package/dobo" target="">NPM</a></div><div class="navbar-item"><a id="" href="https://github.com/ardhi/dobo" target="">Github</a></div><div class="navbar-item"><a id="" href="https://dobo.bajo.app/" target="">Dobo</a></div><div class="navbar-item"><a id="" href="https://bajo.app/" target="">Bajo</a></div></div><div class="mobile-sidebar-items-c"><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-classes"><div>Classes</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="Dobo.html">Dobo</a></div><div class="sidebar-section-children"><a href="DoboAction.html">DoboAction</a></div><div class="sidebar-section-children"><a href="DoboAdapter.html">DoboAdapter</a></div><div class="sidebar-section-children"><a href="DoboConnection.html">DoboConnection</a></div><div class="sidebar-section-children"><a href="DoboFeature.html">DoboFeature</a></div><div class="sidebar-section-children"><a href="DoboModel.html">DoboModel</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-modules"><div>Modules</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="module-Helper.html">Helper</a></div><div class="sidebar-section-children"><a href="module-Helper_Model.html">Helper/Model</a></div><div class="sidebar-section-children"><a href="module-Hook.html">Hook</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-externals"><div>Externals</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="external-Tools.html">Tools</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-global"><div>Global</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="global.html#actionFactory">actionFactory</a></div><div class="sidebar-section-children"><a href="global.html#adapterFactory">adapterFactory</a></div><div class="sidebar-section-children"><a href="global.html#connectionFactory">connectionFactory</a></div><div class="sidebar-section-children"><a href="global.html#featureFactory">featureFactory</a></div><div class="sidebar-section-children"><a href="global.html#modelFactory">modelFactory</a></div><div class="sidebar-section-children"><a href="global.html#pluginFactory">pluginFactory</a></div></div></div><div class="mobile-navbar-actions"><div class="navbar-right-item"><button class="icon-button search-button" aria-label="open-search"><svg><use xlink:href="#search-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button theme-toggle" aria-label="toggle-theme"><svg><use class="theme-svg-use" xlink:href="#dark-theme-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button font-size" aria-label="change-font-size"><svg><use xlink:href="#font-size-icon"></use></svg></button></div></div></div></div><script type="text/javascript" src="scripts/core.min.js"></script><script src="scripts/search.min.js" defer="defer"></script><script src="scripts/third-party/fuse.js" defer="defer"></script><script type="text/javascript">var tocbotInstance=tocbot.init({tocSelector:"#eed4d2a0bfd64539bb9df78095dec881",contentSelector:".main-content",headingSelector:"h1, h2, h3",hasInnerContainers:!0,scrollContainer:".main-content",headingsOffset:130,onClick:bringLinkToView})</script></body></html>