@harperfast/harper 5.0.0-alpha.10 → 5.0.0-beta.1

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 (444) hide show
  1. package/bin/BinObjects.js +17 -0
  2. package/bin/cliOperations.js +157 -0
  3. package/bin/copyDb.ts +280 -0
  4. package/bin/harper.js +156 -0
  5. package/bin/install.js +15 -0
  6. package/bin/lite.js +5 -0
  7. package/bin/restart.js +201 -0
  8. package/bin/run.js +409 -0
  9. package/bin/status.js +65 -0
  10. package/bin/stop.js +22 -0
  11. package/bin/upgrade.js +134 -0
  12. package/components/Application.ts +646 -0
  13. package/components/ApplicationScope.ts +49 -0
  14. package/components/Component.ts +53 -0
  15. package/components/ComponentV1.ts +342 -0
  16. package/components/DEFAULT_CONFIG.ts +18 -0
  17. package/components/EntryHandler.ts +227 -0
  18. package/components/Logger.ts +14 -0
  19. package/components/OptionsWatcher.ts +354 -0
  20. package/components/PluginModule.ts +6 -0
  21. package/components/Scope.ts +329 -0
  22. package/components/componentLoader.ts +529 -0
  23. package/components/deriveCommonPatternBase.ts +31 -0
  24. package/components/deriveGlobOptions.ts +44 -0
  25. package/components/deriveURLPath.ts +57 -0
  26. package/components/operations.js +658 -0
  27. package/components/operationsValidation.js +246 -0
  28. package/components/packageComponent.ts +39 -0
  29. package/components/requestRestart.ts +26 -0
  30. package/components/resolveBaseURLPath.ts +38 -0
  31. package/components/status/ComponentStatus.ts +110 -0
  32. package/components/status/ComponentStatusRegistry.ts +251 -0
  33. package/components/status/api.ts +153 -0
  34. package/components/status/crossThread.ts +405 -0
  35. package/components/status/errors.ts +152 -0
  36. package/components/status/index.ts +44 -0
  37. package/components/status/internal.ts +65 -0
  38. package/components/status/registry.ts +12 -0
  39. package/components/status/types.ts +96 -0
  40. package/config/RootConfigWatcher.ts +59 -0
  41. package/config/configHelpers.ts +11 -0
  42. package/config/configUtils.js +967 -0
  43. package/config/harperConfigEnvVars.ts +641 -0
  44. package/dataLayer/CreateAttributeObject.js +25 -0
  45. package/dataLayer/CreateTableObject.js +11 -0
  46. package/dataLayer/DataLayerObjects.js +43 -0
  47. package/dataLayer/DeleteBeforeObject.js +22 -0
  48. package/dataLayer/DeleteObject.js +25 -0
  49. package/dataLayer/DropAttributeObject.js +11 -0
  50. package/dataLayer/GetBackupObject.js +22 -0
  51. package/dataLayer/InsertObject.js +24 -0
  52. package/dataLayer/ReadAuditLogObject.js +24 -0
  53. package/dataLayer/SQLSearch.js +1335 -0
  54. package/dataLayer/SearchByConditionsObject.js +61 -0
  55. package/dataLayer/SearchByHashObject.js +21 -0
  56. package/dataLayer/SearchObject.js +45 -0
  57. package/dataLayer/SqlSearchObject.js +14 -0
  58. package/dataLayer/UpdateObject.js +23 -0
  59. package/dataLayer/UpsertObject.js +23 -0
  60. package/dataLayer/bulkLoad.js +813 -0
  61. package/dataLayer/dataObjects/BulkLoadObjects.js +27 -0
  62. package/dataLayer/dataObjects/UpsertObject.js +23 -0
  63. package/dataLayer/delete.js +164 -0
  64. package/dataLayer/export.js +381 -0
  65. package/dataLayer/getBackup.js +40 -0
  66. package/dataLayer/harperBridge/BridgeMethods.js +81 -0
  67. package/dataLayer/harperBridge/ResourceBridge.ts +633 -0
  68. package/dataLayer/harperBridge/bridgeUtility/insertUpdateReturnObj.js +28 -0
  69. package/dataLayer/harperBridge/bridgeUtility/insertUpdateValidate.js +88 -0
  70. package/dataLayer/harperBridge/harperBridge.js +21 -0
  71. package/dataLayer/harperBridge/lmdbBridge/LMDBBridge.js +119 -0
  72. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +19 -0
  73. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbCreateAttribute.js +112 -0
  74. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbCreateRecords.js +67 -0
  75. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbCreateSchema.js +31 -0
  76. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbCreateTable.js +94 -0
  77. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbDeleteAuditLogsBefore.js +98 -0
  78. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbDeleteRecords.js +89 -0
  79. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbDropAttribute.js +109 -0
  80. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbDropSchema.js +107 -0
  81. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbDropTable.js +137 -0
  82. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbFlush.js +35 -0
  83. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbGetBackup.js +111 -0
  84. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbGetDataByHash.js +28 -0
  85. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbGetDataByValue.js +29 -0
  86. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbReadAuditLog.js +207 -0
  87. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbSearchByConditions.js +156 -0
  88. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbSearchByHash.js +21 -0
  89. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbSearchByValue.js +30 -0
  90. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbTransaction.js +19 -0
  91. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbUpdateRecords.js +64 -0
  92. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbUpsertRecords.js +70 -0
  93. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/LMDBCreateAttributeObject.js +22 -0
  94. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/LMDBDeleteTransactionObject.js +23 -0
  95. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/LMDBInsertTransactionObject.js +22 -0
  96. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/LMDBTransactionObject.js +23 -0
  97. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/LMDBUpdateTransactionObject.js +24 -0
  98. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/LMDBUpsertTransactionObject.js +24 -0
  99. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/TableSizeObject.js +25 -0
  100. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializeHashSearch.js +21 -0
  101. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +157 -0
  102. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbCheckForNewAttributes.js +94 -0
  103. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbCreateTransactionsAuditEnvironment.js +39 -0
  104. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbGetTableSize.js +34 -0
  105. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbProcessRows.js +100 -0
  106. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbSearch.js +371 -0
  107. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbWriteTransaction.js +109 -0
  108. package/dataLayer/hdbInfoController.js +254 -0
  109. package/dataLayer/insert.js +266 -0
  110. package/dataLayer/readAuditLog.js +59 -0
  111. package/dataLayer/schema.js +366 -0
  112. package/dataLayer/schemaDescribe.js +289 -0
  113. package/dataLayer/search.js +60 -0
  114. package/dataLayer/transaction.js +17 -0
  115. package/dataLayer/update.js +124 -0
  116. package/dist/components/Logger.d.ts +12 -0
  117. package/dist/components/Logger.js +3 -0
  118. package/dist/components/Logger.js.map +1 -0
  119. package/dist/components/Scope.d.ts +14 -4
  120. package/dist/components/Scope.js +18 -10
  121. package/dist/components/Scope.js.map +1 -1
  122. package/dist/components/componentLoader.js +16 -9
  123. package/dist/components/componentLoader.js.map +1 -1
  124. package/dist/components/operations.js +2 -2
  125. package/dist/components/operations.js.map +1 -1
  126. package/dist/config/configUtils.d.ts +1 -1
  127. package/dist/config/configUtils.js +1 -1
  128. package/dist/config/configUtils.js.map +1 -1
  129. package/dist/dataLayer/CreateTableObject.d.ts +2 -2
  130. package/dist/dataLayer/CreateTableObject.js +2 -2
  131. package/dist/dataLayer/CreateTableObject.js.map +1 -1
  132. package/dist/dataLayer/delete.d.ts +1 -1
  133. package/dist/dataLayer/schema.js +6 -5
  134. package/dist/dataLayer/schema.js.map +1 -1
  135. package/dist/dataLayer/schemaDescribe.js +1 -1
  136. package/dist/dataLayer/schemaDescribe.js.map +1 -1
  137. package/dist/index.d.ts +1 -1
  138. package/dist/index.js +2 -0
  139. package/dist/index.js.map +1 -1
  140. package/dist/resources/DatabaseTransaction.d.ts +1 -1
  141. package/dist/resources/IterableEventQueue.d.ts +1 -1
  142. package/dist/resources/LMDBTransaction.d.ts +5 -1
  143. package/dist/resources/Resource.d.ts +1 -1
  144. package/dist/resources/RocksIndexStore.d.ts +3 -3
  145. package/dist/resources/RocksTransactionLogStore.d.ts +6 -3
  146. package/dist/resources/Table.d.ts +15 -6
  147. package/dist/resources/Table.js +4 -1
  148. package/dist/resources/Table.js.map +1 -1
  149. package/dist/resources/analytics/read.js +32 -22
  150. package/dist/resources/analytics/read.js.map +1 -1
  151. package/dist/resources/analytics/write.js +3 -6
  152. package/dist/resources/analytics/write.js.map +1 -1
  153. package/dist/resources/auditStore.d.ts +3 -3
  154. package/dist/resources/blob.d.ts +25 -2
  155. package/dist/resources/databases.d.ts +12 -2
  156. package/dist/resources/databases.js +22 -19
  157. package/dist/resources/databases.js.map +1 -1
  158. package/dist/resources/search.js +11 -5
  159. package/dist/resources/search.js.map +1 -1
  160. package/dist/resources/transaction.d.ts +2 -1
  161. package/dist/security/auth.js +1 -1
  162. package/dist/security/auth.js.map +1 -1
  163. package/dist/security/cryptoHash.d.ts +2 -2
  164. package/dist/security/jsLoader.js +243 -66
  165. package/dist/security/jsLoader.js.map +1 -1
  166. package/dist/security/keys.js +4 -5
  167. package/dist/security/keys.js.map +1 -1
  168. package/dist/security/user.js +3 -3
  169. package/dist/security/user.js.map +1 -1
  170. package/dist/server/REST.js +16 -2
  171. package/dist/server/REST.js.map +1 -1
  172. package/dist/server/Server.d.ts +2 -1
  173. package/dist/server/Server.js.map +1 -1
  174. package/dist/server/fastifyRoutes/plugins/hdbCore.d.ts +6 -1
  175. package/dist/server/fastifyRoutes.js +2 -0
  176. package/dist/server/fastifyRoutes.js.map +1 -1
  177. package/dist/server/http.js +12 -6
  178. package/dist/server/http.js.map +1 -1
  179. package/dist/server/jobs/JobObject.d.ts +3 -3
  180. package/dist/server/loadRootComponents.js +1 -0
  181. package/dist/server/loadRootComponents.js.map +1 -1
  182. package/dist/server/operationsServer.js +3 -1
  183. package/dist/server/operationsServer.js.map +1 -1
  184. package/dist/server/serverHelpers/JSONStream.d.ts +3 -3
  185. package/dist/server/serverHelpers/Request.d.ts +5 -5
  186. package/dist/server/serverHelpers/requestTimePlugin.d.ts +1 -1
  187. package/dist/server/threads/manageThreads.d.ts +2 -2
  188. package/dist/server/threads/manageThreads.js +50 -35
  189. package/dist/server/threads/manageThreads.js.map +1 -1
  190. package/dist/server/threads/socketRouter.d.ts +1 -1
  191. package/dist/sqlTranslator/deleteTranslator.d.ts +1 -1
  192. package/dist/utility/AWS/AWSConnector.d.ts +3 -2
  193. package/dist/utility/common_utils.d.ts +3 -3
  194. package/dist/utility/environment/systemInformation.d.ts +1 -0
  195. package/dist/utility/functions/date/dateFunctions.d.ts +11 -11
  196. package/dist/utility/globalSchema.d.ts +1 -1
  197. package/dist/utility/hdbTerms.d.ts +3 -0
  198. package/dist/utility/hdbTerms.js +3 -0
  199. package/dist/utility/hdbTerms.js.map +1 -1
  200. package/dist/utility/installation.d.ts +2 -4
  201. package/dist/utility/installation.js.map +1 -1
  202. package/dist/utility/lmdb/commonUtility.d.ts +1 -0
  203. package/dist/utility/lmdb/deleteUtility.d.ts +1 -0
  204. package/dist/utility/lmdb/environmentUtility.d.ts +1 -0
  205. package/dist/utility/lmdb/searchUtility.d.ts +2 -1
  206. package/dist/utility/lmdb/writeUtility.d.ts +1 -0
  207. package/dist/utility/logging/harper_logger.d.ts +6 -6
  208. package/dist/utility/processManagement/processManagement.d.ts +1 -1
  209. package/dist/utility/processManagement/servicesConfig.d.ts +12 -6
  210. package/dist/validation/common_validators.d.ts +4 -3
  211. package/dist/validation/configValidator.d.ts +3 -2
  212. package/index.d.ts +56 -0
  213. package/index.js +41 -0
  214. package/json/systemSchema.json +373 -0
  215. package/launchServiceScripts/launchHarperDB.js +3 -0
  216. package/launchServiceScripts/utility/checkNodeVersion.js +15 -0
  217. package/package.json +21 -3
  218. package/resources/DatabaseTransaction.ts +378 -0
  219. package/resources/ErrorResource.ts +57 -0
  220. package/resources/IterableEventQueue.ts +94 -0
  221. package/resources/LMDBTransaction.ts +349 -0
  222. package/resources/RecordEncoder.ts +702 -0
  223. package/resources/RequestTarget.ts +134 -0
  224. package/resources/Resource.ts +789 -0
  225. package/resources/ResourceInterface.ts +221 -0
  226. package/resources/ResourceInterfaceV2.ts +53 -0
  227. package/resources/ResourceV2.ts +67 -0
  228. package/resources/Resources.ts +162 -0
  229. package/resources/RocksIndexStore.ts +70 -0
  230. package/resources/RocksTransactionLogStore.ts +352 -0
  231. package/resources/Table.ts +4527 -0
  232. package/resources/analytics/hostnames.ts +72 -0
  233. package/resources/analytics/metadata.ts +10 -0
  234. package/resources/analytics/read.ts +252 -0
  235. package/resources/analytics/write.ts +803 -0
  236. package/resources/auditStore.ts +556 -0
  237. package/resources/blob.ts +1268 -0
  238. package/resources/crdt.ts +125 -0
  239. package/resources/dataLoader.ts +527 -0
  240. package/resources/databases.ts +1290 -0
  241. package/resources/graphql.ts +221 -0
  242. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +638 -0
  243. package/resources/indexes/customIndexes.ts +7 -0
  244. package/resources/indexes/vector.ts +38 -0
  245. package/resources/jsResource.ts +86 -0
  246. package/resources/loadEnv.ts +22 -0
  247. package/resources/login.ts +18 -0
  248. package/resources/openApi.ts +409 -0
  249. package/resources/registrationDeprecated.ts +8 -0
  250. package/resources/replayLogs.ts +136 -0
  251. package/resources/roles.ts +98 -0
  252. package/resources/search.ts +1301 -0
  253. package/resources/tracked.ts +584 -0
  254. package/resources/transaction.ts +89 -0
  255. package/resources/transactionBroadcast.ts +258 -0
  256. package/security/auth.ts +376 -0
  257. package/security/certificateVerification/certificateVerificationSource.ts +84 -0
  258. package/security/certificateVerification/configValidation.ts +107 -0
  259. package/security/certificateVerification/crlVerification.ts +623 -0
  260. package/security/certificateVerification/index.ts +121 -0
  261. package/security/certificateVerification/ocspVerification.ts +148 -0
  262. package/security/certificateVerification/pkijs-ed25519-patch.ts +188 -0
  263. package/security/certificateVerification/types.ts +128 -0
  264. package/security/certificateVerification/verificationConfig.ts +138 -0
  265. package/security/certificateVerification/verificationUtils.ts +447 -0
  266. package/security/cryptoHash.js +42 -0
  267. package/security/data_objects/PermissionAttributeResponseObject.js +15 -0
  268. package/security/data_objects/PermissionResponseObject.js +115 -0
  269. package/security/data_objects/PermissionTableResponseObject.js +20 -0
  270. package/security/fastifyAuth.js +169 -0
  271. package/security/impersonation.ts +160 -0
  272. package/security/jsLoader.ts +716 -0
  273. package/security/keys.js +948 -0
  274. package/security/permissionsTranslator.js +300 -0
  275. package/security/role.js +218 -0
  276. package/security/tokenAuthentication.ts +228 -0
  277. package/security/user.ts +449 -0
  278. package/server/DurableSubscriptionsSession.ts +503 -0
  279. package/server/REST.ts +407 -0
  280. package/server/Server.ts +89 -0
  281. package/server/fastifyRoutes/helpers/getCORSOptions.js +36 -0
  282. package/server/fastifyRoutes/helpers/getHeaderTimeoutConfig.js +15 -0
  283. package/server/fastifyRoutes/helpers/getServerOptions.js +33 -0
  284. package/server/fastifyRoutes/plugins/hdbCore.js +39 -0
  285. package/server/fastifyRoutes.ts +205 -0
  286. package/server/graphqlQuerying.ts +700 -0
  287. package/server/http.ts +640 -0
  288. package/server/itc/serverHandlers.js +161 -0
  289. package/server/itc/utility/ITCEventObject.js +10 -0
  290. package/server/jobs/JobObject.js +24 -0
  291. package/server/jobs/jobProcess.js +69 -0
  292. package/server/jobs/jobRunner.js +162 -0
  293. package/server/jobs/jobs.js +304 -0
  294. package/server/loadRootComponents.js +44 -0
  295. package/server/mqtt.ts +485 -0
  296. package/server/nodeName.ts +75 -0
  297. package/server/operationsServer.ts +313 -0
  298. package/server/serverHelpers/Headers.ts +108 -0
  299. package/server/serverHelpers/JSONStream.ts +269 -0
  300. package/server/serverHelpers/OperationFunctionObject.ts +13 -0
  301. package/server/serverHelpers/Request.ts +158 -0
  302. package/server/serverHelpers/contentTypes.ts +637 -0
  303. package/server/serverHelpers/requestTimePlugin.js +57 -0
  304. package/server/serverHelpers/serverHandlers.js +148 -0
  305. package/server/serverHelpers/serverUtilities.ts +473 -0
  306. package/server/serverRegistry.ts +8 -0
  307. package/server/static.ts +187 -0
  308. package/server/status/definitions.ts +37 -0
  309. package/server/status/index.ts +125 -0
  310. package/server/storageReclamation.ts +93 -0
  311. package/server/threads/itc.js +89 -0
  312. package/server/threads/manageThreads.js +594 -0
  313. package/server/threads/socketRouter.ts +360 -0
  314. package/server/threads/threadServer.js +279 -0
  315. package/server/throttle.ts +73 -0
  316. package/sqlTranslator/SelectValidator.js +330 -0
  317. package/sqlTranslator/alasqlFunctionImporter.js +62 -0
  318. package/sqlTranslator/deleteTranslator.js +67 -0
  319. package/sqlTranslator/index.js +242 -0
  320. package/sqlTranslator/sql_statement_bucket.js +472 -0
  321. package/static/defaultConfig.yaml +3 -0
  322. package/studio/web/HDBDogOnly.svg +78 -0
  323. package/studio/web/assets/PPRadioGrotesk-Bold-DDaUYG8E.woff +0 -0
  324. package/studio/web/assets/fa-brands-400-CEJbCg16.woff +0 -0
  325. package/studio/web/assets/fa-brands-400-CSYNqBb_.ttf +0 -0
  326. package/studio/web/assets/fa-brands-400-DnkPfk3o.eot +0 -0
  327. package/studio/web/assets/fa-brands-400-UxlILjvJ.woff2 +0 -0
  328. package/studio/web/assets/fa-brands-400-cH1MgKbP.svg +3717 -0
  329. package/studio/web/assets/fa-regular-400-BhTwtT8w.eot +0 -0
  330. package/studio/web/assets/fa-regular-400-D1vz6WBx.ttf +0 -0
  331. package/studio/web/assets/fa-regular-400-DFnMcJPd.woff +0 -0
  332. package/studio/web/assets/fa-regular-400-DGzu1beS.woff2 +0 -0
  333. package/studio/web/assets/fa-regular-400-gwj8Pxq-.svg +801 -0
  334. package/studio/web/assets/fa-solid-900-B4ZZ7kfP.svg +5034 -0
  335. package/studio/web/assets/fa-solid-900-B6Axprfb.eot +0 -0
  336. package/studio/web/assets/fa-solid-900-BUswJgRo.woff2 +0 -0
  337. package/studio/web/assets/fa-solid-900-DOXgCApm.woff +0 -0
  338. package/studio/web/assets/fa-solid-900-mxuxnBEa.ttf +0 -0
  339. package/studio/web/assets/index-BTgXJX9d.js +235 -0
  340. package/studio/web/assets/index-BTgXJX9d.js.map +1 -0
  341. package/studio/web/assets/index-C-GXfcup.js +37 -0
  342. package/studio/web/assets/index-C-GXfcup.js.map +1 -0
  343. package/studio/web/assets/index-PFlNdimM.js +2 -0
  344. package/studio/web/assets/index-PFlNdimM.js.map +1 -0
  345. package/studio/web/assets/index-Y2g_iFpU.css +1 -0
  346. package/studio/web/assets/index-jiPwkrsB.css +1 -0
  347. package/studio/web/assets/index.lazy-C3TJZJ4o.js +266 -0
  348. package/studio/web/assets/index.lazy-C3TJZJ4o.js.map +1 -0
  349. package/studio/web/assets/profiler-DotzgiCJ.js +2 -0
  350. package/studio/web/assets/profiler-DotzgiCJ.js.map +1 -0
  351. package/studio/web/assets/react-redux-VxUEx_mU.js +6 -0
  352. package/studio/web/assets/react-redux-VxUEx_mU.js.map +1 -0
  353. package/studio/web/assets/startRecording-B_9J9Csd.js +3 -0
  354. package/studio/web/assets/startRecording-B_9J9Csd.js.map +1 -0
  355. package/studio/web/fabric-signup-background.webp +0 -0
  356. package/studio/web/fabric-signup-text.png +0 -0
  357. package/studio/web/favicon_purple.png +0 -0
  358. package/studio/web/github-icon.svg +15 -0
  359. package/studio/web/harper-fabric_black.png +0 -0
  360. package/studio/web/harper-fabric_white.png +0 -0
  361. package/studio/web/harper-studio_white.png +0 -0
  362. package/studio/web/index.html +16 -0
  363. package/studio/web/running.css +148 -0
  364. package/studio/web/running.html +147 -0
  365. package/studio/web/running.js +111 -0
  366. package/upgrade/UpgradeObjects.js +13 -0
  367. package/upgrade/directives/directivesController.js +90 -0
  368. package/upgrade/directivesManager.js +139 -0
  369. package/upgrade/upgradePrompt.js +124 -0
  370. package/upgrade/upgradeUtilities.js +28 -0
  371. package/utility/AWS/AWSConnector.js +29 -0
  372. package/utility/OperationFunctionCaller.js +63 -0
  373. package/utility/assignCmdEnvVariables.js +62 -0
  374. package/utility/common_utils.js +867 -0
  375. package/utility/environment/environmentManager.js +208 -0
  376. package/utility/environment/systemInformation.js +355 -0
  377. package/utility/errors/commonErrors.js +267 -0
  378. package/utility/errors/hdbError.js +146 -0
  379. package/utility/functions/date/dateFunctions.js +65 -0
  380. package/utility/functions/geo.js +355 -0
  381. package/utility/functions/sql/alaSQLExtension.js +104 -0
  382. package/utility/globalSchema.js +35 -0
  383. package/utility/hdbTerms.ts +819 -0
  384. package/utility/install/checkJWTTokensExist.js +62 -0
  385. package/utility/install/harperdb.conf +15 -0
  386. package/utility/install/harperdb.service +14 -0
  387. package/utility/install/installer.js +635 -0
  388. package/utility/installation.ts +30 -0
  389. package/utility/lmdb/DBIDefinition.js +20 -0
  390. package/utility/lmdb/DeleteRecordsResponseObject.js +25 -0
  391. package/utility/lmdb/InsertRecordsResponseObject.js +22 -0
  392. package/utility/lmdb/OpenDBIObject.js +31 -0
  393. package/utility/lmdb/OpenEnvironmentObject.js +41 -0
  394. package/utility/lmdb/UpdateRecordsResponseObject.js +25 -0
  395. package/utility/lmdb/UpsertRecordsResponseObject.js +22 -0
  396. package/utility/lmdb/cleanLMDBMap.js +65 -0
  397. package/utility/lmdb/commonUtility.js +119 -0
  398. package/utility/lmdb/deleteUtility.js +128 -0
  399. package/utility/lmdb/environmentUtility.js +477 -0
  400. package/utility/lmdb/searchCursorFunctions.js +187 -0
  401. package/utility/lmdb/searchUtility.js +918 -0
  402. package/utility/lmdb/terms.js +57 -0
  403. package/utility/lmdb/writeUtility.js +407 -0
  404. package/utility/logging/harper_logger.js +876 -0
  405. package/utility/logging/logRotator.js +157 -0
  406. package/utility/logging/logger.ts +24 -0
  407. package/utility/logging/readLog.js +355 -0
  408. package/utility/logging/transactionLog.js +57 -0
  409. package/utility/mount_hdb.js +59 -0
  410. package/utility/npmUtilities.js +102 -0
  411. package/utility/operationPermissions.ts +112 -0
  412. package/utility/operation_authorization.js +836 -0
  413. package/utility/packageUtils.js +55 -0
  414. package/utility/password.ts +99 -0
  415. package/utility/processManagement/processManagement.js +187 -0
  416. package/utility/processManagement/servicesConfig.js +56 -0
  417. package/utility/scripts/restartHdb.js +24 -0
  418. package/utility/scripts/user_data.sh +13 -0
  419. package/utility/signalling.js +36 -0
  420. package/utility/terms/certificates.js +81 -0
  421. package/utility/when.ts +20 -0
  422. package/v1.d.ts +39 -0
  423. package/v1.js +41 -0
  424. package/v2.d.ts +39 -0
  425. package/v2.js +41 -0
  426. package/validation/bulkDeleteValidator.js +24 -0
  427. package/validation/check_permissions.js +19 -0
  428. package/validation/common_validators.js +95 -0
  429. package/validation/configValidator.js +331 -0
  430. package/validation/deleteValidator.js +15 -0
  431. package/validation/fileLoadValidator.js +153 -0
  432. package/validation/insertValidator.js +40 -0
  433. package/validation/installValidator.js +37 -0
  434. package/validation/readLogValidator.js +64 -0
  435. package/validation/role_validation.js +320 -0
  436. package/validation/schemaMetadataValidator.js +42 -0
  437. package/validation/searchValidator.js +166 -0
  438. package/validation/statusValidator.ts +66 -0
  439. package/validation/transactionLogValidator.js +33 -0
  440. package/validation/user_validation.js +55 -0
  441. package/validation/validationWrapper.js +105 -0
  442. package/dist/resources/analytics/profile.d.ts +0 -2
  443. package/dist/resources/analytics/profile.js +0 -144
  444. package/dist/resources/analytics/profile.js.map +0 -1
@@ -0,0 +1,1301 @@
1
+ import { ClientError, ServerError, Violation } from '../utility/errors/hdbError.js';
2
+ import { OVERFLOW_MARKER, MAX_SEARCH_KEY_LENGTH, SEARCH_TYPES } from '../utility/lmdb/terms.js';
3
+ import { compareKeys, MAXIMUM_KEY } from 'ordered-binary';
4
+ import { SKIP } from '@harperfast/extended-iterable';
5
+ import { INVALIDATED, EVICTED } from './Table.ts';
6
+ import type { DirectCondition, Id } from './ResourceInterface.ts';
7
+ import { RequestTarget } from './RequestTarget.ts';
8
+ import { lastMetadata } from './RecordEncoder.ts';
9
+ import { recordAction } from './analytics/write';
10
+
11
+ // these are ratios/percentages of overall table size
12
+ const OPEN_RANGE_ESTIMATE = 0.3;
13
+ const BETWEEN_ESTIMATE = 0.1;
14
+ const STARTS_WITH_ESTIMATE = 0.05;
15
+
16
+ const SYMBOL_OPERATORS = {
17
+ // these are coercing operators
18
+ '<': 'lt',
19
+ '<=': 'le',
20
+ '>': 'gt',
21
+ '>=': 'ge',
22
+ '!=': 'ne',
23
+ '==': 'eq',
24
+ // these are strict operators:
25
+ '===': 'equals',
26
+ '!==': 'not_equal',
27
+ };
28
+ export const COERCIBLE_OPERATORS = {
29
+ lt: true,
30
+ le: true,
31
+ gt: true,
32
+ ge: true,
33
+ ne: true,
34
+ eq: true,
35
+ };
36
+ export function executeConditions(conditions, operator, table, txn, request, context, transformToEntries, filtered) {
37
+ const firstSearch = conditions[0];
38
+ // both AND and OR start by getting an iterator for the ids for first condition
39
+ // and then things diverge...
40
+ if (operator === 'or') {
41
+ let results = executeCondition(firstSearch);
42
+ //get the union of ids from all condition searches
43
+ for (let i = 1; i < conditions.length; i++) {
44
+ const condition = conditions[i];
45
+ // might want to lazily execute this after getting to this point in the iteration
46
+ const nextResults = executeCondition(condition);
47
+ results = results.concat(nextResults);
48
+ }
49
+ const returnedIds = new Set();
50
+ return results.filter((entry) => {
51
+ const id = entry.key ?? entry;
52
+ if (returnedIds.has(id))
53
+ // skip duplicate ids
54
+ return false;
55
+ returnedIds.add(id);
56
+ return true;
57
+ });
58
+ } else {
59
+ // AND
60
+ const results = executeCondition(firstSearch);
61
+ // get the intersection of condition searches by using the indexed query for the first condition
62
+ // and then filtering by all subsequent conditions.
63
+ // now apply filters that require looking up records
64
+ const filters = mapConditionsToFilters(conditions.slice(1), true, firstSearch.estimated_count);
65
+ return filters.length > 0 ? transformToEntries(results, filters) : results;
66
+ }
67
+ function executeCondition(condition) {
68
+ if (condition.conditions)
69
+ return executeConditions(
70
+ condition.conditions,
71
+ condition.operator,
72
+ table,
73
+ txn,
74
+ request,
75
+ context,
76
+ transformToEntries,
77
+ filtered
78
+ );
79
+ return searchByIndex(
80
+ condition,
81
+ txn,
82
+ condition.descending || request.reverse === true,
83
+ table,
84
+ request.allowFullScan,
85
+ filtered,
86
+ context
87
+ );
88
+ }
89
+ function mapConditionsToFilters(conditions, intersection, estimatedIncomingCount) {
90
+ return conditions
91
+ .map((condition, index) => {
92
+ if (condition.conditions) {
93
+ // this is a group of conditions, we need to combine them
94
+ const union = condition.operator === 'or';
95
+ const filters = mapConditionsToFilters(condition.conditions, !union, estimatedIncomingCount);
96
+ if (union) return (record, entry) => filters.some((filter) => filter(record, entry));
97
+ else return (record, entry) => filters.every((filter) => filter(record, entry));
98
+ }
99
+ const isPrimaryKey = (condition.attribute || condition[0]) === table.primaryKey;
100
+ const filter = filterByType(condition, table, context, filtered, isPrimaryKey, estimatedIncomingCount);
101
+ if (intersection && index < conditions.length - 1 && estimatedIncomingCount) {
102
+ estimatedIncomingCount = intersectionEstimate(
103
+ table.primaryStore,
104
+ condition.estimated_count,
105
+ estimatedIncomingCount
106
+ );
107
+ }
108
+ return filter;
109
+ })
110
+ .filter(Boolean);
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Search for records or keys, based on the search condition, using an index if available
116
+ * @param searchCondition
117
+ * @param transaction
118
+ * @param reverse
119
+ * @param Table
120
+ * @param allowFullScan
121
+ * @param filtered
122
+ */
123
+ export function searchByIndex(
124
+ searchCondition: DirectCondition,
125
+ transaction: any,
126
+ reverse: boolean,
127
+ Table: any,
128
+ allowFullScan?: boolean,
129
+ filtered?: boolean,
130
+ context?: any
131
+ ): AsyncIterable<Id | { key: Id; value: any }> {
132
+ let attribute_name = searchCondition[0] ?? searchCondition.attribute;
133
+ let value = searchCondition[1] ?? searchCondition.value;
134
+ const comparator = searchCondition.comparator;
135
+ if (value === undefined && comparator !== 'sort') {
136
+ throw new ClientError(`Search condition for ${attribute_name} must have a value`);
137
+ }
138
+ if (Array.isArray(attribute_name)) {
139
+ const firstAttributeName = attribute_name[0];
140
+ // get the potential relationship attribute
141
+ const attribute = findAttribute(Table.attributes, firstAttributeName);
142
+ if (attribute.relationship) {
143
+ // it is a join/relational query
144
+ if (attribute_name.length < 2)
145
+ throw new ClientError(
146
+ 'Can not directly query a relational attribute, must query an attribute within the target table'
147
+ );
148
+ const relatedTable = attribute.definition?.tableClass || attribute.elements?.definition?.tableClass;
149
+ const joined = new Map();
150
+ // search the related table
151
+ let results = searchByIndex(
152
+ {
153
+ attribute: attribute_name.length > 2 ? attribute_name.slice(1) : attribute_name[1],
154
+ value,
155
+ comparator,
156
+ },
157
+ transaction,
158
+ reverse,
159
+ relatedTable,
160
+ allowFullScan,
161
+ joined
162
+ );
163
+ if (attribute.relationship.to) {
164
+ // this is one-to-many or many-to-many, so we need to track the filtering of related entries that match
165
+ filtered[attribute_name[0]] = joined;
166
+ // Use the joinTo to join the results of the related table to the current table (can be one-to-many or many-to-many)
167
+ const isManyToMany = Boolean(findAttribute(relatedTable.attributes, attribute.relationship.to)?.elements);
168
+ results = joinTo(results, attribute, relatedTable.primaryStore, isManyToMany, joined);
169
+ }
170
+ if (attribute.relationship.from) {
171
+ const searchEntry = (relatedEntry) => {
172
+ if (relatedEntry?.key !== undefined) relatedEntry = relatedEntry.key;
173
+ return searchByIndex(
174
+ { attribute: attribute.relationship.from, value: relatedEntry },
175
+ transaction,
176
+ reverse,
177
+ Table,
178
+ allowFullScan,
179
+ joined
180
+ );
181
+ };
182
+ if (attribute.elements) {
183
+ filtered[attribute_name[0]] = joined;
184
+ // many-to-many relationship (forward), get all the ids first
185
+ results = joinFrom(results, attribute, relatedTable.primaryStore, joined, searchEntry);
186
+ } else {
187
+ // many-to-one relationship, need to flatten the ids that point back to potentially many instances of this
188
+ results = results.flatMap(searchEntry);
189
+ }
190
+ }
191
+ return results;
192
+ } else if (attribute_name.length === 1) {
193
+ attribute_name = attribute_name[0];
194
+ } else {
195
+ throw new ClientError('Unable to query by attribute ' + JSON.stringify(attribute_name));
196
+ }
197
+ }
198
+ const isPrimaryKey = attribute_name === Table.primaryKey || attribute_name == null;
199
+ const index = isPrimaryKey ? Table.primaryStore : Table.indices[attribute_name];
200
+ let start;
201
+ let end, inclusiveEnd, exclusiveStart;
202
+ if (value instanceof Date) value = value.getTime();
203
+ let needFullScan;
204
+ switch (ALTERNATE_COMPARATOR_NAMES[comparator] || comparator) {
205
+ case 'lt':
206
+ start = true;
207
+ end = value;
208
+ break;
209
+ case 'le':
210
+ start = true;
211
+ end = value;
212
+ inclusiveEnd = true;
213
+ break;
214
+ case 'gt':
215
+ start = value;
216
+ exclusiveStart = true;
217
+ break;
218
+ case 'ge':
219
+ start = value;
220
+ break;
221
+ case 'prefix': // this is form finding multi-part keys that start with the provided prefix
222
+ // this search needs to be of the form:
223
+ // start: [prefix, null], end: [prefix, MAXIMUM_KEY]
224
+ if (!Array.isArray(value)) value = [value, null];
225
+ else if (value[value.length - 1] != null) value = value.concat(null);
226
+ start = value;
227
+ end = value.slice(0);
228
+ end[end.length - 1] = MAXIMUM_KEY;
229
+ break;
230
+ case 'starts_with':
231
+ start = value.toString();
232
+ end = value + String.fromCharCode(0xffff);
233
+ break;
234
+ case 'between':
235
+ case 'gele':
236
+ case 'gelt':
237
+ case 'gtlt':
238
+ case 'gtle':
239
+ start = value[0];
240
+ if (start instanceof Date) start = start.getTime();
241
+ end = value[1];
242
+ if (end instanceof Date) end = end.getTime();
243
+ inclusiveEnd = comparator === 'gele' || comparator === 'gtle' || comparator === 'between';
244
+ exclusiveStart = comparator === 'gtlt' || comparator === 'gtle';
245
+ break;
246
+ case 'equals':
247
+ case undefined:
248
+ start = value;
249
+ end = value;
250
+ inclusiveEnd = true;
251
+ break;
252
+ case 'ne':
253
+ if (value === null) {
254
+ // since null is the lowest value in an index, we can treat anything higher as a non-null
255
+ start = value;
256
+ exclusiveStart = true;
257
+ break;
258
+ }
259
+ case 'sort': // this is a special case for when we want to get all records for sorting
260
+ case 'contains':
261
+ case 'ends_with':
262
+ // we have to revert to full table scan here
263
+ start = true;
264
+ needFullScan = true;
265
+ break;
266
+ default:
267
+ throw new ClientError(`Unknown query comparator "${comparator}"`);
268
+ }
269
+ let filter;
270
+ if (typeof start === 'string' && start.length > MAX_SEARCH_KEY_LENGTH) {
271
+ // if the key is too long, we need to truncate it and filter the results
272
+ start = start.slice(0, MAX_SEARCH_KEY_LENGTH) + OVERFLOW_MARKER;
273
+ exclusiveStart = false;
274
+ filter = filterByType(searchCondition, Table, null, filtered, isPrimaryKey);
275
+ }
276
+ if (typeof end === 'string' && end.length > MAX_SEARCH_KEY_LENGTH) {
277
+ // if the key is too long, we need to truncate it and filter the results
278
+ end = end.slice(0, MAX_SEARCH_KEY_LENGTH) + OVERFLOW_MARKER;
279
+ inclusiveEnd = true;
280
+ filter = filter ?? filterByType(searchCondition, Table, null, filtered, isPrimaryKey);
281
+ }
282
+ if (reverse) {
283
+ let newEnd = start;
284
+ start = end;
285
+ end = newEnd;
286
+ newEnd = !exclusiveStart;
287
+ exclusiveStart = !inclusiveEnd;
288
+ inclusiveEnd = newEnd;
289
+ }
290
+ if (!index || index.isIndexing || needFullScan || (value === null && !index.indexNulls)) {
291
+ // no indexed searching available, need a full scan
292
+ if (allowFullScan === false && !index)
293
+ throw new ClientError(`"${attribute_name}" is not indexed, can not search for this attribute`, 404);
294
+ if (allowFullScan === false && needFullScan)
295
+ throw new ClientError(
296
+ `Can not use ${
297
+ comparator || 'equal'
298
+ } operator without combining with a condition that uses an index, can not search for attribute ${attribute_name}`,
299
+ 403
300
+ );
301
+ if (index?.isIndexing)
302
+ throw new ServerError(`"${attribute_name}" is not indexed yet, can not search for this attribute`, 503);
303
+ if (value === null && index && !index.indexNulls)
304
+ throw new ClientError(
305
+ `"${attribute_name}" is not indexed for nulls, index needs to be rebuilt to search for nulls, can not search for this attribute`,
306
+ 400
307
+ );
308
+ filter = filter ?? filterByType(searchCondition, Table, null, filtered, isPrimaryKey);
309
+ if (!filter) {
310
+ throw new ClientError(`Unknown search operator ${searchCondition.comparator}`);
311
+ }
312
+ }
313
+ const rangeOptions = {
314
+ start,
315
+ end,
316
+ inclusiveEnd,
317
+ exclusiveStart,
318
+ values: true,
319
+ versions: isPrimaryKey,
320
+ transaction,
321
+ reverse,
322
+ };
323
+ if (isPrimaryKey) {
324
+ const results = index.getRange(rangeOptions).map(
325
+ filter
326
+ ? function ({ key, value }) {
327
+ if (this?.isSync) return value && filter(value) ? key : SKIP;
328
+ // for filter operations, we intentionally yield the event turn so that scanning queries
329
+ // do not hog resources
330
+ return new Promise((resolve, reject) =>
331
+ setImmediate(() => {
332
+ try {
333
+ resolve(value && filter(value) ? key : SKIP);
334
+ } catch (error) {
335
+ reject(error);
336
+ }
337
+ })
338
+ );
339
+ }
340
+ : function (entry) {
341
+ let result: any;
342
+ if (entry.value == null && !(entry.metadataFlags & (INVALIDATED | EVICTED))) result = SKIP;
343
+ else {
344
+ Object.freeze(entry.value);
345
+ recordRead(entry);
346
+ result = entry;
347
+ }
348
+ if (true || this.isSync) return result;
349
+ return new Promise((resolve) => setImmediate(() => resolve(result)));
350
+ }
351
+ );
352
+ results.hasEntries = true;
353
+ return results;
354
+ } else if (index) {
355
+ if (index.customIndex) {
356
+ return index.customIndex.search(searchCondition, context).map((entry) => {
357
+ // if the custom index returns an entry with metadata, merge it with the loaded entry
358
+ if (typeof entry === 'object' && entry) {
359
+ const { key, ...otherProps } = entry;
360
+ const loadedEntry = Table.primaryStore.getEntry(key);
361
+ Object.freeze(loadedEntry?.value);
362
+ recordRead(loadedEntry);
363
+ return { ...otherProps, ...loadedEntry };
364
+ }
365
+ return entry;
366
+ });
367
+ }
368
+ return index.getRange(rangeOptions).map(
369
+ filter
370
+ ? function ({ key, value }) {
371
+ let recordMatcher: any;
372
+ if (typeof key === 'string' && key.length > MAX_SEARCH_KEY_LENGTH) {
373
+ // if it is an overflow string, need to get the actual value from the database
374
+ recordMatcher = Table.primaryStore.getSync(value);
375
+ } else recordMatcher = { [attribute_name]: key };
376
+ if (this.isSync) return filter(recordMatcher) ? value : SKIP;
377
+ // for filter operations, we intentionally yield the event turn so that scanning queries
378
+ // do not hog resources
379
+ return new Promise((resolve, reject) =>
380
+ setImmediate(() => {
381
+ try {
382
+ resolve(filter(recordMatcher) ? value : SKIP);
383
+ } catch (error) {
384
+ reject(error);
385
+ }
386
+ })
387
+ );
388
+ }
389
+ : ({ value }) => value
390
+ );
391
+ } else {
392
+ return Table.primaryStore
393
+ .getRange(reverse ? { end: true, transaction, reverse: true } : { start: true, transaction })
394
+ .map(function (entry) {
395
+ const { key, value } = entry;
396
+ if (this.isSync) {
397
+ recordRead(entry);
398
+ return value && filter(value) ? key : SKIP;
399
+ }
400
+ // for filter operations, we intentionally yield the event turn so that scanning queries
401
+ // do not hog resources
402
+ return new Promise((resolve, reject) =>
403
+ setImmediate(() => {
404
+ try {
405
+ recordRead(entry);
406
+ resolve(value && filter(value) ? key : SKIP);
407
+ } catch (error) {
408
+ reject(error);
409
+ }
410
+ })
411
+ );
412
+ });
413
+ }
414
+ function recordRead(entry) {
415
+ if ((Table.databaseName !== 'system' || Table.name === 'hdb_analytics') && entry?.value) {
416
+ recordAction(entry.size ?? 1, 'db-read', Table.name, null);
417
+ }
418
+ }
419
+ }
420
+
421
+ export function findAttribute(attributes, attribute_name) {
422
+ if (Array.isArray(attribute_name)) {
423
+ if (attribute_name.length > 1) {
424
+ const firstAttribute = findAttribute(attributes, attribute_name[0]);
425
+ const nextAttributes =
426
+ (firstAttribute?.definition?.tableClass || firstAttribute?.elements?.definition?.tableClass)?.attributes ??
427
+ firstAttribute?.properties;
428
+ if (nextAttributes) return findAttribute(nextAttributes, attribute_name.slice(1));
429
+ return;
430
+ } else attribute_name = attribute_name.toString();
431
+ } else if (typeof attribute_name !== 'string') attribute_name = attribute_name.toString();
432
+ return attributes.find((attribute) => attribute.name === attribute_name);
433
+ }
434
+
435
+ /**
436
+ * This is used to join the results of a query where the right side is a set of records with the foreign key that
437
+ * points to the left side (from right to left)
438
+ * @param rightIterable
439
+ * @param attribute
440
+ * @param store
441
+ * @param isManyToMany
442
+ * @param joined
443
+ * @returns
444
+ */
445
+ function joinTo(rightIterable, attribute, store, isManyToMany, joined: Map<any, any[]>) {
446
+ return new rightIterable.constructor({
447
+ [Symbol.iterator]() {
448
+ let joinedIterator;
449
+ joined.hasMappings = true;
450
+ return {
451
+ next() {
452
+ if (!joinedIterator) {
453
+ const rightProperty = attribute.relationship.to;
454
+ const addEntry = (key, entry) => {
455
+ let entriesForKey = joined.get(key);
456
+ if (entriesForKey) entriesForKey.push(entry);
457
+ else joined.set(key, (entriesForKey = [entry]));
458
+ };
459
+ //let i = 0;
460
+ // get all the ids of the related records
461
+ for (const entry of rightIterable) {
462
+ const record = entry.value ?? store.getSync(entry.key ?? entry);
463
+ const leftKey = record?.[rightProperty];
464
+ if (leftKey == null) continue;
465
+ if (joined.filters?.some((filter) => !filter(record))) continue;
466
+ if (isManyToMany) {
467
+ for (let i = 0; i < leftKey.length; i++) {
468
+ addEntry(leftKey[i], entry);
469
+ }
470
+ } else {
471
+ addEntry(leftKey, entry);
472
+ }
473
+ // TODO: Enable this with async iterator manually iterating so that we don't need to do an await on every iteration
474
+ /*
475
+ if (i++ > 100) {
476
+ // yield the event turn every 100 ids. See below for more explanation
477
+ await new Promise(setImmediate);
478
+ i = 0;
479
+ }*/
480
+ }
481
+ joinedIterator = joined.keys()[Symbol.iterator]();
482
+ return this.next();
483
+ }
484
+ const joinedEntry = joinedIterator.next();
485
+ if (joinedEntry.done) return joinedEntry;
486
+ return {
487
+ // if necessary, get the original key from the entries array
488
+ value: joinedEntry.value,
489
+ };
490
+ },
491
+ return() {
492
+ if (joinedIterator?.return) return joinedIterator.return();
493
+ },
494
+ };
495
+ },
496
+ });
497
+ }
498
+ /**
499
+ * This is used to join the results of a query where the right side is a set of ids and the left side is a set of records
500
+ * that have the foreign key (from left to right)
501
+ * @param rightIterable
502
+ * @param attribute
503
+ * @param store
504
+ * @param joined
505
+ * @param searchEntry
506
+ * @returns
507
+ */
508
+ function joinFrom(rightIterable, attribute, store, joined: Map<any, any[]>, searchEntry) {
509
+ return new rightIterable.constructor({
510
+ [Symbol.iterator]() {
511
+ let idIterator;
512
+ let joinedIterator;
513
+ const seenIds = new Set();
514
+ return {
515
+ next() {
516
+ let joinedEntry;
517
+ if (joinedIterator) {
518
+ while (true) {
519
+ joinedEntry = joinedIterator.next();
520
+ if (joinedEntry.done) break; // and continue to find next
521
+ const id = joinedEntry.value;
522
+ if (seenIds.has(id)) continue;
523
+ seenIds.add(id);
524
+ return joinedEntry;
525
+ }
526
+ }
527
+ if (!idIterator) {
528
+ // get the ids of the related records as a Set so we can quickly check if it is in the set
529
+ // when are iterating through the results
530
+ const ids = new Set();
531
+ // Define the fromRecord function so that we can use it to filter the related records
532
+ // that are in the select(), to only those that are in this set of ids
533
+ joined.fromRecord = (record) => {
534
+ // TODO: Sort based on order ids
535
+ return record[attribute.relationship.from]?.filter?.((id) => ids.has(id));
536
+ };
537
+ //let i = 0;
538
+ // get all the ids of the related records
539
+ for (const id of rightIterable) {
540
+ if (joined.filters) {
541
+ // if additional filters are defined, we need to check them
542
+ const record = store.getSync(id);
543
+ if (joined.filters.some((filter) => !filter(record))) continue;
544
+ }
545
+ ids.add(id);
546
+ // TODO: Re-enable this when async iteration is used, and do so with manually iterating so that we don't need to do an await on every iteration
547
+ /*
548
+ if (i++ > 100) {
549
+ // yield the event turn every 100 ids. We don't want to monopolize the
550
+ // event loop, give others a chance to run. However, we are much more aggressive
551
+ // about running here than in simple filter operations, because we are
552
+ // executing a very minimal range iteration and because this is consuming
553
+ // memory (so we want to get it over with) and the user isn't getting any
554
+ // results until we finish
555
+ await new Promise(setImmediate);
556
+ i = 0;
557
+ }*/
558
+ }
559
+ // and now start iterating through the ids
560
+ idIterator = ids[Symbol.iterator]();
561
+ return this.next();
562
+ }
563
+ do {
564
+ const idEntry = idIterator.next();
565
+ if (idEntry.done) return idEntry;
566
+ joinedIterator = searchEntry(idEntry.value)[Symbol.iterator]();
567
+ return this.next();
568
+ } while (true);
569
+ },
570
+ return() {
571
+ return joinedIterator?.return?.();
572
+ },
573
+ throw() {
574
+ return joinedIterator?.throw?.();
575
+ },
576
+ };
577
+ },
578
+ });
579
+ }
580
+
581
+ const ALTERNATE_COMPARATOR_NAMES = {
582
+ 'eq': 'equals',
583
+ 'greater_than': 'gt',
584
+ 'greaterThan': 'gt',
585
+ 'greater_than_equal': 'ge',
586
+ 'greaterThanEqual': 'ge',
587
+ 'less_than': 'lt',
588
+ 'lessThan': 'lt',
589
+ 'less_than_equal': 'le',
590
+ 'lessThanEqual': 'le',
591
+ 'not_equal': 'ne',
592
+ 'notEqual': 'ne',
593
+ 'equal': 'equals',
594
+ 'sw': 'starts_with',
595
+ 'startsWith': 'starts_with',
596
+ 'ew': 'ends_with',
597
+ 'endsWith': 'ends_with',
598
+ 'ct': 'contains',
599
+ '>': 'gt',
600
+ '>=': 'ge',
601
+ '<': 'lt',
602
+ '<=': 'le',
603
+ '...': 'between',
604
+ };
605
+
606
+ /**
607
+ * Create a filter based on the search condition that can be used to test each supplied record.
608
+ * @param {SearchObject} searchCondition
609
+ * @returns {({}) => boolean}
610
+ */
611
+ export function filterByType(searchCondition, Table, context, filtered, isPrimaryKey?, estimatedIncomingCount?) {
612
+ const comparator = searchCondition.comparator;
613
+ let attribute = searchCondition[0] ?? searchCondition.attribute;
614
+ let value = searchCondition[1] ?? searchCondition.value;
615
+ if (Array.isArray(attribute)) {
616
+ if (attribute.length === 0) return () => true;
617
+ if (attribute.length === 1) attribute = attribute[0];
618
+ else if (attribute.length > 1) {
619
+ const firstAttributeName = attribute[0];
620
+ // get the relationship attribute
621
+ const firstAttribute = findAttribute(Table.attributes, firstAttributeName);
622
+ const relatedTable = firstAttribute.definition?.tableClass || firstAttribute.elements.definition?.tableClass;
623
+ // TODO: If this is a relationship, we can potentially make this more efficient by using the index
624
+ // and retrieving the set of matching ids first
625
+ const filterMap = filtered?.[firstAttributeName];
626
+ const nextFilter = filterByType(
627
+ {
628
+ attribute: attribute.length > 2 ? attribute.slice(1) : attribute[1],
629
+ value,
630
+ comparator,
631
+ },
632
+ relatedTable,
633
+ context,
634
+ filterMap?.[firstAttributeName]?.joined,
635
+ attribute[1] === relatedTable.primaryKey,
636
+ estimatedIncomingCount
637
+ );
638
+ if (!nextFilter) return;
639
+ if (filterMap) {
640
+ if (!filterMap.filters) filterMap.filters = [];
641
+ filterMap.filters.push(nextFilter);
642
+ return;
643
+ }
644
+ const resolver = Table.propertyResolvers?.[firstAttributeName];
645
+ if (resolver.to) nextFilter.to = resolver.to;
646
+ let subIdFilter;
647
+ const getSubObject = (record, entry) => {
648
+ let subObject, subEntry;
649
+ if (resolver) {
650
+ if (resolver.returnDirect) {
651
+ // indicates that the resolver will direct return the value instead of an entry
652
+ subObject = resolver(record, context, entry);
653
+ subEntry = lastMetadata;
654
+ } else {
655
+ subEntry = resolver(record, context, entry, true);
656
+ if (Array.isArray(subEntry)) {
657
+ // if any array, map the values
658
+ subObject = subEntry.map((subEntry) => subEntry.value);
659
+ subEntry = null;
660
+ } else {
661
+ subObject = subEntry?.value;
662
+ }
663
+ }
664
+ } else subObject = record[firstAttributeName];
665
+ return { subObject, subEntry };
666
+ };
667
+ const recordFilter = (record, entry) => {
668
+ if (resolver) {
669
+ if (nextFilter.idFilter) {
670
+ // if we are filtering by id, we can use the idFilter to avoid loading the record
671
+ if (!subIdFilter) {
672
+ if (nextFilter.idFilter.idSet?.size === 1) {
673
+ // if there is a single id we are looking for, we can create a new search condition that the
674
+ // attribute comparator could eventually use to create a recursive id set
675
+ // TODO: Eventually we should be able to handle multiple ids by creating a union
676
+ for (const id of nextFilter.idFilter.idSet) {
677
+ searchCondition = {
678
+ attribute: resolver.from ?? Table.primaryKey, // if no from, we use our primary key
679
+ value: id,
680
+ };
681
+ }
682
+ // indicate that we can use an index for this. also we indicate that we allow object matching to allow array ids to directly tested
683
+ subIdFilter = attributeComparator(resolver.from ?? Table.primaryKey, nextFilter.idFilter, true, true);
684
+ } else
685
+ subIdFilter = attributeComparator(resolver.from ?? Table.primaryKey, nextFilter.idFilter, false, true);
686
+ }
687
+ const matches = subIdFilter(record);
688
+ if (subIdFilter.idFilter) recordFilter.idFilter = subIdFilter.idFilter;
689
+ return matches;
690
+ }
691
+ }
692
+ const { subObject, subEntry } = getSubObject(record, entry);
693
+ if (!subObject) return false;
694
+ if (!Array.isArray(subObject)) return nextFilter(subObject, subEntry);
695
+ const filterMap = filtered?.[firstAttributeName];
696
+ if (!filterMap && filtered) {
697
+ // establish a filtering that can preserve this filter for the select() results of these sub objects
698
+ filtered[firstAttributeName] = {
699
+ fromRecord(record) {
700
+ // this is called when selecting the fields to include in results
701
+ const value = getSubObject(record).subObject;
702
+ if (Array.isArray(value)) return value.filter(nextFilter).map((value) => value[relatedTable.primaryKey]);
703
+ return value;
704
+ },
705
+ };
706
+ }
707
+ return subObject.some(nextFilter);
708
+ };
709
+ return recordFilter;
710
+ }
711
+ }
712
+ if (value instanceof Date) value = value.getTime();
713
+
714
+ switch (ALTERNATE_COMPARATOR_NAMES[comparator] || comparator) {
715
+ case SEARCH_TYPES.EQUALS:
716
+ case undefined:
717
+ return attributeComparator(attribute, (recordValue) => recordValue === value, true);
718
+ case 'contains':
719
+ return attributeComparator(attribute, (recordValue) => recordValue?.toString().includes(value));
720
+ case 'ends_with':
721
+ return attributeComparator(attribute, (recordValue) => recordValue?.toString().endsWith(value));
722
+ case 'starts_with':
723
+ return attributeComparator(
724
+ attribute,
725
+ (recordValue) => typeof recordValue === 'string' && recordValue.startsWith(value),
726
+ true
727
+ );
728
+ case 'prefix':
729
+ if (!Array.isArray(value)) value = [value];
730
+ else if (value[value.length - 1] == null) value = value.slice(0, -1);
731
+ return attributeComparator(
732
+ attribute,
733
+ (recordValue) => {
734
+ if (!Array.isArray(recordValue)) return false;
735
+ for (let i = 0, l = value.length; i < l; i++) {
736
+ if (recordValue[i] !== value[i]) return false;
737
+ }
738
+ return true;
739
+ },
740
+ true
741
+ );
742
+ case 'between':
743
+ if (value[0] instanceof Date) value[0] = value[0].getTime();
744
+ if (value[1] instanceof Date) value[1] = value[1].getTime();
745
+ return attributeComparator(
746
+ attribute,
747
+ (recordValue) => {
748
+ return compareKeys(recordValue, value[0]) >= 0 && compareKeys(recordValue, value[1]) <= 0;
749
+ },
750
+ true
751
+ );
752
+ case 'gt':
753
+ return attributeComparator(attribute, (recordValue) => compareKeys(recordValue, value) > 0);
754
+ case 'ge':
755
+ return attributeComparator(attribute, (recordValue) => compareKeys(recordValue, value) >= 0);
756
+ case 'lt':
757
+ return attributeComparator(attribute, (recordValue) => compareKeys(recordValue, value) < 0);
758
+ case 'le':
759
+ return attributeComparator(attribute, (recordValue) => compareKeys(recordValue, value) <= 0);
760
+ case 'ne':
761
+ return attributeComparator(attribute, (recordValue) => compareKeys(recordValue, value) !== 0, false, true);
762
+ case 'sort':
763
+ return () => true;
764
+ default:
765
+ throw new ClientError(`Unknown query comparator "${comparator}"`);
766
+ }
767
+ /** Create a comparison function that can take the record and check the attribute's value with the filter function */
768
+ function attributeComparator(
769
+ attribute: string,
770
+ filter: (record: any) => boolean,
771
+ canUseIndex?: boolean,
772
+ allowObjectMatching?: boolean
773
+ ) {
774
+ let thresholdRemainingMisses: number;
775
+ canUseIndex =
776
+ canUseIndex && // is it a comparator that makes sense to use index
777
+ !isPrimaryKey && // no need to use index for primary keys, since we will be iterating over the primary keys
778
+ Table?.indices[attribute] && // is there an index for this attribute
779
+ estimatedIncomingCount > 3; // do we have a valid estimate of multiple incoming records (that is worth using an index for)
780
+ if (canUseIndex) {
781
+ if (searchCondition.estimated_count == undefined) estimateCondition(Table)(searchCondition);
782
+ thresholdRemainingMisses = searchCondition.estimated_count >> 4;
783
+ if (isNaN(thresholdRemainingMisses) || thresholdRemainingMisses >= estimatedIncomingCount)
784
+ // invalid or can't be ever reached
785
+ canUseIndex = false;
786
+ }
787
+ let misses = 0;
788
+ let filteredSoFar = 3; // what we use to calculate miss rate; we give some buffer so we don't jump to indexed retrieval too quickly
789
+ function recordFilter(record: any) {
790
+ const value = record[attribute];
791
+ let matches: boolean;
792
+ if (typeof value !== 'object' || !value || allowObjectMatching) matches = filter(value);
793
+ else if (Array.isArray(value)) matches = value.some(filter);
794
+ else if (value instanceof Date) matches = filter(value.getTime());
795
+ //else matches = false;
796
+ // As we are filtering, we can lazily/reactively switch to indexing if we are getting a low match rate, allowing use to load
797
+ // a set of ids instead of loading each record. This can be a significant performance improvement for large queries with low match rates
798
+ if (canUseIndex) {
799
+ filteredSoFar++;
800
+ if (
801
+ !matches &&
802
+ !recordFilter.idFilter &&
803
+ // miss rate x estimated remaining to filter > 10% of estimated incoming
804
+ (++misses / filteredSoFar) * estimatedIncomingCount > thresholdRemainingMisses
805
+ ) {
806
+ // if we have missed too many times, we need to switch to indexed retrieval
807
+ const searchResults = searchByIndex(searchCondition, Table._readTxnForContext(context), false, Table);
808
+ let matchingIds: Iterable<Id>;
809
+ if (recordFilter.to) {
810
+ // the values could be an array of keys, so we flatten the mapping
811
+ matchingIds = searchResults.flatMap((id) => Table.primaryStore.getSync(id)[recordFilter.to]);
812
+ } else {
813
+ matchingIds = searchResults.map(flattenKey);
814
+ }
815
+ // now generate a hash set that we can efficiently check primary keys against
816
+ // TODO: Do this asynchronously
817
+ const idSet = new Set(matchingIds);
818
+ recordFilter.idFilter = (id) => idSet.has(flattenKey(id));
819
+ recordFilter.idFilter.idSet = idSet;
820
+ }
821
+ }
822
+ return matches;
823
+ }
824
+ if (isPrimaryKey) {
825
+ recordFilter.idFilter = filter;
826
+ }
827
+ return recordFilter;
828
+ }
829
+ }
830
+
831
+ export function estimateCondition(table) {
832
+ function estimateConditionForTable(condition) {
833
+ if (condition.estimated_count === undefined) {
834
+ if (condition.conditions) {
835
+ // for a group of conditions, we can estimate the count by combining the estimates of the sub-conditions
836
+ let estimatedCount;
837
+ if (condition.operator === 'or') {
838
+ // with a union, we can just add the estimated counts
839
+ estimatedCount = 0;
840
+ for (const subCondition of condition.conditions) {
841
+ estimateConditionForTable(subCondition);
842
+ estimatedCount += subCondition.estimated_count;
843
+ }
844
+ } else {
845
+ // with an intersection, we have to use the rate of the sub-conditions to apply to estimate count of last condition
846
+ estimatedCount = Infinity;
847
+ for (const subCondition of condition.conditions) {
848
+ estimateConditionForTable(subCondition);
849
+ estimatedCount = isFinite(estimatedCount)
850
+ ? (estimatedCount * subCondition.estimated_count) / estimatedEntryCount(table.primaryStore)
851
+ : subCondition.estimated_count;
852
+ }
853
+ }
854
+ condition.estimated_count = estimatedCount;
855
+ return condition.estimated_count;
856
+ }
857
+ // skip if it is cached
858
+ let searchType = condition.comparator || condition.search_type;
859
+ searchType = ALTERNATE_COMPARATOR_NAMES[searchType] || searchType;
860
+ if (searchType === SEARCH_TYPES.EQUALS || !searchType) {
861
+ const attribute_name = condition[0] ?? condition.attribute;
862
+ if (attribute_name == null || attribute_name === table.primaryKey) condition.estimated_count = 1;
863
+ else if (Array.isArray(attribute_name) && attribute_name.length > 1) {
864
+ const attribute = findAttribute(table.attributes, attribute_name[0]);
865
+ const relatedTable = attribute.definition?.tableClass || attribute.elements.definition?.tableClass;
866
+ const estimate = estimateCondition(relatedTable)({
867
+ value: condition.value,
868
+ attribute: attribute_name.length > 2 ? attribute_name.slice(1) : attribute_name[1],
869
+ comparator: 'equals',
870
+ });
871
+ const fromIndex = table.indices[attribute.relationship.from];
872
+ // the estimated count is sum of the estimate of the related table and the estimate of the index
873
+ condition.estimated_count =
874
+ estimate +
875
+ (fromIndex
876
+ ? (estimate * estimatedEntryCount(table.indices[attribute.relationship.from])) /
877
+ (estimatedEntryCount(relatedTable.primaryStore) || 1)
878
+ : estimate);
879
+ } else {
880
+ // we only attempt to estimate count on equals operator because that's really all that LMDB supports (some other key-value stores like libmdbx could be considered if we need to do estimated counts of ranges at some point)
881
+ const index = table.indices[attribute_name];
882
+ condition.estimated_count = index ? index.getValuesCount(condition[1] ?? condition.value) : Infinity;
883
+ }
884
+ } else if (searchType === 'contains' || searchType === 'ends_with' || searchType === 'ne') {
885
+ const attribute_name = condition[0] ?? condition.attribute;
886
+ const index = table.indices[attribute_name];
887
+ if (condition.value === null && searchType === 'ne') {
888
+ condition.estimated_count =
889
+ estimatedEntryCount(table.primaryStore) - (index ? index.getValuesCount(null) : 0);
890
+ } else condition.estimated_count = Infinity;
891
+ // for range queries (betweens, startsWith, greater, etc.), just arbitrarily guess
892
+ } else if (searchType === 'starts_with' || searchType === 'prefix')
893
+ condition.estimated_count = STARTS_WITH_ESTIMATE * estimatedEntryCount(table.primaryStore) + 1;
894
+ else if (searchType === 'between')
895
+ condition.estimated_count = BETWEEN_ESTIMATE * estimatedEntryCount(table.primaryStore) + 1;
896
+ else if (searchType === 'sort') {
897
+ const attribute_name = condition[0] ?? condition.attribute;
898
+ const index = table.indices[attribute_name];
899
+ if (index?.customIndex?.estimateCountAsSort)
900
+ // allow custom index to define its own estimation of counts
901
+ condition.estimated_count = index.customIndex.estimateCountAsSort(condition);
902
+ else condition.estimated_count = estimatedEntryCount(table.primaryStore) + 1; // only used by sort
903
+ } else {
904
+ // for the search types that use the broadest range, try do them last
905
+ const attribute_name = condition[0] ?? condition.attribute;
906
+ const index = table.indices[attribute_name];
907
+ if (index?.customIndex?.estimateCount)
908
+ // allow custom index to define its own estimation of counts
909
+ condition.estimated_count = index.customIndex.estimateCount(condition.value);
910
+ else condition.estimated_count = OPEN_RANGE_ESTIMATE * estimatedEntryCount(table.primaryStore) + 1;
911
+ }
912
+ // we give a condition significantly more weight/preference if we will be ordering by it
913
+ if (typeof condition.descending === 'boolean') condition.estimated_count /= 2;
914
+ }
915
+ return condition.estimated_count; // use cached count
916
+ }
917
+ return estimateConditionForTable;
918
+ }
919
+ class SyntaxViolation extends Violation {}
920
+ const NEEDS_PARSER = /[()[\]|!<>.]|(=\w*=)/;
921
+ const QUERY_PARSER = /([^?&|=<>!([{}\]),]*)([([{}\])|,&]|[=<>!]*)/g;
922
+ const VALUE_PARSER = /([^&|=[\]{}]+)([[\]{}]|[&|=]*)/g;
923
+ let lastIndex;
924
+ let currentQuery;
925
+ let queryString;
926
+ /**
927
+ * This is responsible for taking a query string (from a get()) and merging the parsed elements into a RequestTarget object.
928
+ * @param queryString
929
+ */
930
+ export function parseQuery(queryToParse: string, query: RequestTarget) {
931
+ if (!queryToParse) return;
932
+ queryString = queryToParse;
933
+ // TODO: We can remove this if we are sure all exits points end with lastIndex as zero (reaching the end of parsing will do that)
934
+ QUERY_PARSER.lastIndex = 0;
935
+ if (NEEDS_PARSER.test(queryToParse)) {
936
+ try {
937
+ if (query) query.conditions = [];
938
+ currentQuery = query ?? new Query();
939
+ parseBlock(currentQuery, '');
940
+ if (lastIndex !== queryString.length) recordError(`Unable to parse query, unexpected end of query`);
941
+ if (currentQuery.parseErrorMessage) {
942
+ currentQuery.parseError = new SyntaxViolation(query.parseErrorMessage);
943
+ if (!query) throw currentQuery.parseError;
944
+ }
945
+ return currentQuery;
946
+ } catch (error) {
947
+ error.statusCode = 400;
948
+ error.message = `Unable to parse query, ${error.message} at position ${lastIndex} in '${queryString}'`;
949
+ if (currentQuery.parseErrorMessage) error.message += ', ' + currentQuery.parseErrorMessage;
950
+ if (query) {
951
+ query.parseError = error;
952
+ } else {
953
+ throw error;
954
+ }
955
+ }
956
+ } else {
957
+ return query ?? new URLSearchParams(queryToParse);
958
+ }
959
+ }
960
+ function recordError(message: string) {
961
+ const errorMessage = `${message} at position ${lastIndex}`;
962
+ currentQuery.parseErrorMessage = currentQuery.parseErrorMessage
963
+ ? currentQuery.parseErrorMessage + ', ' + errorMessage
964
+ : errorMessage;
965
+ }
966
+ function parseBlock(query, expectedEnd) {
967
+ let parser = QUERY_PARSER;
968
+ let match;
969
+ let attribute, comparator, expectingDelimiter, expectingValue;
970
+ let valueDecoder = decodeURIComponent;
971
+ let lastBinaryOperator;
972
+ while ((match = parser.exec(queryString))) {
973
+ lastIndex = parser.lastIndex;
974
+ const [, value, operator] = match;
975
+ if (expectingDelimiter) {
976
+ if (value) recordError(`expected operator, but encountered '${value}'`);
977
+ expectingDelimiter = false;
978
+ expectingValue = false;
979
+ } else expectingValue = true;
980
+ let entry;
981
+ switch (operator) {
982
+ case '=':
983
+ if (attribute != undefined) {
984
+ // a FIQL operator like =gt= (and don't allow just any string)
985
+ if (value.length <= 2) comparator = value;
986
+ else recordError(`invalid FIQL operator ${value}`);
987
+ valueDecoder = typedDecoding; // use typed/auto-cast decoding for FIQL operators
988
+ } else {
989
+ // standard equal comparison
990
+ valueDecoder = decodeURIComponent; // use strict decoding
991
+ comparator = 'equals'; // strict equals
992
+ if (!value) recordError(`attribute must be specified before equality comparator`);
993
+ attribute = decodeProperty(value);
994
+ }
995
+ break;
996
+ case '==':
997
+ // TODO: Separate decoder to handle * operator here for startsWith, endsWith, and contains?
998
+ // fall through
999
+ case '!=':
1000
+ case '<':
1001
+ case '<=':
1002
+ case '>':
1003
+ case '>=':
1004
+ case '===':
1005
+ case '!==':
1006
+ comparator = SYMBOL_OPERATORS[operator];
1007
+ valueDecoder = COERCIBLE_OPERATORS[comparator] ? typedDecoding : decodeURIComponent;
1008
+ if (!value) recordError(`attribute must be specified before comparator ${operator}`);
1009
+ attribute = decodeProperty(value);
1010
+ break;
1011
+ case '&=': // for chaining conditions on to the same attribute
1012
+ case '|=':
1013
+ case '|':
1014
+ case '&':
1015
+ case '':
1016
+ case undefined:
1017
+ if (attribute == null) {
1018
+ if (attribute === undefined) {
1019
+ if (expectedEnd)
1020
+ recordError(
1021
+ `expected '${expectedEnd}', but encountered ${operator[0] ? "'" + operator[0] + "'" : 'end of string'}}`
1022
+ );
1023
+ recordError(`no comparison specified before ${operator ? "'" + operator + "'" : 'end of string'}`);
1024
+ }
1025
+ } else {
1026
+ if (!query.conditions) recordError('conditions/comparisons are not allowed in a property list');
1027
+ const condition = {
1028
+ comparator,
1029
+ attribute: attribute || null,
1030
+ value: valueDecoder(value),
1031
+ };
1032
+ if (comparator === 'eq') wildcardDecoding(condition, value);
1033
+ if (attribute === '') {
1034
+ // this is a nested condition
1035
+ const lastCondition = query.conditions[query.conditions.length - 1];
1036
+ lastCondition.chainedConditions = lastCondition.chainedConditions || [];
1037
+ lastCondition.chainedConditions.push(condition);
1038
+ lastCondition.operator = lastBinaryOperator;
1039
+ } else {
1040
+ assignOperator(query, lastBinaryOperator);
1041
+ query.conditions.push(condition);
1042
+ }
1043
+ }
1044
+ if (operator === '&') {
1045
+ lastBinaryOperator = 'and';
1046
+ attribute = undefined;
1047
+ } else if (operator === '|') {
1048
+ lastBinaryOperator = 'or';
1049
+ attribute = undefined;
1050
+ } else if (operator === '&=') {
1051
+ lastBinaryOperator = 'and';
1052
+ attribute = '';
1053
+ } else if (operator === '|=') {
1054
+ lastBinaryOperator = 'or';
1055
+ attribute = '';
1056
+ }
1057
+ break;
1058
+ case ',':
1059
+ if (query.conditions) {
1060
+ // TODO: Add support for a list of values
1061
+ recordError('conditions/comparisons are not allowed in a property list');
1062
+ } else {
1063
+ query.push(decodeProperty(value));
1064
+ }
1065
+ attribute = undefined;
1066
+ break;
1067
+ case '(':
1068
+ QUERY_PARSER.lastIndex = lastIndex;
1069
+ const args = parseBlock(value ? [] : new Query(), ')');
1070
+ switch (value) {
1071
+ case '': // nested/grouped condition
1072
+ assignOperator(query, lastBinaryOperator);
1073
+ query.conditions.push(args);
1074
+ break;
1075
+ case 'limit':
1076
+ switch (args.length) {
1077
+ case 1:
1078
+ query.limit = +args[0];
1079
+ break;
1080
+ case 2:
1081
+ query.offset = +args[0];
1082
+ query.limit = args[1] - query.offset;
1083
+ break;
1084
+ default:
1085
+ recordError('limit must have 1 or 2 arguments');
1086
+ }
1087
+ break;
1088
+ case 'select':
1089
+ if (Array.isArray(args[0]) && args.length === 1 && !args[0].name) {
1090
+ query.select = args[0];
1091
+ query.select.asArray = true;
1092
+ } else if (args.length === 1) query.select = args[0];
1093
+ else if (args.length === 2 && args[1] === '') query.select = args.slice(0, 1);
1094
+ else query.select = args;
1095
+ break;
1096
+ case 'group-by':
1097
+ recordError('group by is not implemented yet');
1098
+ case 'sort':
1099
+ query.sort = toSortObject(args);
1100
+ break;
1101
+ default:
1102
+ recordError(`unknown query function call ${value}`);
1103
+ }
1104
+ if (queryString[lastIndex] === ',') {
1105
+ parser.lastIndex = ++lastIndex;
1106
+ } else expectingDelimiter = true;
1107
+ attribute = null;
1108
+ break;
1109
+ case '{':
1110
+ if (query.conditions) recordError('property sets are not allowed in a queries');
1111
+ if (!value) recordError('property sets must have a defined parent property name');
1112
+ // this is interpreted as property{subProperty}
1113
+ QUERY_PARSER.lastIndex = lastIndex;
1114
+ entry = parseBlock([], '}');
1115
+ entry.name = value;
1116
+ query.push(entry);
1117
+ if (queryString[lastIndex] === ',') {
1118
+ parser.lastIndex = ++lastIndex;
1119
+ } else expectingDelimiter = true;
1120
+ break;
1121
+ case '[':
1122
+ QUERY_PARSER.lastIndex = lastIndex;
1123
+ if (value) {
1124
+ // this is interpreted as propertyWithArray[name=value&anotherOtherConditions...]
1125
+ entry = parseBlock(new Query(), ']');
1126
+ entry.name = value;
1127
+ } else {
1128
+ // this is interpreted a property list that can be used within other lists
1129
+ entry = parseBlock(query.conditions ? new Query() : [], ']');
1130
+ }
1131
+ if (query.conditions) {
1132
+ assignOperator(query, lastBinaryOperator);
1133
+ if (queryString[lastIndex] === '=') {
1134
+ // handle the case of a query parameter like property[]=value, using the standard equal behavior
1135
+ valueDecoder = decodeURIComponent; // use strict decoding
1136
+ comparator = 'equals'; // strict equals
1137
+ attribute = decodeProperty(value);
1138
+ parser.lastIndex = ++lastIndex;
1139
+ break;
1140
+ } else {
1141
+ query.conditions.push(entry);
1142
+ attribute = null;
1143
+ }
1144
+ } else query.push(entry);
1145
+ if (queryString[lastIndex] === ',') {
1146
+ parser.lastIndex = ++lastIndex;
1147
+ } else expectingDelimiter = true;
1148
+ break;
1149
+ case ')':
1150
+ case ']':
1151
+ case '}':
1152
+ if (expectedEnd === operator[0]) {
1153
+ // assert that it is expected
1154
+ if (query.conditions) {
1155
+ // finish condition
1156
+ if (attribute) {
1157
+ const condition = {
1158
+ comparator: comparator || 'equals',
1159
+ attribute,
1160
+ value: valueDecoder(value),
1161
+ };
1162
+ if (comparator === 'eq') wildcardDecoding(condition, value);
1163
+ assignOperator(query, lastBinaryOperator);
1164
+ query.conditions.push(condition);
1165
+ } else if (value) {
1166
+ recordError('no attribute or comparison specified');
1167
+ }
1168
+ } else if (value || (query.length > 0 && expectingValue)) {
1169
+ query.push(decodeProperty(value));
1170
+ }
1171
+ return query;
1172
+ } else if (expectedEnd) recordError(`expected '${expectedEnd}', but encountered '${operator[0]}'`);
1173
+ else recordError(`unexpected token '${operator[0]}'`);
1174
+ default:
1175
+ recordError(`unexpected operator '${operator}'`);
1176
+ }
1177
+ if (expectedEnd !== ')') {
1178
+ parser = attribute ? VALUE_PARSER : QUERY_PARSER;
1179
+ parser.lastIndex = lastIndex;
1180
+ }
1181
+ if (lastIndex === queryString.length) return query;
1182
+ }
1183
+ if (expectedEnd) recordError(`expected '${expectedEnd}', but encountered end of string`);
1184
+ }
1185
+ function assignOperator(query, lastBinaryOperator) {
1186
+ if (query.conditions.length > 0) {
1187
+ if (query.operator) {
1188
+ if (query.operator !== lastBinaryOperator) recordError('Can not mix operators within a condition grouping');
1189
+ } else query.operator = lastBinaryOperator;
1190
+ }
1191
+ }
1192
+ function decodeProperty(name) {
1193
+ if (name.indexOf('.') > -1) {
1194
+ return name.split('.').map(decodeProperty);
1195
+ }
1196
+ return decodeURIComponent(name);
1197
+ }
1198
+
1199
+ function typedDecoding(value) {
1200
+ // for non-strict operators, we allow for coercion of types
1201
+ if (value === 'null') return null;
1202
+ if (value.indexOf(':') > -1) {
1203
+ const [type, valueToCoerce] = value.split(':');
1204
+ if (type === 'number') {
1205
+ if (valueToCoerce[0] === '$') return parseInt(valueToCoerce.slice(1), 36);
1206
+ return +valueToCoerce;
1207
+ } else if (type === 'boolean') return valueToCoerce === 'true';
1208
+ else if (type === 'date')
1209
+ return new Date(isNaN(valueToCoerce) ? decodeURIComponent(valueToCoerce) : +valueToCoerce);
1210
+ else if (type === 'string') return decodeURIComponent(valueToCoerce);
1211
+ else throw new ClientError(`Unknown type ${type}`);
1212
+ }
1213
+ return decodeURIComponent(value);
1214
+ }
1215
+ /**
1216
+ * Perform wildcard detection and conversion to correct comparator
1217
+ * @param condition
1218
+ * @param value
1219
+ */
1220
+ function wildcardDecoding(condition, value) {
1221
+ if (value.indexOf('*') > -1) {
1222
+ if (value.endsWith('*')) {
1223
+ condition.comparator = 'starts_with';
1224
+ condition.value = decodeURIComponent(value.slice(0, -1));
1225
+ } else {
1226
+ throw new ClientError('wildcard can only be used at the end of a string');
1227
+ }
1228
+ }
1229
+ }
1230
+
1231
+ function toSortObject(sort) {
1232
+ const sortObject = toSortEntry(sort[0]);
1233
+ if (sort.length > 1) {
1234
+ sortObject.next = toSortObject(sort.slice(1));
1235
+ }
1236
+ return sortObject;
1237
+ }
1238
+ function toSortEntry(sort) {
1239
+ if (Array.isArray(sort)) {
1240
+ const sortObject = toSortEntry(sort[0]);
1241
+ sort[0] = sortObject.attribute;
1242
+ sortObject.attribute = sort;
1243
+ return sortObject;
1244
+ }
1245
+ if (typeof sort === 'string') {
1246
+ switch (sort[0]) {
1247
+ case '-':
1248
+ return { attribute: sort.slice(1), descending: true };
1249
+ case '+':
1250
+ return { attribute: sort.slice(1), descending: false };
1251
+ default:
1252
+ return { attribute: sort, descending: false };
1253
+ }
1254
+ }
1255
+ recordError(`Unknown sort type ${sort}`);
1256
+ }
1257
+
1258
+ class Query {
1259
+ declare conditions: { attribute: string; value: any; comparator: string }[];
1260
+ declare limit: number;
1261
+ declare offset: number;
1262
+ declare select: string[];
1263
+ constructor() {
1264
+ this.conditions = [];
1265
+ }
1266
+ [Symbol.iterator]() {
1267
+ return this.conditions[Symbol.iterator]();
1268
+ }
1269
+ get(name) {
1270
+ for (let i = 0; i < this.conditions.length; i++) {
1271
+ const condition = this.conditions[i];
1272
+ if (condition.attribute === name) return condition.value;
1273
+ }
1274
+ }
1275
+ getAll() {
1276
+ const values = [];
1277
+ for (let i = 0, len = this.conditions.length; i < len; i++) {
1278
+ const condition = this.conditions[i];
1279
+ if (condition.attribute) values.push(condition.value);
1280
+ }
1281
+ return values;
1282
+ }
1283
+ }
1284
+ export function flattenKey(key) {
1285
+ if (Array.isArray(key)) return key.join('\x00');
1286
+ return key;
1287
+ }
1288
+
1289
+ function estimatedEntryCount(store) {
1290
+ const now = Date.now();
1291
+ if ((store.estimatedEntryCountExpires || 0) < now) {
1292
+ // use getStats for LMDB because it is fast path, otherwise RocksDB can handle fast path on its own
1293
+ store.estimatedEntryCount = store.readerCheck ? store.getStats().entryCount : store.getKeysCount();
1294
+ store.estimatedEntryCountExpires = now + 10000;
1295
+ }
1296
+ return store.estimatedEntryCount;
1297
+ }
1298
+
1299
+ export function intersectionEstimate(store, left, right) {
1300
+ return (left * right) / estimatedEntryCount(store);
1301
+ }