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
@@ -0,0 +1,1057 @@
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: lib/factory/adapter.js</title><!--[if lt IE 9]>
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 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">lib_factory_adapter.js</h1></header><article><pre class="prettyprint source lang-js"><code>import { ulid } from 'ulid'
4
+ import { v4 as uuidv4, v7 as uuidv7 } from 'uuid'
5
+ import crypto from 'crypto'
6
+
7
+ const defIdField = {
8
+ name: '_id',
9
+ type: 'string',
10
+ maxLength: 50,
11
+ required: true,
12
+ index: 'primary'
13
+ }
14
+
15
+ /**
16
+ * @external Tools
17
+ * @see {@link https://ardhi.github.io/bajo/Tools.html|Bajo Tools}
18
+ */
19
+
20
+ /**
21
+ * @typedef TIdField
22
+ * @type {object}
23
+ * @memberof DoboAdapter
24
+ * @property {string} [name='_id'] - The name of the ID field.
25
+ * @property {string} [type='string'] - The data type of the ID field.
26
+ * @property {number} [maxLength=50] - The maximum length of the ID field.
27
+ * @property {boolean} [required=true] - Indicates if the ID field is required.
28
+ * @property {string} [index='primary'] - The index type of the ID field.
29
+ */
30
+
31
+ /**
32
+ * @typedef TSupport
33
+ * @memberof DoboAdapter
34
+ * @type {object}
35
+ * @property {object} [propType={}] - An object indicating support for various property types.
36
+ * @property {boolean} [propType.object=false] - Indicates if object property type is supported.
37
+ * @property {boolean} [propType.array=false] - Indicates if array property type is supported.
38
+ * @property {boolean} [propType.datetime=true] - Indicates if datetime property type is supported.
39
+ * @property {boolean} [search=false] - Indicates if search functionality is supported.
40
+ * @property {boolean} [uniqueIndex=false] - Indicates if unique index functionality is supported.
41
+ * @property {boolean} [nullableField=true] - Indicates if nullable fields are supported.
42
+ * @property {boolean} [transaction=false] - Indicates if transaction functionality is supported.
43
+ */
44
+
45
+ /**
46
+ * Adapter factory function.
47
+ *
48
+ * @async
49
+ * @returns {Promise&lt;DoboAdapter>}
50
+ */
51
+ async function adapterFactory () {
52
+ const { Tools } = this.app.baseClass
53
+ const { pick, cloneDeep, has, uniq, without, isEmpty, omit, isFunction, camelCase, last } = this.app.lib._
54
+ const { isSet } = this.app.lib.aneka
55
+ const { runHook } = this.app.bajo
56
+
57
+ /**
58
+ * DoboAdapter class serves as a base class for all database adapters in the Dobo framework. It provides common functionality for managing models, records, and database operations.
59
+ * Child classes should implement the abstract methods to provide specific database functionality.
60
+ *
61
+ * @class
62
+ * @extends external:Tools
63
+ */
64
+ class DoboAdapter extends Tools {
65
+ /**
66
+ * Constructor.
67
+ */
68
+ constructor (plugin, name, options = {}) {
69
+ super(plugin)
70
+
71
+ /**
72
+ * Adapter name
73
+ * @type {string}
74
+ */
75
+ this.name = name
76
+
77
+ /**
78
+ * ID field configuration
79
+ * @type {DoboAdapter.TIdField}
80
+ */
81
+ this.idField = cloneDeep(defIdField)
82
+ this.propertyType = {}
83
+
84
+ /**
85
+ * Support configuration for the adapter
86
+ * @type {DoboAdapter.TSupport}
87
+ */
88
+ this.support = {
89
+ propType: {
90
+ object: false,
91
+ array: false,
92
+ datetime: true
93
+ },
94
+ search: false,
95
+ uniqueIndex: false,
96
+ nullableField: true,
97
+ transaction: false
98
+ }
99
+
100
+ /**
101
+ * Indicates whether to use UTC for datetime fields
102
+ * @type {boolean}
103
+ */
104
+ this.useUtc = false
105
+
106
+ /**
107
+ * Maximum chunk size for bulk operations
108
+ * @type {number}
109
+ */
110
+ this.maxChunkSize = 500
111
+
112
+ /**
113
+ * Indicates whether the adapter uses in-memory storage
114
+ * @type {boolean}
115
+ */
116
+ this.memory = false
117
+
118
+ /**
119
+ * Adapter options
120
+ * @type {object}
121
+ */
122
+ this.options = options
123
+ }
124
+
125
+ /**
126
+ * Sanitize connection object
127
+ * @async
128
+ * @method
129
+ * @param {Object} conn - Connection object
130
+ * @returns {Promise&lt;void>}
131
+ */
132
+ async sanitizeConnection (conn) {
133
+ conn.proto = conn.proto ?? 'http' // used by adapter that use url based connection
134
+ conn.memory = false
135
+ }
136
+
137
+ /**
138
+ * Sanitizes the body of a record before creating or updating it. It ensures that all required fields
139
+ * are present and have valid values, and converts data types as necessary.
140
+ * @param {DoboModel} model - The model instance for which the body is being sanitized
141
+ * @param {object} body - The body of the record to be sanitized
142
+ * @param {boolean} [partial=false] - Indicates whether to perform a partial update
143
+ * @returns {object} - Sanitized body
144
+ */
145
+ sanitizeBody (model, body = {}, partial) {
146
+ const { keys, pick } = this.app.lib._
147
+ const item = cloneDeep(body)
148
+ let newId = false
149
+ if (has(item, 'id') &amp;&amp; this.idField.name !== 'id') {
150
+ item[this.idField.name] = item.id
151
+ newId = true
152
+ }
153
+ for (const prop of model.getNonVirtualProperties()) {
154
+ if (item[prop.name] === 'null') item[prop.name] = null
155
+ if (!isSet(item[prop.name]) &amp;&amp; !this.support.nullableField) {
156
+ switch (prop.type) {
157
+ case 'datetime': item[prop.name] = new Date(0); break
158
+ case 'float':
159
+ case 'double': item[prop.name] = 0; break
160
+ case 'string':
161
+ case 'text': item[prop.name] = ''; break
162
+ case 'object': item[prop.name] = {}; break
163
+ case 'array': item[prop.name] = []; break
164
+ }
165
+ }
166
+ if (isSet(item[prop.name]) &amp;&amp; !this.support.propType[prop.type]) {
167
+ if (prop.type === 'datetime') item[prop.name] = item[prop.name].toISOString()
168
+ else if (['object', 'array'].includes(prop.type)) item[prop.name] = JSON.stringify(item[prop.name])
169
+ }
170
+ }
171
+ const result = partial ? pick(item, keys(body)) : item
172
+ if (newId) delete result.id
173
+ return result
174
+ }
175
+
176
+ /**
177
+ * Sanitizes a record retrieved from the database, converting data types as necessary
178
+ * and ensuring that the record conforms to the model's schema.
179
+ * @param {DoboModel} model - The model instance for which the record is being sanitized
180
+ * @param {object} record - The record retrieved from the database
181
+ * @returns {object} - Sanitized record
182
+ */
183
+ sanitizeRecord (model, record = {}) {
184
+ const { dayjs } = this.app.lib
185
+ const { isString } = this.app.lib._
186
+ const item = { ...record }
187
+ if (has(item, this.idField.name) &amp;&amp; this.idField.name !== 'id') {
188
+ item.id = item[this.idField.name]
189
+ delete item[this.idField.name]
190
+ }
191
+ for (const prop of model.properties) {
192
+ if (isSet(item[prop.name])) {
193
+ if (!this.support.propType[prop.type]) {
194
+ try {
195
+ if (prop.type === 'datetime') {
196
+ const dt = this.useUtc ? dayjs.utc(item[prop.name]) : dayjs(item[prop.name])
197
+ item[prop.name] = dt.toDate()
198
+ } else if (['object', 'array'].includes(prop.type)) item[prop.name] = JSON.parse(item[prop.name])
199
+ } catch (err) {
200
+ item[prop.name] = null
201
+ }
202
+ }
203
+ if (prop.type === 'datetime' &amp;&amp; isString(item[prop.name])) {
204
+ const dt = this.useUtc ? dayjs.utc(item[prop.name]) : dayjs(item[prop.name])
205
+ item[prop.name] = dt.toDate()
206
+ }
207
+ if (prop.type === 'boolean' &amp;&amp; isSet(item[prop.name])) item[prop.name] = Boolean(item[prop.name])
208
+ }
209
+ }
210
+ return item
211
+ }
212
+
213
+ /**
214
+ * Utility method to get the real fields of a model, excluding virtual fields.
215
+ * This is useful for operations that require only the actual stored properties of a model.
216
+ * @param {*} model
217
+ * @returns {string[]} - Array of real field names
218
+ */
219
+ getRealFields (model) {
220
+ return model.getProperties({ noVirtual: true, namesOnly: true })
221
+ }
222
+
223
+ /**
224
+ * Utility method to get the virtual fields of a model.
225
+ * This is useful for operations that need to work with computed or derived properties.
226
+ * @param {DoboModel} model - The model instance
227
+ * @returns {string[]} - Array of virtual field names
228
+ */
229
+ getVirtualFields (model) {
230
+ return model.getVirtualProperties({ namesOnly: true })
231
+ }
232
+
233
+ /**
234
+ * Get returning fields for a model based on the provided options. If the adapter supports returning fields,
235
+ * it will return the specified fields or all model properties. It ensures that the ID field is always
236
+ * included in the returned fields.
237
+ * @param {DoboModel} model - The model instance for which to get the returning fields
238
+ * @param {object} options - Options that may include the fields to return
239
+ * @returns {string[]} - Array of field names to be returned
240
+ */
241
+ _getReturningFields (model, options = {}) {
242
+ const { fields = [] } = options
243
+ if (!this.support.returning) return []
244
+ let items = fields.length > 0 ? [...fields] : model.properties.map(prop => prop.name)
245
+ if (!items.includes(this.idField.name)) items.unshift(this.idField.name)
246
+ if (this.idField.name !== 'id') items = without(items, ['id'])
247
+ return uniq(items)
248
+ }
249
+
250
+ /**
251
+ * Attaches hooks to the model for various operations. It runs the appropriate hooks
252
+ * before and after the specified operation, allowing for custom behavior to be injected
253
+ * into the model's lifecycle.
254
+ * @internal
255
+ * @async
256
+ * @method
257
+ * @param {string} name - The name of the hook
258
+ * @param {DoboModel} model - The model instance to which the hook is being attached
259
+ * @param {...any} args - Additional arguments to be passed to the hook
260
+ */
261
+ async _attachHook (name, model, ...args) {
262
+ const { ns } = this.app.dobo
263
+ const { kebabCase } = this.app.lib._
264
+ const options = last(args)
265
+ if (!options.noAdapterHook) {
266
+ const prefix = kebabCase(name).split('-')[0]
267
+ await runHook(`${ns}.adapter:${prefix}Any`, model, options)
268
+ await runHook(`${ns}.adapter:${name}`, model, ...args)
269
+ await runHook(`${ns}.adapter.${camelCase(model.name)}:${name}`, ...args)
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Checks the uniqueness of fields with a unique index.
275
+ * @async
276
+ * @method
277
+ * @internal
278
+ * @param {DoboModel} model - The model instance to check
279
+ * @param {object} body - The data to be checked for uniqueness
280
+ * @param {object} options - Additional options, including the action being performed
281
+ * @returns {Promise&lt;void>} - Resolves if unique, throws an error if not
282
+ */
283
+ _checkUnique = async (model, body = {}, options = {}) => {
284
+ const { isSet } = this.app.lib.aneka
285
+ const { filter, map, isEmpty, forOwn } = this.app.lib._
286
+ const indexes = filter(model.indexes ?? [], idx => idx.type === 'unique')
287
+ for (const index of indexes) {
288
+ const query = {}
289
+ for (const field of index.fields) {
290
+ if (isSet(body[field])) query[field] = body[field]
291
+ }
292
+ if (isEmpty(query)) continue
293
+ const { data } = await model.findOneRecord({ query }, options)
294
+ if (!isEmpty(data)) {
295
+ if (['updateRecord', 'upsertRecord'].includes(options.action)) {
296
+ let eq = true
297
+ forOwn(query, (v, k) => {
298
+ if (data[k] !== v) eq = false
299
+ })
300
+ if (!eq) continue
301
+ }
302
+ const error = this.app.dobo.t('uniqueConstraintError')
303
+ const details = map(index.fields, field => {
304
+ return { field, error }
305
+ })
306
+ throw this.app.dobo.error(error, { details, body })
307
+ }
308
+ }
309
+ }
310
+
311
+ // Internal calls that will be called by model
312
+
313
+ /**
314
+ * Wrapper for the `modelExists` method, called internally by `model` to make sure
315
+ * all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
316
+ *
317
+ * @internal
318
+ * @async
319
+ * @method
320
+ * @param {DoboModel} model
321
+ * @param {object} options
322
+ * @returns {Promise&lt;boolean>}
323
+ */
324
+ async _modelExists (model, options = {}) {
325
+ return await this.modelExists(model, options)
326
+ }
327
+
328
+ /**
329
+ * Wrapper for the `buildModel` method, called internally by `model` to make sure
330
+ * all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
331
+ * @internal
332
+ * @async
333
+ * @method
334
+ * @param {DoboModel} model
335
+ * @param {object} options
336
+ * @returns {Promise&lt;object>}
337
+ */
338
+ async _buildModel (model, options = {}) {
339
+ return await this.buildModel(model, options)
340
+ }
341
+
342
+ /**
343
+ * Wrapper for the `dropModel` method, called internally by `model` to make sure
344
+ * all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
345
+ * @internal
346
+ * @async
347
+ * @method
348
+ * @param {DoboModel} model
349
+ * @param {object} options
350
+ * @returns {Promise&lt;object>}
351
+ */
352
+ async _dropModel (model, options = {}) {
353
+ return await this.dropModel(model, options)
354
+ }
355
+
356
+ /**
357
+ * Prepares the body of a record for creation by populating default values for properties
358
+ * that are not set. It handles various types of default values, including functions,
359
+ * special strings (like 'now', 'uuid', etc.), and static values.
360
+ * @internal
361
+ * @async
362
+ * @method
363
+ * @param {DoboModel} model - The model instance for which the body is being prepared
364
+ * @param {object} body - The data to be prepared for creation
365
+ * @param {object} options - Additional options that may affect the preparation
366
+ * @returns {Promise&lt;object>} - The prepared body with default values populated
367
+ */
368
+ async _prepBodyForCreate (model, body = {}, options = {}) {
369
+ const { callHandler } = this.app.bajo
370
+ const { isSet, generateId } = this.app.lib.aneka
371
+ for (const prop of model.getProperties({ noVirtual: true })) {
372
+ if (isSet(prop.default) &amp;&amp; (!options.noDefault) &amp;&amp; (!isSet(body[prop.name]) || body[prop.name] === prop.default)) {
373
+ if (isFunction(prop.default)) body[prop.name] = await prop.default.call(model)
374
+ else if (typeof prop.default !== 'string') body[prop.name] = prop.default
375
+ else {
376
+ if (['now'].includes(prop.default) &amp;&amp; prop.type === 'datetime') {
377
+ body[prop.name] = new Date()
378
+ } else if (['uuid', 'uuidv4'].includes(prop.default) &amp;&amp; prop.type === 'string') {
379
+ body[prop.name] = uuidv4().slice(0, prop.maxLength)
380
+ } else if (prop.default === 'uuidv7' &amp;&amp; prop.type === 'string') {
381
+ body[prop.name] = uuidv7().slice(0, prop.maxLength)
382
+ } else if (prop.default === 'ulid' &amp;&amp; prop.type === 'string') {
383
+ body[prop.name] = ulid().slice(0, prop.maxLength)
384
+ } else if (prop.default === 'generateid' &amp;&amp; prop.type === 'string') {
385
+ body[prop.name] = generateId()
386
+ } else if (prop.default.startsWith('handler:')) {
387
+ const [, ...args] = prop.default.split(':')
388
+ if (args.length > 0) body[prop.name] = await callHandler(args.join(':'))
389
+ } else if (prop.default.startsWith('md5:') &amp;&amp; prop.type === 'string') {
390
+ const [, field] = prop.default.split(':')
391
+ const fields = field.split(',')
392
+ if (model.properties.filter(item => fields.includes(item.name)).length === fields.length) {
393
+ const values = fields.map(f => body[f])
394
+ body[prop.name] = crypto.createHash('md5').update(values.join(':')).digest('hex')
395
+ }
396
+ } else {
397
+ body[prop.name] = prop.default
398
+ }
399
+ }
400
+ }
401
+ }
402
+ return pick(body, this.getRealFields(model))
403
+ }
404
+
405
+ /**
406
+ * Prepares the ID for a record before creation. It generates an ID if it is not set in the body.
407
+ *
408
+ * @internal
409
+ * @async
410
+ * @method
411
+ * @param {DoboModel} model - The model instance for which the ID is being prepared
412
+ * @param {object} body - The data containing the ID
413
+ * @param {object} options - Additional options that may affect ID generation
414
+ * @returns {Promise&lt;void>} - Resolves when the ID has been prepared
415
+ */
416
+ async _prepIdForCreate (model, body = {}, options = {}) {
417
+ const { isSet, generateId } = this.app.lib.aneka
418
+ const { isFunction } = this.app.lib._
419
+ const prop = model.properties.find(p => p.name === 'id')
420
+ if (!isSet(body.id) &amp;&amp; prop.type === 'string') {
421
+ if (this.idGenerator) {
422
+ if (['uuid', 'uuidv4'].includes(this.idGenerator)) body.id = uuidv4()
423
+ else if (['uuidv7'].includes(this.idGenerator)) body.id = uuidv7()
424
+ else if (this.idGenerator === 'generateId') body.id = generateId()
425
+ else if (isFunction(this.idGenerator)) body.id = await this.idGenerator(model, body, options)
426
+ }
427
+ if (!body.id) body.id = ulid()
428
+ body.id = body.id.slice(0, prop.maxLength)
429
+ }
430
+ }
431
+
432
+ _injectMeta (result = {}, options = {}) {
433
+ result.warnings = result.warnings ?? []
434
+ result.warnings.push(...(options.warnings ?? []))
435
+ }
436
+
437
+ /**
438
+ * Wrapper for the {@link DoboAdapter#createRecord} method, called internally by `model` to make sure
439
+ * all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
440
+ * @internal
441
+ * @async
442
+ * @method
443
+ * @param {DoboModel} model
444
+ * @param {object} input
445
+ * @param {object} options
446
+ * @returns {Promise&lt;object>}
447
+ */
448
+ async _createRecord (model, input = {}, options = {}) {
449
+ const { isSet } = this.app.lib.aneka
450
+ let body = await this._prepBodyForCreate(model, input, options)
451
+ await this._prepIdForCreate(model, body, options)
452
+ if (!options.noUniqueCheck) {
453
+ if (!this.support.uniqueIndex) await this._checkUnique(model, body, options)
454
+ }
455
+ if (!options.noIdCheck &amp;&amp; isSet(body.id)) {
456
+ const resp = await this.getRecord(model, body.id, { noMagic: true })
457
+ if (!isEmpty(resp.data)) throw this.plugin.error('recordExists%s%s', body.id, model.name)
458
+ }
459
+ body = this.sanitizeBody(model, body)
460
+
461
+ await this._attachHook('beforeCreateRecord', model, body, options)
462
+ const result = await this.createRecord(model, body, options)
463
+ await this._attachHook('afterCreateRecord', model, body, result, options)
464
+
465
+ if (options.noResult) return
466
+ result.data = this.sanitizeRecord(model, result.data)
467
+ this._injectMeta(result, options)
468
+ return result
469
+ }
470
+
471
+ /**
472
+ * Wrapper for the {@link DoboAdapter#bulkCreateRecord} method, called internally by `model` to make sure
473
+ * all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
474
+ * @internal
475
+ * @async
476
+ * @method
477
+ * @param {DoboModel} model
478
+ * @param {Array&lt;object>} bodies
479
+ * @param {object} options
480
+ * @returns {Promise&lt;void>}
481
+ */
482
+ async _bulkCreateRecord (model, bodies = [], options = {}) {
483
+ const { chunk } = this.app.lib._
484
+ let { chunkSize = this.maxChunkSize } = options
485
+ if (chunkSize > this.maxChunkSize) chunkSize = this.maxChunkSize
486
+ for (const idx in bodies) {
487
+ const body = await this._prepBodyForCreate(model, bodies[idx], options)
488
+ await this._prepIdForCreate(model, body, options)
489
+ bodies[idx] = this.sanitizeBody(model, body)
490
+ }
491
+
492
+ await this._attachHook('beforeBulkCreateRecord', model, bodies, options)
493
+ const items = chunk(bodies, chunkSize)
494
+ for (const item of items) {
495
+ await this.bulkCreateRecord(model, item, options)
496
+ }
497
+ await this._attachHook('afterBulkCreateRecord', model, bodies, [], options)
498
+ }
499
+
500
+ /**
501
+ * Wrapper for the {@link DoboAdapter#getRecord} method, called internally by `model` to make sure
502
+ * all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
503
+ * @internal
504
+ * @async
505
+ * @method
506
+ * @param {DoboModel} model
507
+ * @param {string|number} id
508
+ * @param {object} options
509
+ * @returns {Promise&lt;object>}
510
+ */
511
+ async _getRecord (model, id, options = {}) {
512
+ await this._attachHook('beforeGetRecord', model, id, options)
513
+ const result = await this.getRecord(model, id, options)
514
+ await this._attachHook('afterGetRecord', model, id, result, options)
515
+
516
+ if (isEmpty(result.data) &amp;&amp; options.throwNotFound) throw this.plugin.error('recordNotFound%s%s', id, model.name)
517
+ result.data = this.sanitizeRecord(model, result.data)
518
+ this._injectMeta(result, options)
519
+ return result
520
+ }
521
+
522
+ /**
523
+ * Wrapper for the {@link DoboAdapter#updateRecord} method, called internally by `model` to make sure
524
+ * all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
525
+ * @internal
526
+ * @async
527
+ * @method
528
+ * @param {DoboModel} model
529
+ * @param {string|number} id
530
+ * @param {object} input
531
+ * @param {object} options
532
+ * @returns {Promise&lt;object>}
533
+ */
534
+ async _updateRecord (model, id, input = {}, options = {}) {
535
+ let body = omit(input, this.getVirtualFields(model))
536
+ if (!options.noUniqueCheck) {
537
+ if (!this.support.uniqueIndex) await this._checkUnique(model, body, options)
538
+ }
539
+ if (!options._data) {
540
+ const resp = await this.getRecord(model, id, { noMagic: true })
541
+ if (!resp.data) throw this.plugin.error('recordNotFound%s%s', id, model.name)
542
+ options._data = resp.data
543
+ }
544
+ body = this.sanitizeBody(model, body, true)
545
+ delete body.id
546
+
547
+ await this._attachHook('beforeUpdateRecord', model, id, body, options)
548
+ const result = await this.updateRecord(model, id, body, options)
549
+ await this._attachHook('afterUpdateRecord', model, id, body, result, options)
550
+
551
+ if (options.noResult) return
552
+ result.oldData = this.sanitizeRecord(model, result.oldData)
553
+ result.data = this.sanitizeRecord(model, result.data)
554
+ this._injectMeta(result, options)
555
+ return result
556
+ }
557
+
558
+ /**
559
+ * Upserts a record for the given model.
560
+ * This method will only run if child adapter does not implement {@link DoboAdapter#upsertRecord}.
561
+ *
562
+ * @internal
563
+ * @async
564
+ * @method
565
+ * @param {DoboModel} model - The model instance for which the record is being upserted
566
+ * @param {object} input - The input data for the upsert
567
+ * @param {object} options - Additional options that may affect record upserting
568
+ * @returns {Promise&lt;object>} - The result of the record upsert
569
+ */
570
+ async _upsertRecord (model, input = {}, options = {}) {
571
+ let body = omit(input, this.getVirtualFields(model))
572
+ if (!options.noUniqueCheck) {
573
+ if (!this.support.uniqueIndex) await this._checkUnique(model, body, options)
574
+ }
575
+ if (isSet(body.id)) {
576
+ if (!options._data) {
577
+ const resp = await this.getRecord(model, body.id, { noMagic: true })
578
+ if (!resp.data) throw this.plugin.error('recordNotFound%s%s', body.id, model.name)
579
+ options._data = resp.data
580
+ }
581
+ }
582
+ body = this.sanitizeBody(model, body)
583
+
584
+ await this._attachHook('beforeUpsertRecord', model, body, options)
585
+ const result = await this.upsertRecord(model, body, options)
586
+ await this._attachHook('afterUpsertRecord', model, body, result, options)
587
+
588
+ if (options.noResult) return
589
+ if (result.oldData) result.oldData = this.sanitizeRecord(model, result.oldData)
590
+ result.data = this.sanitizeRecord(model, result.data)
591
+ this._injectMeta(result, options)
592
+ return result
593
+ }
594
+
595
+ /**
596
+ * Wrapper for the {@link DoboAdapter#removeRecord} method, called internally by `model` to make sure
597
+ * all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
598
+ * @internal
599
+ * @async
600
+ * @method
601
+ * @param {DoboModel} model
602
+ * @param {string|number} id
603
+ * @param {object} options
604
+ * @returns {Promise&lt;object>}
605
+ */
606
+ async _removeRecord (model, id, options = {}) {
607
+ if (!options._data) {
608
+ const resp = await this.getRecord(model, id, { noMagic: true })
609
+ if (!resp.data) throw this.plugin.error('recordNotFound%s%s', id, model.name)
610
+ options._data = resp.data
611
+ }
612
+
613
+ await this._attachHook('beforeRemoveRecord', model, id, options)
614
+ const result = await this.removeRecord(model, id, options)
615
+ await this._attachHook('afterRemoveRecord', model, id, result, options)
616
+
617
+ if (options.noResult) return
618
+ result.oldData = this.sanitizeRecord(model, result.oldData)
619
+ this._injectMeta(result, options)
620
+ return result
621
+ }
622
+
623
+ /**
624
+ * Wrapper for the {@link DoboAdapter#clearRecord} method, called internally by `model` to make sure
625
+ * all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
626
+ * @internal
627
+ * @async
628
+ * @method
629
+ * @param {DoboModel} model
630
+ * @param {object} options
631
+ * @returns {Promise&lt;object>}
632
+ */
633
+ async _clearRecord (model, options = {}) {
634
+ await this._attachHook('beforeClearRecord', model, options)
635
+ const result = await this.clearRecord(model, options)
636
+ await this._attachHook('afterClearRecord', model, result, options)
637
+
638
+ this._injectMeta(result, options)
639
+ return result
640
+ }
641
+
642
+ /**
643
+ * Wrapper for the {@link DoboAdapter#findRecord} method, called internally by `model` to make sure
644
+ * all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
645
+ * @internal
646
+ * @async
647
+ * @method
648
+ * @param {DoboModel} model
649
+ * @param {object} filter
650
+ * @param {object} options
651
+ * @returns {Promise&lt;object>}
652
+ */
653
+ async _findRecord (model, filter = {}, options = {}) {
654
+ let result
655
+ try {
656
+ await this._attachHook('beforeFindRecord', model, filter, options)
657
+ result = await this.findRecord(model, filter, options)
658
+ await this._attachHook('afterFindRecord', model, filter, result, options)
659
+ } catch (err) {
660
+ if (!['_emptyColumnQuery', '_abortAction'].includes(err.message)) throw err
661
+ result = {
662
+ data: [],
663
+ count: 0
664
+ // warnings: [] // TODO: should generate warnings?
665
+ }
666
+ }
667
+
668
+ for (const idx in result.data) {
669
+ result.data[idx] = this.sanitizeRecord(model, result.data[idx])
670
+ }
671
+ this._injectMeta(result, options)
672
+ return result
673
+ }
674
+
675
+ /**
676
+ * Finds all records for the given model based on the provided filter.
677
+ * This method will only run if child adapter does not implement {@link DoboAdapter#findAllRecord}.
678
+ *
679
+ * @internal
680
+ * @async
681
+ * @method
682
+ * @param {DoboModel} model - The model instance for which the records are being found
683
+ * @param {object} filter - The filter criteria for finding records
684
+ * @param {object} options - Additional options that may affect record finding
685
+ * @returns {Promise&lt;object>} - The result of the record finding
686
+ */
687
+ async _findAllRecord (model, filter = {}, options = {}) {
688
+ let result
689
+ try {
690
+ await this._attachHook('beforeFindAllRecord', model, filter, options)
691
+ result = await this.findAllRecord(model, filter, options)
692
+ await this._attachHook('afterFindAllRecord', model, filter, result, options)
693
+ } catch (err) {
694
+ if (err.message !== '_emptyColumnQuery') throw err
695
+ result = {
696
+ data: [],
697
+ count: 0
698
+ // warnings: [] // TODO: should generate warnings?
699
+ }
700
+ }
701
+
702
+ for (const idx in result.data) {
703
+ result.data[idx] = this.sanitizeRecord(model, result.data[idx])
704
+ }
705
+ this._injectMeta(result, options)
706
+ return result
707
+ }
708
+
709
+ /**
710
+ * Wrapper for the {@link DoboAdapter#countRecord} method, called internally by `model` to make sure
711
+ * all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
712
+ * @internal
713
+ * @async
714
+ * @method
715
+ * @param {DoboModel} model
716
+ * @param {object} filter
717
+ * @param {object} options
718
+ * @returns {Promise&lt;object>}
719
+ */
720
+ async _countRecord (model, filter = {}, options = {}) {
721
+ let result
722
+ try {
723
+ await this._attachHook('beforeCountRecord', model, filter, options)
724
+ result = await this.countRecord(model, filter, options)
725
+ await this._attachHook('afterCountRecord', model, filter, result, options)
726
+ } catch (err) {
727
+ if (err.message !== '_emptyColumnQuery') throw err
728
+ result = { data: 0 }
729
+ }
730
+
731
+ return result
732
+ }
733
+
734
+ /**
735
+ * Wrapper for the {@link DoboAdapter#createAggregate} method, called internally by `model` to make sure
736
+ * all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
737
+ * @internal
738
+ * @async
739
+ * @method
740
+ * @param {DoboModel} model
741
+ * @param {object} filter
742
+ * @param {object} params
743
+ * @param {object} options
744
+ * @returns {Promise&lt;object>}
745
+ */
746
+ async _createAggregate (model, filter = {}, params = {}, options = {}) {
747
+ const fieldPropTypes = ['integer', 'smallint', 'float', 'double']
748
+ const groupPropTypes = ['string', ...fieldPropTypes]
749
+ this.app.dobo.checkAggregateParams(params)
750
+ const { group, field } = params
751
+
752
+ let prop = model.properties.find(p => p.name === group)
753
+ if (!prop) throw this.plugin.error('unknown%s%s', this.plugin.t('field.field'), group)
754
+ if (!groupPropTypes.includes(prop.type)) throw this.plugin.error('allowedPropType%s%s', group, groupPropTypes.join(', '))
755
+
756
+ prop = model.properties.find(p => p.name === field)
757
+ if (!prop) throw this.plugin.error('unknown%s%s', this.plugin.t('field.field'), field)
758
+ // if (!fieldPropTypes.includes(prop.type)) throw this.plugin.error('allowedPropType%s%s', field, fieldPropTypes.join(', '))
759
+
760
+ let result
761
+ try {
762
+ await this._attachHook('beforeCreateAggregate', model, filter, params, options)
763
+ result = await this.createAggregate(model, filter, params, options)
764
+ await this._attachHook('afterCreateAggregate', model, filter, params, result, options)
765
+ } catch (err) {
766
+ if (err.message !== '_emptyColumnQuery') throw err
767
+ result = { data: [] }
768
+ }
769
+
770
+ for (const idx in result.data) {
771
+ result.data[idx] = this.sanitizeRecord(model, result.data[idx])
772
+ }
773
+ this._injectMeta(result, options)
774
+ return result
775
+ }
776
+
777
+ /**
778
+ * Wrapper for the {@link DoboAdapter#createHistogram} method, called internally by `model` to make sure
779
+ * all adapter hooks are executed accordingly, and all inputs and outputs are sanitized.
780
+ * @internal
781
+ * @async
782
+ * @method
783
+ * @param {DoboModel} model
784
+ * @param {object} filter
785
+ * @param {object} params
786
+ * @param {object} options
787
+ * @returns {Promise&lt;object>}
788
+ */
789
+ async _createHistogram (model, filter = {}, params, options = {}) {
790
+ // const fieldPropTypes = ['integer', 'smallint', 'float', 'double']
791
+ const groupPropTypes = ['datetime', 'date']
792
+ this.app.dobo.checkHistogramParams(params)
793
+ const { group, field } = params
794
+
795
+ let prop = model.properties.find(p => p.name === group)
796
+ if (!prop) throw this.plugin.error('unknown%s%s', this.plugin.t('field.field'), group)
797
+ if (!groupPropTypes.includes(prop.type)) throw this.plugin.error('allowedPropType%s%s', group, groupPropTypes.join(', '))
798
+
799
+ prop = model.properties.find(p => p.name === field)
800
+ if (!prop) throw this.plugin.error('unknown%s%s', this.plugin.t('field.field'), field)
801
+
802
+ let result
803
+ try {
804
+ await this._attachHook('beforeCreateHistogram', model, filter, params, options)
805
+ result = await this.createHistogram(model, filter, params, options)
806
+ await this._attachHook('afterCreateHistogram', model, filter, params, result, options)
807
+ } catch (err) {
808
+ if (err.message !== '_emptyColumnQuery') throw err
809
+ result = { data: [] }
810
+ }
811
+
812
+ for (const idx in result.data) {
813
+ result.data[idx] = this.sanitizeRecord(model, result.data[idx])
814
+ }
815
+ this._injectMeta(result, options)
816
+ return result
817
+ }
818
+
819
+ // Public calls that need to be implemented by child adapters
820
+
821
+ /**
822
+ * Connects to the database using the provided connection parameters.
823
+ * @param {*} connection - The connection parameters for the database
824
+ * @param {*} noRebuild - Flag indicating whether to skip rebuilding the database schema
825
+ * @returns {Promise&lt;void>} - Resolves when the connection is established
826
+ */
827
+ async connect (connection, noRebuild) {
828
+ }
829
+
830
+ /**
831
+ * Checks if the model exists in the database.
832
+ *
833
+ * Must be implemented by child classes to provide specific database functionality for model existence checking,
834
+ * or throw an error if the operation is not supported by the adapter.
835
+ *
836
+ * @param {DoboModel} model - The model instance to check for existence
837
+ * @param {object} options - Additional options that may affect the existence check
838
+ */
839
+ async modelExists (model, options = {}) {
840
+ throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'modelExists', this.name)
841
+ }
842
+
843
+ /**
844
+ * Builds the model in the database.
845
+ *
846
+ * Must be implemented by child classes to provide specific database functionality for model building,
847
+ * or throw an error if the operation is not supported by the adapter.
848
+ *
849
+ * @param {DoboModel} model - The model instance for which the record is being built
850
+ * @param {object} options - Additional options that may affect model building
851
+ */
852
+ async buildModel (model, options = {}) {
853
+ throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'buildModel', this.name)
854
+ }
855
+
856
+ /**
857
+ * Drops the model from the database.
858
+ *
859
+ * Must be implemented by child classes to provide specific database functionality for dropping a model,
860
+ * or throw an error if the operation is not supported by the adapter.
861
+ *
862
+ * @param {DoboModel} model - The model instance for which the record is being dropped
863
+ * @param {object} options - Additional options that may affect record dropping
864
+ */
865
+ async dropModel (model, options = {}) {
866
+ throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'dropModel', this.name)
867
+ }
868
+
869
+ /**
870
+ * Creates a new record for the given model.
871
+ *
872
+ * Must be implemented by child classes to provide specific database functionality for record creation,
873
+ * or throw an error if the operation is not supported by the adapter.
874
+ *
875
+ * @param {DoboModel} model - The model instance for which the record is being created
876
+ * @param {object} input - The input data for the new record
877
+ * @param {object} options - Additional options that may affect record creation
878
+ * @returns {Promise&lt;object>} - The result of the record creation
879
+ */
880
+ async createRecord (model, body = {}, options = {}) {
881
+ throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'createRecord', this.name)
882
+ }
883
+
884
+ /**
885
+ * Retrieves a record for the given model by its ID.
886
+ *
887
+ * Must be implemented by child classes to provide specific database functionality for record retrieval,
888
+ * or throw an error if the operation is not supported by the adapter.
889
+ *
890
+ * @param {DoboModel} model - The model instance for which the record is being retrieved
891
+ * @param {string|number} id - The ID of the record to retrieve
892
+ * @param {object} options - Additional options that may affect record retrieval
893
+ * @returns {Promise&lt;object>} - The result of the record retrieval
894
+ */
895
+ async getRecord (model, id, options = {}) {
896
+ throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'getRecord', this.name)
897
+ }
898
+
899
+ /**
900
+ * Updates a record for the given model by its ID.
901
+ *
902
+ * Must be implemented by child classes to provide specific database functionality for record updating,
903
+ * or throw an error if the operation is not supported by the adapter.
904
+ *
905
+ * @param {DoboModel} model - The model instance for which the record is being updated
906
+ * @param {string|number} id - The ID of the record to update
907
+ * @param {object} input - The input data for the update
908
+ * @param {object} options - Additional options that may affect record updating
909
+ * @returns {Promise&lt;object>} - The result of the record update
910
+ */
911
+ async updateRecord (model, id, body = {}, options = {}) {
912
+ throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'updateRecord', this.name)
913
+ }
914
+
915
+ /**
916
+ * Removes a record for the given model by its ID.
917
+ *
918
+ * Must be implemented by child classes to provide specific database functionality for record removal,
919
+ * or throw an error if the operation is not supported by the adapter.
920
+ *
921
+ * @param {DoboModel} model - The model instance for which the record is being removed
922
+ * @param {string|number} id - The ID of the record to remove
923
+ * @param {object} options - Additional options that may affect record removal
924
+ * @returns {Promise&lt;object>} - The result of the record removal
925
+ */
926
+ async removeRecord (model, id, options = {}) {
927
+ throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'removeRecord', this.name)
928
+ }
929
+
930
+ /**
931
+ * Clears all records for the given model.
932
+ *
933
+ * Must be implemented by child classes to provide specific database functionality for record clearing,
934
+ * or throw an error if the operation is not supported by the adapter.
935
+ *
936
+ * @param {DoboModel} model - The model instance for which the records are being cleared
937
+ * @param {object} options - Additional options that may affect record clearing
938
+ * @returns {Promise&lt;object>} - The result of the record clearing
939
+ */
940
+ async clearRecord (model, options = {}) {
941
+ throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'clearRecord', this.name)
942
+ }
943
+
944
+ /**
945
+ * Finds records for the given model based on the provided filter.
946
+ *
947
+ * Must be implemented by child classes to provide specific database functionality for record finding,
948
+ * or throw an error if the operation is not supported by the adapter.
949
+ *
950
+ * @param {DoboModel} model - The model instance for which the records are being found
951
+ * @param {object} filter - The filter criteria for finding records
952
+ * @param {object} options - Additional options that may affect record finding
953
+ * @returns {Promise&lt;object>} - The result of the record finding
954
+ */
955
+ async findRecord (model, filter = {}, options = {}) {
956
+ throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'findRecord', this.name)
957
+ }
958
+
959
+ /**
960
+ * Bulk creates records for the given model.
961
+ *
962
+ * Must be implemented by child classes to provide specific database functionality for bulk record creation,
963
+ * or throw an error if the operation is not supported by the adapter.
964
+ *
965
+ * @param {DoboModel} model - The model instance for which the records are being created
966
+ * @param {Array&lt;object>} bodies - The array of input data for the new records
967
+ * @param {object} options - Additional options that may affect record creation
968
+ * @returns {Promise&lt;void>} - Resolves when the records have been created
969
+ */
970
+ async bulkCreateRecord (model, bodies = [], options = {}) {
971
+ throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'bulkCreateRecord', this.name)
972
+ }
973
+
974
+ /**
975
+ * Counts the records for the given model based on the provided filter.
976
+ *
977
+ * Must be implemented by child classes to provide specific database functionality for record counting,
978
+ * or throw an error if the operation is not supported by the adapter.
979
+ *
980
+ * @param {DoboModel} model - The model instance for which the records are being counted
981
+ * @param {object} filter - The filter criteria for counting records
982
+ * @param {object} options - Additional options that may affect record counting
983
+ * @returns {Promise&lt;object>} - The result of the record counting
984
+ */
985
+ async countRecord (model, filter = {}, options = {}) {
986
+ throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'countRecord', this.name)
987
+ }
988
+
989
+ /**
990
+ * Creates an aggregate for the given model based on the provided filter and parameters.
991
+ *
992
+ * Must be implemented by child classes to provide specific database functionality for aggregate creation,
993
+ * or throw an error if the operation is not supported by the adapter.
994
+ *
995
+ * @param {DoboModel} model - The model instance for which the aggregate is being created
996
+ * @param {object} filter - The filter criteria for creating the aggregate
997
+ * @param {object} params - The parameters for the aggregate creation
998
+ * @param {object} options - Additional options that may affect aggregate creation
999
+ * @returns {Promise&lt;object>} - The result of the aggregate creation
1000
+ */
1001
+ async createAggregate (model, filter = {}, params = {}, options = {}) {
1002
+ throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'createAggregate', this.name)
1003
+ }
1004
+
1005
+ /**
1006
+ * Creates a histogram for the given model based on the provided filter and parameters.
1007
+ *
1008
+ * Must be implemented by child classes to provide specific database functionality for histogram creation,
1009
+ * or throw an error if the operation is not supported by the adapter.
1010
+ *
1011
+ * @param {DoboModel} model - The model instance for which the histogram is being created
1012
+ * @param {object} filter - The filter criteria for creating the histogram
1013
+ * @param {object} params - The parameters for the histogram creation
1014
+ * @param {object} options - Additional options that may affect histogram creation
1015
+ * @returns {Promise&lt;object>} - The result of the histogram creation
1016
+ */
1017
+ async createHistogram (model, filter = {}, params = {}, options = {}) {
1018
+ throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'createHistogram', this.name)
1019
+ }
1020
+
1021
+ /**
1022
+ * Executes a transaction for the given model.
1023
+ *
1024
+ * Must be implemented by child classes to provide specific database functionality for transactions,
1025
+ * or throw an error if the operation is not supported by the adapter.
1026
+ *
1027
+ * @param {DoboModel} model - The model instance for which the transaction is being executed
1028
+ * @param {Function} handler - The transaction handler function
1029
+ * @param {...any} args - Additional arguments for the transaction handler
1030
+ */
1031
+ async transaction (model, handler, ...args) {
1032
+ throw this.plugin.error('notSupportedAdapter%s%s%s', this.app.t('method'), 'transaction', this.name)
1033
+ }
1034
+
1035
+ /**
1036
+ * Disposes of the adapter, performing any necessary cleanup operations.
1037
+ *
1038
+ * @returns {Promise&lt;void>} - Resolves when the adapter has been disposed
1039
+ */
1040
+ async dispose () {
1041
+ await super.dispose()
1042
+ }
1043
+ }
1044
+
1045
+ class DoboNullAdapter extends DoboAdapter {
1046
+ constructor (plugin, name = 'null', options = {}) {
1047
+ super(plugin, name, options)
1048
+ this.memory = true
1049
+ }
1050
+ }
1051
+
1052
+ this.app.baseClass.DoboAdapter = DoboAdapter
1053
+ this.app.baseClass.DoboNullAdapter = DoboNullAdapter
1054
+ }
1055
+
1056
+ export default adapterFactory
1057
+ </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>