@harperfast/harper 5.0.0-alpha.10 → 5.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/BinObjects.js +17 -0
- package/bin/cliOperations.js +157 -0
- package/bin/copyDb.ts +280 -0
- package/bin/harper.js +156 -0
- package/bin/install.js +15 -0
- package/bin/lite.js +5 -0
- package/bin/restart.js +201 -0
- package/bin/run.js +409 -0
- package/bin/status.js +65 -0
- package/bin/stop.js +22 -0
- package/bin/upgrade.js +134 -0
- package/components/Application.ts +646 -0
- package/components/ApplicationScope.ts +49 -0
- package/components/Component.ts +53 -0
- package/components/ComponentV1.ts +342 -0
- package/components/DEFAULT_CONFIG.ts +18 -0
- package/components/EntryHandler.ts +227 -0
- package/components/Logger.ts +14 -0
- package/components/OptionsWatcher.ts +354 -0
- package/components/PluginModule.ts +6 -0
- package/components/Scope.ts +329 -0
- package/components/componentLoader.ts +529 -0
- package/components/deriveCommonPatternBase.ts +31 -0
- package/components/deriveGlobOptions.ts +44 -0
- package/components/deriveURLPath.ts +57 -0
- package/components/operations.js +658 -0
- package/components/operationsValidation.js +246 -0
- package/components/packageComponent.ts +39 -0
- package/components/requestRestart.ts +26 -0
- package/components/resolveBaseURLPath.ts +38 -0
- package/components/status/ComponentStatus.ts +110 -0
- package/components/status/ComponentStatusRegistry.ts +251 -0
- package/components/status/api.ts +153 -0
- package/components/status/crossThread.ts +405 -0
- package/components/status/errors.ts +152 -0
- package/components/status/index.ts +44 -0
- package/components/status/internal.ts +65 -0
- package/components/status/registry.ts +12 -0
- package/components/status/types.ts +96 -0
- package/config/RootConfigWatcher.ts +59 -0
- package/config/configHelpers.ts +11 -0
- package/config/configUtils.js +967 -0
- package/config/harperConfigEnvVars.ts +641 -0
- package/dataLayer/CreateAttributeObject.js +25 -0
- package/dataLayer/CreateTableObject.js +11 -0
- package/dataLayer/DataLayerObjects.js +43 -0
- package/dataLayer/DeleteBeforeObject.js +22 -0
- package/dataLayer/DeleteObject.js +25 -0
- package/dataLayer/DropAttributeObject.js +11 -0
- package/dataLayer/GetBackupObject.js +22 -0
- package/dataLayer/InsertObject.js +24 -0
- package/dataLayer/ReadAuditLogObject.js +24 -0
- package/dataLayer/SQLSearch.js +1335 -0
- package/dataLayer/SearchByConditionsObject.js +61 -0
- package/dataLayer/SearchByHashObject.js +21 -0
- package/dataLayer/SearchObject.js +45 -0
- package/dataLayer/SqlSearchObject.js +14 -0
- package/dataLayer/UpdateObject.js +23 -0
- package/dataLayer/UpsertObject.js +23 -0
- package/dataLayer/bulkLoad.js +813 -0
- package/dataLayer/dataObjects/BulkLoadObjects.js +27 -0
- package/dataLayer/dataObjects/UpsertObject.js +23 -0
- package/dataLayer/delete.js +164 -0
- package/dataLayer/export.js +381 -0
- package/dataLayer/getBackup.js +40 -0
- package/dataLayer/harperBridge/BridgeMethods.js +81 -0
- package/dataLayer/harperBridge/ResourceBridge.ts +633 -0
- package/dataLayer/harperBridge/bridgeUtility/insertUpdateReturnObj.js +28 -0
- package/dataLayer/harperBridge/bridgeUtility/insertUpdateValidate.js +88 -0
- package/dataLayer/harperBridge/harperBridge.js +21 -0
- package/dataLayer/harperBridge/lmdbBridge/LMDBBridge.js +119 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +19 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbCreateAttribute.js +112 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbCreateRecords.js +67 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbCreateSchema.js +31 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbCreateTable.js +94 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbDeleteAuditLogsBefore.js +98 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbDeleteRecords.js +89 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbDropAttribute.js +109 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbDropSchema.js +107 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbDropTable.js +137 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbFlush.js +35 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbGetBackup.js +111 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbGetDataByHash.js +28 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbGetDataByValue.js +29 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbReadAuditLog.js +207 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbSearchByConditions.js +156 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbSearchByHash.js +21 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbSearchByValue.js +30 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbTransaction.js +19 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbUpdateRecords.js +64 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/lmdbUpsertRecords.js +70 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/LMDBCreateAttributeObject.js +22 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/LMDBDeleteTransactionObject.js +23 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/LMDBInsertTransactionObject.js +22 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/LMDBTransactionObject.js +23 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/LMDBUpdateTransactionObject.js +24 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/LMDBUpsertTransactionObject.js +24 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/TableSizeObject.js +25 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializeHashSearch.js +21 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +157 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbCheckForNewAttributes.js +94 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbCreateTransactionsAuditEnvironment.js +39 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbGetTableSize.js +34 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbProcessRows.js +100 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbSearch.js +371 -0
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbWriteTransaction.js +109 -0
- package/dataLayer/hdbInfoController.js +254 -0
- package/dataLayer/insert.js +266 -0
- package/dataLayer/readAuditLog.js +59 -0
- package/dataLayer/schema.js +366 -0
- package/dataLayer/schemaDescribe.js +289 -0
- package/dataLayer/search.js +60 -0
- package/dataLayer/transaction.js +17 -0
- package/dataLayer/update.js +124 -0
- package/dist/components/Logger.d.ts +12 -0
- package/dist/{resources/ResourceInterfaceV2.js → components/Logger.js} +1 -1
- package/dist/components/Logger.js.map +1 -0
- package/dist/components/Scope.d.ts +14 -4
- package/dist/components/Scope.js +18 -10
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/componentLoader.js +17 -10
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/operations.js +2 -2
- package/dist/components/operations.js.map +1 -1
- package/dist/config/configUtils.d.ts +1 -1
- package/dist/config/configUtils.js +1 -1
- package/dist/config/configUtils.js.map +1 -1
- package/dist/dataLayer/CreateTableObject.d.ts +2 -2
- package/dist/dataLayer/CreateTableObject.js +2 -2
- package/dist/dataLayer/CreateTableObject.js.map +1 -1
- package/dist/dataLayer/delete.d.ts +1 -1
- package/dist/dataLayer/schema.js +6 -5
- package/dist/dataLayer/schema.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +1 -1
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/resources/DatabaseTransaction.d.ts +1 -1
- package/dist/resources/IterableEventQueue.d.ts +1 -1
- package/dist/resources/LMDBTransaction.d.ts +5 -1
- package/dist/resources/Resource.d.ts +1 -1
- package/dist/resources/ResourceInterface.d.ts +1 -1
- package/dist/resources/RocksIndexStore.d.ts +3 -3
- package/dist/resources/RocksTransactionLogStore.d.ts +6 -3
- package/dist/resources/Table.d.ts +15 -6
- package/dist/resources/Table.js +12 -4
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/read.js +32 -22
- package/dist/resources/analytics/read.js.map +1 -1
- package/dist/resources/analytics/write.js +3 -6
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/resources/auditStore.d.ts +3 -3
- package/dist/resources/blob.d.ts +25 -2
- package/dist/resources/databases.d.ts +12 -2
- package/dist/resources/databases.js +22 -19
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/search.js +11 -5
- package/dist/resources/search.js.map +1 -1
- package/dist/resources/transaction.d.ts +2 -1
- package/dist/security/auth.js +1 -1
- package/dist/security/auth.js.map +1 -1
- package/dist/security/cryptoHash.d.ts +2 -2
- package/dist/security/jsLoader.js +265 -73
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/security/keys.js +11 -12
- package/dist/security/keys.js.map +1 -1
- package/dist/security/user.js +3 -3
- package/dist/security/user.js.map +1 -1
- package/dist/server/REST.js +16 -2
- package/dist/server/REST.js.map +1 -1
- package/dist/server/Server.d.ts +2 -1
- package/dist/server/Server.js.map +1 -1
- package/dist/server/fastifyRoutes/plugins/hdbCore.d.ts +6 -1
- package/dist/server/fastifyRoutes.js +2 -0
- package/dist/server/fastifyRoutes.js.map +1 -1
- package/dist/server/http.js +12 -6
- package/dist/server/http.js.map +1 -1
- package/dist/server/jobs/JobObject.d.ts +3 -3
- package/dist/server/loadRootComponents.js +1 -0
- package/dist/server/loadRootComponents.js.map +1 -1
- package/dist/server/operationsServer.js +3 -1
- package/dist/server/operationsServer.js.map +1 -1
- package/dist/server/serverHelpers/JSONStream.d.ts +3 -3
- package/dist/server/serverHelpers/Request.d.ts +5 -5
- package/dist/server/serverHelpers/requestTimePlugin.d.ts +1 -1
- package/dist/server/threads/manageThreads.d.ts +2 -2
- package/dist/server/threads/manageThreads.js +52 -35
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/socketRouter.d.ts +1 -1
- package/dist/sqlTranslator/deleteTranslator.d.ts +1 -1
- package/dist/utility/AWS/AWSConnector.d.ts +3 -2
- package/dist/utility/common_utils.d.ts +3 -3
- package/dist/utility/environment/systemInformation.d.ts +1 -0
- package/dist/utility/functions/date/dateFunctions.d.ts +11 -11
- package/dist/utility/globalSchema.d.ts +1 -1
- package/dist/utility/hdbTerms.d.ts +3 -0
- package/dist/utility/hdbTerms.js +3 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/installation.d.ts +2 -4
- package/dist/utility/installation.js.map +1 -1
- package/dist/utility/lmdb/commonUtility.d.ts +2 -1
- package/dist/utility/lmdb/commonUtility.js +20 -13
- package/dist/utility/lmdb/commonUtility.js.map +1 -1
- package/dist/utility/lmdb/deleteUtility.d.ts +1 -0
- package/dist/utility/lmdb/environmentUtility.d.ts +1 -0
- package/dist/utility/lmdb/searchUtility.d.ts +2 -1
- package/dist/utility/lmdb/writeUtility.d.ts +1 -0
- package/dist/utility/logging/harper_logger.d.ts +6 -6
- package/dist/utility/processManagement/processManagement.d.ts +1 -1
- package/dist/utility/processManagement/servicesConfig.d.ts +12 -6
- package/dist/validation/common_validators.d.ts +4 -3
- package/dist/validation/configValidator.d.ts +3 -2
- package/index.d.ts +56 -0
- package/index.js +41 -0
- package/json/systemSchema.json +373 -0
- package/launchServiceScripts/launchHarperDB.js +3 -0
- package/launchServiceScripts/utility/checkNodeVersion.js +15 -0
- package/package.json +35 -16
- package/resources/DatabaseTransaction.ts +378 -0
- package/resources/ErrorResource.ts +57 -0
- package/resources/IterableEventQueue.ts +94 -0
- package/resources/LMDBTransaction.ts +349 -0
- package/resources/RecordEncoder.ts +702 -0
- package/resources/RequestTarget.ts +134 -0
- package/resources/Resource.ts +789 -0
- package/resources/ResourceInterface.ts +221 -0
- package/resources/Resources.ts +162 -0
- package/resources/RocksIndexStore.ts +70 -0
- package/resources/RocksTransactionLogStore.ts +352 -0
- package/resources/Table.ts +4531 -0
- package/resources/analytics/hostnames.ts +72 -0
- package/resources/analytics/metadata.ts +10 -0
- package/resources/analytics/read.ts +252 -0
- package/resources/analytics/write.ts +803 -0
- package/resources/auditStore.ts +556 -0
- package/resources/blob.ts +1268 -0
- package/resources/crdt.ts +125 -0
- package/resources/dataLoader.ts +527 -0
- package/resources/databases.ts +1290 -0
- package/resources/graphql.ts +221 -0
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +638 -0
- package/resources/indexes/customIndexes.ts +7 -0
- package/resources/indexes/vector.ts +38 -0
- package/resources/jsResource.ts +86 -0
- package/resources/loadEnv.ts +22 -0
- package/resources/login.ts +18 -0
- package/resources/openApi.ts +409 -0
- package/resources/registrationDeprecated.ts +8 -0
- package/resources/replayLogs.ts +136 -0
- package/resources/roles.ts +98 -0
- package/resources/search.ts +1301 -0
- package/resources/tracked.ts +584 -0
- package/resources/transaction.ts +89 -0
- package/resources/transactionBroadcast.ts +258 -0
- package/security/auth.ts +376 -0
- package/security/certificateVerification/certificateVerificationSource.ts +84 -0
- package/security/certificateVerification/configValidation.ts +107 -0
- package/security/certificateVerification/crlVerification.ts +623 -0
- package/security/certificateVerification/index.ts +121 -0
- package/security/certificateVerification/ocspVerification.ts +148 -0
- package/security/certificateVerification/pkijs-ed25519-patch.ts +188 -0
- package/security/certificateVerification/types.ts +128 -0
- package/security/certificateVerification/verificationConfig.ts +138 -0
- package/security/certificateVerification/verificationUtils.ts +447 -0
- package/security/cryptoHash.js +42 -0
- package/security/data_objects/PermissionAttributeResponseObject.js +15 -0
- package/security/data_objects/PermissionResponseObject.js +115 -0
- package/security/data_objects/PermissionTableResponseObject.js +20 -0
- package/security/fastifyAuth.js +169 -0
- package/security/impersonation.ts +160 -0
- package/security/jsLoader.ts +733 -0
- package/security/keys.js +948 -0
- package/security/permissionsTranslator.js +300 -0
- package/security/role.js +218 -0
- package/security/tokenAuthentication.ts +228 -0
- package/security/user.ts +449 -0
- package/server/DurableSubscriptionsSession.ts +503 -0
- package/server/REST.ts +407 -0
- package/server/Server.ts +89 -0
- package/server/fastifyRoutes/helpers/getCORSOptions.js +36 -0
- package/server/fastifyRoutes/helpers/getHeaderTimeoutConfig.js +15 -0
- package/server/fastifyRoutes/helpers/getServerOptions.js +33 -0
- package/server/fastifyRoutes/plugins/hdbCore.js +39 -0
- package/server/fastifyRoutes.ts +205 -0
- package/server/graphqlQuerying.ts +700 -0
- package/server/http.ts +640 -0
- package/server/itc/serverHandlers.js +161 -0
- package/server/itc/utility/ITCEventObject.js +10 -0
- package/server/jobs/JobObject.js +24 -0
- package/server/jobs/jobProcess.js +69 -0
- package/server/jobs/jobRunner.js +162 -0
- package/server/jobs/jobs.js +304 -0
- package/server/loadRootComponents.js +44 -0
- package/server/mqtt.ts +485 -0
- package/server/nodeName.ts +75 -0
- package/server/operationsServer.ts +313 -0
- package/server/serverHelpers/Headers.ts +108 -0
- package/server/serverHelpers/JSONStream.ts +269 -0
- package/server/serverHelpers/OperationFunctionObject.ts +13 -0
- package/server/serverHelpers/Request.ts +158 -0
- package/server/serverHelpers/contentTypes.ts +637 -0
- package/server/serverHelpers/requestTimePlugin.js +57 -0
- package/server/serverHelpers/serverHandlers.js +148 -0
- package/server/serverHelpers/serverUtilities.ts +473 -0
- package/server/serverRegistry.ts +8 -0
- package/server/static.ts +187 -0
- package/server/status/definitions.ts +37 -0
- package/server/status/index.ts +125 -0
- package/server/storageReclamation.ts +93 -0
- package/server/threads/itc.js +89 -0
- package/server/threads/manageThreads.js +596 -0
- package/server/threads/socketRouter.ts +360 -0
- package/server/threads/threadServer.js +279 -0
- package/server/throttle.ts +73 -0
- package/sqlTranslator/SelectValidator.js +330 -0
- package/sqlTranslator/alasqlFunctionImporter.js +62 -0
- package/sqlTranslator/deleteTranslator.js +67 -0
- package/sqlTranslator/index.js +242 -0
- package/sqlTranslator/sql_statement_bucket.js +472 -0
- package/static/defaultConfig.yaml +3 -0
- package/studio/web/HDBDogOnly.svg +78 -0
- package/studio/web/assets/PPRadioGrotesk-Bold-DDaUYG8E.woff +0 -0
- package/studio/web/assets/fa-brands-400-CEJbCg16.woff +0 -0
- package/studio/web/assets/fa-brands-400-CSYNqBb_.ttf +0 -0
- package/studio/web/assets/fa-brands-400-DnkPfk3o.eot +0 -0
- package/studio/web/assets/fa-brands-400-UxlILjvJ.woff2 +0 -0
- package/studio/web/assets/fa-brands-400-cH1MgKbP.svg +3717 -0
- package/studio/web/assets/fa-regular-400-BhTwtT8w.eot +0 -0
- package/studio/web/assets/fa-regular-400-D1vz6WBx.ttf +0 -0
- package/studio/web/assets/fa-regular-400-DFnMcJPd.woff +0 -0
- package/studio/web/assets/fa-regular-400-DGzu1beS.woff2 +0 -0
- package/studio/web/assets/fa-regular-400-gwj8Pxq-.svg +801 -0
- package/studio/web/assets/fa-solid-900-B4ZZ7kfP.svg +5034 -0
- package/studio/web/assets/fa-solid-900-B6Axprfb.eot +0 -0
- package/studio/web/assets/fa-solid-900-BUswJgRo.woff2 +0 -0
- package/studio/web/assets/fa-solid-900-DOXgCApm.woff +0 -0
- package/studio/web/assets/fa-solid-900-mxuxnBEa.ttf +0 -0
- package/studio/web/assets/index-C1G-Jo6n.js +37 -0
- package/studio/web/assets/index-C1G-Jo6n.js.map +1 -0
- package/studio/web/assets/index-D-CahN0-.js +2 -0
- package/studio/web/assets/index-D-CahN0-.js.map +1 -0
- package/studio/web/assets/index-DxlZI0PX.js +235 -0
- package/studio/web/assets/index-DxlZI0PX.js.map +1 -0
- package/studio/web/assets/index-Y2g_iFpU.css +1 -0
- package/studio/web/assets/index-jiPwkrsB.css +1 -0
- package/studio/web/assets/index.lazy-BUXDDqq9.js +266 -0
- package/studio/web/assets/index.lazy-BUXDDqq9.js.map +1 -0
- package/studio/web/assets/profiler-CU93QiSW.js +2 -0
- package/studio/web/assets/profiler-CU93QiSW.js.map +1 -0
- package/studio/web/assets/react-redux-B8k9Ep7e.js +6 -0
- package/studio/web/assets/react-redux-B8k9Ep7e.js.map +1 -0
- package/studio/web/assets/startRecording-DFeBXGk6.js +3 -0
- package/studio/web/assets/startRecording-DFeBXGk6.js.map +1 -0
- package/studio/web/fabric-signup-background.webp +0 -0
- package/studio/web/fabric-signup-text.png +0 -0
- package/studio/web/favicon_purple.png +0 -0
- package/studio/web/github-icon.svg +15 -0
- package/studio/web/harper-fabric_black.png +0 -0
- package/studio/web/harper-fabric_white.png +0 -0
- package/studio/web/harper-studio_white.png +0 -0
- package/studio/web/index.html +16 -0
- package/studio/web/running.css +148 -0
- package/studio/web/running.html +147 -0
- package/studio/web/running.js +111 -0
- package/upgrade/UpgradeObjects.js +13 -0
- package/upgrade/directives/directivesController.js +90 -0
- package/upgrade/directivesManager.js +139 -0
- package/upgrade/upgradePrompt.js +124 -0
- package/upgrade/upgradeUtilities.js +28 -0
- package/utility/AWS/AWSConnector.js +29 -0
- package/utility/OperationFunctionCaller.js +63 -0
- package/utility/assignCmdEnvVariables.js +62 -0
- package/utility/common_utils.js +867 -0
- package/utility/environment/environmentManager.js +208 -0
- package/utility/environment/systemInformation.js +355 -0
- package/utility/errors/commonErrors.js +267 -0
- package/utility/errors/hdbError.js +146 -0
- package/utility/functions/date/dateFunctions.js +65 -0
- package/utility/functions/geo.js +355 -0
- package/utility/functions/sql/alaSQLExtension.js +104 -0
- package/utility/globalSchema.js +35 -0
- package/utility/hdbTerms.ts +819 -0
- package/utility/install/checkJWTTokensExist.js +62 -0
- package/utility/install/harperdb.conf +15 -0
- package/utility/install/harperdb.service +14 -0
- package/utility/install/installer.js +635 -0
- package/utility/installation.ts +30 -0
- package/utility/lmdb/DBIDefinition.js +20 -0
- package/utility/lmdb/DeleteRecordsResponseObject.js +25 -0
- package/utility/lmdb/InsertRecordsResponseObject.js +22 -0
- package/utility/lmdb/OpenDBIObject.js +31 -0
- package/utility/lmdb/OpenEnvironmentObject.js +41 -0
- package/utility/lmdb/UpdateRecordsResponseObject.js +25 -0
- package/utility/lmdb/UpsertRecordsResponseObject.js +22 -0
- package/utility/lmdb/cleanLMDBMap.js +65 -0
- package/utility/lmdb/commonUtility.js +130 -0
- package/utility/lmdb/deleteUtility.js +128 -0
- package/utility/lmdb/environmentUtility.js +477 -0
- package/utility/lmdb/searchCursorFunctions.js +187 -0
- package/utility/lmdb/searchUtility.js +918 -0
- package/utility/lmdb/terms.js +57 -0
- package/utility/lmdb/writeUtility.js +407 -0
- package/utility/logging/harper_logger.js +876 -0
- package/utility/logging/logRotator.js +157 -0
- package/utility/logging/logger.ts +24 -0
- package/utility/logging/readLog.js +355 -0
- package/utility/logging/transactionLog.js +57 -0
- package/utility/mount_hdb.js +59 -0
- package/utility/npmUtilities.js +102 -0
- package/utility/operationPermissions.ts +112 -0
- package/utility/operation_authorization.js +836 -0
- package/utility/packageUtils.js +55 -0
- package/utility/password.ts +99 -0
- package/utility/processManagement/processManagement.js +187 -0
- package/utility/processManagement/servicesConfig.js +56 -0
- package/utility/scripts/restartHdb.js +24 -0
- package/utility/scripts/user_data.sh +13 -0
- package/utility/signalling.js +36 -0
- package/utility/terms/certificates.js +81 -0
- package/utility/when.ts +20 -0
- package/validation/bulkDeleteValidator.js +24 -0
- package/validation/check_permissions.js +19 -0
- package/validation/common_validators.js +95 -0
- package/validation/configValidator.js +331 -0
- package/validation/deleteValidator.js +15 -0
- package/validation/fileLoadValidator.js +153 -0
- package/validation/insertValidator.js +40 -0
- package/validation/installValidator.js +37 -0
- package/validation/readLogValidator.js +64 -0
- package/validation/role_validation.js +320 -0
- package/validation/schemaMetadataValidator.js +42 -0
- package/validation/searchValidator.js +166 -0
- package/validation/statusValidator.ts +66 -0
- package/validation/transactionLogValidator.js +33 -0
- package/validation/user_validation.js +55 -0
- package/validation/validationWrapper.js +105 -0
- package/dist/resources/ResourceInterfaceV2.d.ts +0 -21
- package/dist/resources/ResourceInterfaceV2.js.map +0 -1
- package/dist/resources/ResourceV2.d.ts +0 -30
- package/dist/resources/ResourceV2.js +0 -27
- package/dist/resources/ResourceV2.js.map +0 -1
- package/dist/resources/analytics/profile.d.ts +0 -2
- package/dist/resources/analytics/profile.js +0 -144
- package/dist/resources/analytics/profile.js.map +0 -1
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
import { cosineDistance, euclideanDistance } from './vector.ts';
|
|
2
|
+
import { FLOAT32_OPTIONS } from 'msgpackr';
|
|
3
|
+
import { loggerWithTag } from '../../utility/logging/logger.ts';
|
|
4
|
+
import { ClientError } from '../../utility/errors/hdbError.js';
|
|
5
|
+
import type { Id } from '../../resources/ResourceInterface.ts';
|
|
6
|
+
|
|
7
|
+
const logger = loggerWithTag('HNSW');
|
|
8
|
+
/**
|
|
9
|
+
* Implementation of a vector index for Harper, using hierarchical navigable small world graphs.
|
|
10
|
+
*/
|
|
11
|
+
const ENTRY_POINT = Symbol.for('entryPoint');
|
|
12
|
+
const KEY_PREFIX = Symbol.for('key');
|
|
13
|
+
const MAX_LEVEL = 10; // should give good high-level skip list performance up to trillions of nodes
|
|
14
|
+
type Connection = {
|
|
15
|
+
id: number;
|
|
16
|
+
distance: number;
|
|
17
|
+
};
|
|
18
|
+
type Node = {
|
|
19
|
+
vector: number[];
|
|
20
|
+
level?: number;
|
|
21
|
+
primaryKey: string;
|
|
22
|
+
[level: number]: Connection[];
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Represents a Hierarchical Navigable Small World (HNSW) index for approximate nearest neighbor search.
|
|
26
|
+
* This implementation is based on hierarchical graph navigation to efficiently index and search high-dimensional vectors.
|
|
27
|
+
* A HNSW is basically a multi-dimensional skip list. Each node has (potentially) higher levels that are used for quickly
|
|
28
|
+
* traversing the graph get in the neighborhood of the node, and then lower levels are used to more accurately find the
|
|
29
|
+
* closest neighbors.
|
|
30
|
+
*
|
|
31
|
+
* This implementation is based on the paper "Efficient and Robust Approximate Nearest Neighbor Search in High Dimensions"
|
|
32
|
+
* (mostly influenced AI's contributions)
|
|
33
|
+
*/
|
|
34
|
+
export class HierarchicalNavigableSmallWorld {
|
|
35
|
+
static useObjectStore = true;
|
|
36
|
+
indexStore: any;
|
|
37
|
+
M: number = 16; // max number of connections per layer
|
|
38
|
+
efConstruction: number = 100; // size of dynamic candidate list
|
|
39
|
+
efConstructionSearch: number = 50; // size of dynamic candidate list for search
|
|
40
|
+
mL: number = 1 / Math.log(this.M); // normalization factor for level generation
|
|
41
|
+
// how aggressive do we avoid connections that have alternate indirect routes; a value of 0 never avoids connections,
|
|
42
|
+
// a value of 1 is extremely aggressive.
|
|
43
|
+
optimizeRouting = 0.5;
|
|
44
|
+
nodesVisitedCount = 0;
|
|
45
|
+
|
|
46
|
+
idIncrementer: BigInt64Array | undefined;
|
|
47
|
+
distance: (a: number[], b: number[]) => number;
|
|
48
|
+
constructor(indexStore: any, options: any) {
|
|
49
|
+
this.indexStore = indexStore;
|
|
50
|
+
if (indexStore) {
|
|
51
|
+
// use float32 representation of numbers as it is twice as space efficient as typical float64 and plenty accurate
|
|
52
|
+
// (we would actually like to use float16 if it were available)
|
|
53
|
+
this.indexStore.encoder.useFloat32 = FLOAT32_OPTIONS.ALWAYS;
|
|
54
|
+
}
|
|
55
|
+
this.distance = options?.distance === 'euclidean' ? euclideanDistance : cosineDistance;
|
|
56
|
+
if (options) {
|
|
57
|
+
// allow all the HNSW parameters to be configured/tuned
|
|
58
|
+
if (options.M !== undefined) {
|
|
59
|
+
this.M = options.M;
|
|
60
|
+
this.mL = 1 / Math.log(this.M); // recalculate
|
|
61
|
+
}
|
|
62
|
+
if (options.efConstruction !== undefined)
|
|
63
|
+
this.efConstruction = this.efConstructionSearch = options.efConstruction;
|
|
64
|
+
if (options.efConstructionSearch !== undefined) this.efConstructionSearch = options.efConstructionSearch;
|
|
65
|
+
if (options.mL !== undefined) this.mL = options.mL;
|
|
66
|
+
if (options.optimizeRouting !== undefined) this.optimizeRouting = options.optimizeRouting;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
index(primaryKey: Id, vector: number[], existingVector?: number[], options: any = {}) {
|
|
70
|
+
// first get the node id for the primary key; we use internal node ids for better efficiency,
|
|
71
|
+
// but we must use a safe key that won't collide with the node ids
|
|
72
|
+
const safeKey = typeof primaryKey === 'number' ? [KEY_PREFIX, primaryKey] : primaryKey;
|
|
73
|
+
let nodeId = this.indexStore.getSync(safeKey, options);
|
|
74
|
+
// if the node id is not found, create a new node (and store it in the index store)
|
|
75
|
+
// (note that we don't need to check if the node id is already in the index store,
|
|
76
|
+
// because we use internal node ids for better efficiency, and we use a safe key
|
|
77
|
+
// that won't collide with the node ids, so we can't have a collision with internal
|
|
78
|
+
if (!nodeId) {
|
|
79
|
+
if (!vector) return; // didn't exist before, doesn't exist now, nothing to do
|
|
80
|
+
if (!this.idIncrementer) {
|
|
81
|
+
let largestNodeId = 0;
|
|
82
|
+
for (const key of this.indexStore.getKeys({
|
|
83
|
+
reverse: true,
|
|
84
|
+
limit: 1,
|
|
85
|
+
start: Infinity,
|
|
86
|
+
end: 0,
|
|
87
|
+
transaction: options.transaction,
|
|
88
|
+
})) {
|
|
89
|
+
if (typeof key === 'number') largestNodeId = key;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.idIncrementer = new BigInt64Array([BigInt(largestNodeId) + 1n]);
|
|
93
|
+
this.idIncrementer = new BigInt64Array(
|
|
94
|
+
this.indexStore.getUserSharedBuffer('next-id', this.idIncrementer.buffer)
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
nodeId = Number(Atomics.add(this.idIncrementer, 0, 1n));
|
|
98
|
+
this.indexStore.put(safeKey, nodeId, options);
|
|
99
|
+
}
|
|
100
|
+
const updatedNodes = new Map<number, Node>();
|
|
101
|
+
let oldNode: Node;
|
|
102
|
+
// If this is the first entry, create it as the entry point
|
|
103
|
+
let entryPointId = this.indexStore.getSync(ENTRY_POINT, options);
|
|
104
|
+
if (existingVector) {
|
|
105
|
+
// If we are updating an existing entry, we need to update the entry point
|
|
106
|
+
// if the new entry is closer to the entry point than the old one
|
|
107
|
+
oldNode = { ...this.indexStore.getSync(nodeId, options) };
|
|
108
|
+
} else oldNode = {} as Node;
|
|
109
|
+
if (vector) {
|
|
110
|
+
let entryPoint = entryPointId && this.indexStore.getSync(entryPointId, options);
|
|
111
|
+
if (entryPoint == null) {
|
|
112
|
+
const level = Math.floor(-Math.log(Math.random()) * this.mL);
|
|
113
|
+
const node = {
|
|
114
|
+
vector,
|
|
115
|
+
level,
|
|
116
|
+
primaryKey,
|
|
117
|
+
};
|
|
118
|
+
for (let i = 0; i <= level; i++) {
|
|
119
|
+
node[i] = [];
|
|
120
|
+
}
|
|
121
|
+
this.indexStore.put(nodeId, node, options);
|
|
122
|
+
if (typeof nodeId !== 'number') {
|
|
123
|
+
throw new Error('Invalid nodeId: ' + nodeId);
|
|
124
|
+
}
|
|
125
|
+
logger.debug?.('setting entry point to', nodeId);
|
|
126
|
+
this.indexStore.put(ENTRY_POINT, nodeId, options);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Generate random level for this new element
|
|
131
|
+
const level = oldNode.level ?? Math.min(Math.floor(-Math.log(Math.random()) * this.mL), MAX_LEVEL);
|
|
132
|
+
let currentLevel = entryPoint.level;
|
|
133
|
+
if (level >= currentLevel) {
|
|
134
|
+
// if we are at this level or higher, make this the new entry point
|
|
135
|
+
if (typeof nodeId !== 'number') {
|
|
136
|
+
throw new Error('Invalid nodeId: ' + nodeId);
|
|
137
|
+
}
|
|
138
|
+
logger.debug?.('setting entry point to', nodeId);
|
|
139
|
+
this.indexStore.put(ENTRY_POINT, nodeId, options);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// For each level from top to bottom
|
|
143
|
+
while (currentLevel > level) {
|
|
144
|
+
// Search for closest neighbors at current level
|
|
145
|
+
const neighbors = this.searchLayer(vector, entryPointId, entryPoint, this.efConstruction, currentLevel);
|
|
146
|
+
|
|
147
|
+
if (neighbors.length > 0) {
|
|
148
|
+
entryPointId = neighbors[0].id; // closest neighbor becomes new entry point
|
|
149
|
+
entryPoint = neighbors[0].node;
|
|
150
|
+
}
|
|
151
|
+
currentLevel--;
|
|
152
|
+
}
|
|
153
|
+
const connections = new Array(level + 1);
|
|
154
|
+
for (let i = 0; i <= level; i++) {
|
|
155
|
+
connections[i] = [];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Connect the new element to neighbors at its level and below
|
|
159
|
+
for (let l = Math.min(level, currentLevel); l >= 0; l--) {
|
|
160
|
+
let neighbors = this.searchLayer(vector, entryPointId, entryPoint, this.efConstruction, l);
|
|
161
|
+
neighbors = neighbors.slice(0, this.M << 1) as SearchResults;
|
|
162
|
+
|
|
163
|
+
if (neighbors.length === 0 && l === 0) {
|
|
164
|
+
logger.info?.('should not have zero connections for', entryPointId);
|
|
165
|
+
}
|
|
166
|
+
const connectionsAtLevel = connections[l];
|
|
167
|
+
// Create bidirectional connections
|
|
168
|
+
for (let i = 0; i < neighbors.length; i++) {
|
|
169
|
+
const { id, distance, node } = neighbors[i];
|
|
170
|
+
if (id === nodeId) continue; // don't connect to self
|
|
171
|
+
const connectionsToBeReplaced: { fromId: number; toId: number }[] = [];
|
|
172
|
+
if (this.optimizeRouting) {
|
|
173
|
+
// if we have existing connections through other nodes, we deprioritize new connections through them.
|
|
174
|
+
// I believe this yields better HNSW graphs, avoiding redundant paths, with better directed connectivity
|
|
175
|
+
// towards desired results
|
|
176
|
+
let skipping = false;
|
|
177
|
+
const neighborNeighbors = node[l];
|
|
178
|
+
const distanceThreshold = 1 + this.optimizeRouting * (1 + (0.5 * i) / this.M);
|
|
179
|
+
for (let i2 = 0; i2 < neighborNeighbors?.length; i2++) {
|
|
180
|
+
const { id: neighborId, distance: neighborDistance } = neighborNeighbors[i2];
|
|
181
|
+
const neighborDistanceThreshold = 1 + this.optimizeRouting * (1 + (0.5 * i2) / this.M);
|
|
182
|
+
for (let i3 = 0; i3 < connectionsAtLevel.length; i3++) {
|
|
183
|
+
const { id: addedId, distance: addedDistance } = connectionsAtLevel[i3];
|
|
184
|
+
if (addedId === neighborId) {
|
|
185
|
+
if (distance * distanceThreshold > addedDistance + neighborDistance) {
|
|
186
|
+
// if the new distance is relatively low compared to existing indirect connections,
|
|
187
|
+
// we skip this neighbor since it is of less value
|
|
188
|
+
skipping = true;
|
|
189
|
+
} else if (neighborDistance * neighborDistanceThreshold > distance + addedDistance) {
|
|
190
|
+
// potentially remove the neighbor's neighbor, because we are adding a better route (if we do add it)
|
|
191
|
+
connectionsToBeReplaced.push({ fromId: addedId, toId: id });
|
|
192
|
+
connectionsToBeReplaced.push({ fromId: id, toId: addedId });
|
|
193
|
+
}
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (skipping) break;
|
|
198
|
+
}
|
|
199
|
+
if (skipping) continue;
|
|
200
|
+
} else if (i >= (l > 0 ? this.M : this.M << 1)) {
|
|
201
|
+
// fallback to traditional HNSW level limiting; if we are at the maximum number of neighbors, we skip this one
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
// Add connection to the new element
|
|
205
|
+
connectionsAtLevel.push({ id, distance });
|
|
206
|
+
|
|
207
|
+
for (const { fromId, toId } of connectionsToBeReplaced) {
|
|
208
|
+
let from = updateNode(fromId);
|
|
209
|
+
if (!from) from = updateNode(fromId, this.indexStore.getSync(fromId, options));
|
|
210
|
+
for (let i = 0; i < from[l].length; i++) {
|
|
211
|
+
if (from[l][i].id === toId) {
|
|
212
|
+
if (Object.isFrozen(from[l])) {
|
|
213
|
+
from[l] = from[l].slice();
|
|
214
|
+
}
|
|
215
|
+
from[l].splice(i, 1);
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Add reverse connection from neighbor to new element if it didn't exist before
|
|
222
|
+
// First check to see if we had an existing neighbor connection before. If we did we can
|
|
223
|
+
// just remove from the list of the connections to remove (don't remove, leave it in place)
|
|
224
|
+
let oldConnections = oldNode[l] as WithCopied;
|
|
225
|
+
const oldConnection = oldConnections?.find(({ id: nid }) => nid === id);
|
|
226
|
+
if (oldConnection) {
|
|
227
|
+
const oldPosition = oldConnections?.indexOf(oldConnection);
|
|
228
|
+
if (!oldConnections.copied) {
|
|
229
|
+
// make a copy, it is likely frozen
|
|
230
|
+
oldConnections = [...oldConnections] as WithCopied;
|
|
231
|
+
oldConnections.copied = true;
|
|
232
|
+
oldNode[l] = oldConnections;
|
|
233
|
+
}
|
|
234
|
+
oldConnections.splice(oldPosition, 1);
|
|
235
|
+
} else {
|
|
236
|
+
// add new connection since this is truly a new connection now
|
|
237
|
+
this.addConnection(id, updateNode(id, node), nodeId, l, distance, updateNode, options);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Store the new element
|
|
243
|
+
this.indexStore.put(
|
|
244
|
+
nodeId,
|
|
245
|
+
{
|
|
246
|
+
vector,
|
|
247
|
+
level,
|
|
248
|
+
primaryKey,
|
|
249
|
+
...connections,
|
|
250
|
+
},
|
|
251
|
+
options
|
|
252
|
+
);
|
|
253
|
+
} else {
|
|
254
|
+
// removal of this node, but first make sure we have a valid entry point
|
|
255
|
+
if (entryPointId === nodeId) {
|
|
256
|
+
// if this is the entry point, find a new entry point
|
|
257
|
+
const lastLevel = oldNode.level ?? 0;
|
|
258
|
+
for (let l = lastLevel; l >= 0; l--) {
|
|
259
|
+
entryPointId = oldNode[l]?.[0]?.id;
|
|
260
|
+
if (entryPointId !== undefined) break;
|
|
261
|
+
}
|
|
262
|
+
if (entryPointId === undefined) {
|
|
263
|
+
// scan through all nodes to find one with highest level
|
|
264
|
+
let highestLevel = -1;
|
|
265
|
+
for (const { key, value } of this.indexStore.getRange({
|
|
266
|
+
start: 0,
|
|
267
|
+
end: Infinity,
|
|
268
|
+
})) {
|
|
269
|
+
if (value.level > highestLevel) {
|
|
270
|
+
entryPointId = key;
|
|
271
|
+
if (value.level === lastLevel) break; // if we found a node at the same level as the last entry point, we can stop
|
|
272
|
+
highestLevel = value.level;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (entryPointId === undefined) {
|
|
277
|
+
// no nodes left in index
|
|
278
|
+
this.indexStore.remove(ENTRY_POINT, options);
|
|
279
|
+
} else {
|
|
280
|
+
// set the new entry point
|
|
281
|
+
if (typeof entryPointId !== 'number') {
|
|
282
|
+
throw new Error('Invalid nodeId: ' + entryPointId);
|
|
283
|
+
}
|
|
284
|
+
logger.debug?.('setting entry point to', entryPointId);
|
|
285
|
+
this.indexStore.put(ENTRY_POINT, entryPointId, options);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
this.indexStore.remove(nodeId, options);
|
|
289
|
+
}
|
|
290
|
+
const needsReindexing = new Map();
|
|
291
|
+
// remove connections to this node that are no longer valid
|
|
292
|
+
if (oldNode.level !== undefined) {
|
|
293
|
+
for (let l = 0; l <= oldNode.level; l++) {
|
|
294
|
+
const oldConnections = oldNode[l];
|
|
295
|
+
for (const { id: neighborId } of oldConnections) {
|
|
296
|
+
// get and copy the neighbor node so we can modify it
|
|
297
|
+
const neighborNode = updateNode(neighborId, this.indexStore.getSync(neighborId, options));
|
|
298
|
+
if (!neighborNode) continue;
|
|
299
|
+
for (let l2 = 0; l2 <= l; l2++) {
|
|
300
|
+
// remove the connection to this node from the neighbor node
|
|
301
|
+
neighborNode[l2] = neighborNode[l2]?.filter(({ id: nid }) => {
|
|
302
|
+
return nid !== nodeId;
|
|
303
|
+
});
|
|
304
|
+
if (neighborNode[l2]?.length === 0) {
|
|
305
|
+
logger.trace?.('node was left orphaned, will reindex', neighborId);
|
|
306
|
+
needsReindexing.set(neighborNode.primaryKey, neighborNode.vector);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function updateNode(id: number, node?: Node) {
|
|
313
|
+
// keep a record of all our changes, maintaining any changes that are queued to be written
|
|
314
|
+
let updatedNode: Node = updatedNodes.get(id);
|
|
315
|
+
if (!updatedNode && node) {
|
|
316
|
+
// copy the node so we can modify it
|
|
317
|
+
updatedNode = { ...node };
|
|
318
|
+
updatedNodes.set(id, updatedNode);
|
|
319
|
+
}
|
|
320
|
+
return updatedNode;
|
|
321
|
+
}
|
|
322
|
+
for (const [id, updatedNode] of updatedNodes) {
|
|
323
|
+
this.indexStore.put(id, updatedNode, options);
|
|
324
|
+
}
|
|
325
|
+
for (const [key, vector] of needsReindexing) {
|
|
326
|
+
this.index(key, vector, vector);
|
|
327
|
+
}
|
|
328
|
+
this.checkSymmetry(nodeId, this.indexStore.getSync(nodeId, options), options);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private getEntryPoint() {
|
|
332
|
+
// Get entry point
|
|
333
|
+
const entryPointId = this.indexStore.getSync(ENTRY_POINT);
|
|
334
|
+
if (entryPointId === undefined) return;
|
|
335
|
+
const node = this.indexStore.getSync(entryPointId);
|
|
336
|
+
return { id: entryPointId, ...node };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Search one layer of the skip-list using HNSW algorithm for creating a candidate list and navigating the graph
|
|
341
|
+
* TODO: This should be async, but we can't really do that with lmdb-js's transaction system right now. Should be
|
|
342
|
+
* doable with RocksDB. We could also create an async version for searching.
|
|
343
|
+
* @param queryVector
|
|
344
|
+
* @param entryPointId
|
|
345
|
+
* @param entryPoint
|
|
346
|
+
* @param ef
|
|
347
|
+
* @param level
|
|
348
|
+
* @param distanceFunction
|
|
349
|
+
* @private
|
|
350
|
+
*/
|
|
351
|
+
private searchLayer(
|
|
352
|
+
queryVector: number[],
|
|
353
|
+
entryPointId: number,
|
|
354
|
+
entryPoint: any,
|
|
355
|
+
ef: number,
|
|
356
|
+
level: number,
|
|
357
|
+
distanceFunction = this.distance
|
|
358
|
+
): SearchResults {
|
|
359
|
+
const visited = new Set([entryPointId]);
|
|
360
|
+
const candidates = [
|
|
361
|
+
{
|
|
362
|
+
id: entryPointId,
|
|
363
|
+
distance: this.distance(queryVector, entryPoint.vector),
|
|
364
|
+
node: entryPoint,
|
|
365
|
+
},
|
|
366
|
+
];
|
|
367
|
+
const results = [...candidates] as SearchResults;
|
|
368
|
+
|
|
369
|
+
while (candidates.length > 0) {
|
|
370
|
+
// Get closest unvisited element
|
|
371
|
+
candidates.sort((a, b) => a.distance - b.distance);
|
|
372
|
+
const current = candidates.shift();
|
|
373
|
+
|
|
374
|
+
// Get least result distance
|
|
375
|
+
const furthestDistance = results[results.length - 1].distance;
|
|
376
|
+
|
|
377
|
+
// If current candidate is less similar than our worst result, we're done
|
|
378
|
+
if (current.distance > furthestDistance) break;
|
|
379
|
+
|
|
380
|
+
// Check neighbors of current point
|
|
381
|
+
const currentNode = current.node;
|
|
382
|
+
for (const { id: neighborId } of currentNode[level] || []) {
|
|
383
|
+
if (visited.has(neighborId) || neighborId === undefined) continue;
|
|
384
|
+
visited.add(neighborId);
|
|
385
|
+
|
|
386
|
+
const neighbor = this.indexStore.getSync(neighborId);
|
|
387
|
+
if (!neighbor) continue;
|
|
388
|
+
this.nodesVisitedCount++;
|
|
389
|
+
const distance = distanceFunction(queryVector, neighbor.vector);
|
|
390
|
+
|
|
391
|
+
if (distance < furthestDistance || results.length < ef) {
|
|
392
|
+
const candidate = {
|
|
393
|
+
id: neighborId,
|
|
394
|
+
distance,
|
|
395
|
+
node: neighbor,
|
|
396
|
+
};
|
|
397
|
+
candidates.push(candidate);
|
|
398
|
+
results.push(candidate);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
results.sort((a, b) => a.distance - b.distance);
|
|
402
|
+
if (results.length > ef) results.splice(ef, results.length - ef);
|
|
403
|
+
}
|
|
404
|
+
results.visited = visited.size;
|
|
405
|
+
return results;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* This the main entry from Harper's query functionality, where we actually search for an ordered list of nearest
|
|
410
|
+
* neighbors, using the provided sort/order definition object and performing the multi-layer skip-list search.
|
|
411
|
+
* This returns an iterable of the nearest neighbors to the provided target vector, with nearest ordered first.
|
|
412
|
+
* @param target
|
|
413
|
+
* @param value
|
|
414
|
+
* @param descending
|
|
415
|
+
* @param distance
|
|
416
|
+
* @param comparator
|
|
417
|
+
* @param context
|
|
418
|
+
*/
|
|
419
|
+
search({
|
|
420
|
+
target,
|
|
421
|
+
value,
|
|
422
|
+
descending,
|
|
423
|
+
distance,
|
|
424
|
+
comparator,
|
|
425
|
+
}: {
|
|
426
|
+
target: number[];
|
|
427
|
+
value: number;
|
|
428
|
+
descending: boolean;
|
|
429
|
+
distance: string;
|
|
430
|
+
comparator: string;
|
|
431
|
+
}) {
|
|
432
|
+
let limit = 0; // zero is ignored, only used if set below
|
|
433
|
+
switch (comparator) {
|
|
434
|
+
case 'lt':
|
|
435
|
+
case 'le':
|
|
436
|
+
limit = value;
|
|
437
|
+
// fallthrough
|
|
438
|
+
case 'sort':
|
|
439
|
+
break;
|
|
440
|
+
default:
|
|
441
|
+
throw new ClientError(`Can not use "${comparator}" comparator with HNSW`);
|
|
442
|
+
}
|
|
443
|
+
if (descending) throw new ClientError(`Can not use descending sort order with HNSW`);
|
|
444
|
+
let distanceFunction: (a: number[], b: number[]) => number;
|
|
445
|
+
if (distance === 'cosine') distanceFunction = cosineDistance;
|
|
446
|
+
else if (distance === 'euclidean') distanceFunction = euclideanDistance;
|
|
447
|
+
else if (distance) throw new ClientError('Unknown distance function');
|
|
448
|
+
else distanceFunction = this.distance;
|
|
449
|
+
if (!target) throw new ClientError('A target vector must be provided for an HNSW query');
|
|
450
|
+
if (!Array.isArray(target)) throw new ClientError('The target vector must be an array');
|
|
451
|
+
|
|
452
|
+
let entryPoint = this.getEntryPoint();
|
|
453
|
+
if (!entryPoint) return [];
|
|
454
|
+
let entryPointId = entryPoint.id;
|
|
455
|
+
let results: Candidate[] = [];
|
|
456
|
+
// For each level from top to bottom
|
|
457
|
+
for (let l = entryPoint.level; l >= 0; l--) {
|
|
458
|
+
// Search for closest neighbors at current level
|
|
459
|
+
results = this.searchLayer(target, entryPointId, entryPoint, this.efConstructionSearch, l, distanceFunction);
|
|
460
|
+
|
|
461
|
+
if (results.length > 0) {
|
|
462
|
+
const neighbor = results[0]; // closest neighbor becomes new entry point
|
|
463
|
+
entryPoint = neighbor.node;
|
|
464
|
+
entryPointId = neighbor.id;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (limit) results = results.filter((candidate) => candidate.distance < limit);
|
|
468
|
+
return results.map((candidate) => ({
|
|
469
|
+
// we return the result as an entry so we can provide distance as metadata
|
|
470
|
+
key: candidate.node.primaryKey, // return value
|
|
471
|
+
distance: candidate.distance,
|
|
472
|
+
}));
|
|
473
|
+
}
|
|
474
|
+
private checkSymmetry(id, node, options) {
|
|
475
|
+
if (!node) return;
|
|
476
|
+
let l = 0;
|
|
477
|
+
let connections: Candidate[];
|
|
478
|
+
while ((connections = node[l])) {
|
|
479
|
+
// verify that the level is not empty, otherwise this means we have an orphaned node
|
|
480
|
+
if (connections.length === 0) break;
|
|
481
|
+
for (const { id: neighbor } of connections) {
|
|
482
|
+
const neighborNode = this.indexStore.getSync(neighbor, options);
|
|
483
|
+
if (!neighborNode) {
|
|
484
|
+
logger.info?.('could not find neighbor node', neighborNode);
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
// verify that the connection is symmetrical
|
|
488
|
+
const symmetrical = neighborNode[l]?.find(({ id: nid }) => nid == id);
|
|
489
|
+
if (!symmetrical) {
|
|
490
|
+
logger.info?.('asymmetry detected', neighborNode[l]);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
l++;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
private addConnection(
|
|
497
|
+
fromId: number,
|
|
498
|
+
node: any,
|
|
499
|
+
toId: number,
|
|
500
|
+
level: number,
|
|
501
|
+
distance: number,
|
|
502
|
+
updateNode: (id: number, node?: Node) => any,
|
|
503
|
+
options: any
|
|
504
|
+
) {
|
|
505
|
+
if (!node[level]) {
|
|
506
|
+
node[level] = [];
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
let maxConnections = level === 0 ? this.M << 1 : this.M;
|
|
510
|
+
if (this.optimizeRouting) maxConnections <<= 2; // bump up the max connections beyond traditional HNSW because we are naturally limiting
|
|
511
|
+
// have we exceeded the max connections (with 25% grace period)
|
|
512
|
+
if (node[level].length >= maxConnections + (maxConnections >> 2)) {
|
|
513
|
+
logger.debug?.('maxConnections reached, removing some connections', maxConnections);
|
|
514
|
+
// Get all connections with their similarities
|
|
515
|
+
|
|
516
|
+
// Sort by distance but prioritize nodes that have reverse connections
|
|
517
|
+
const connections = [...node[level]];
|
|
518
|
+
connections.sort((a, b) => {
|
|
519
|
+
return a.distance - b.distance;
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
// Keep the best connections
|
|
523
|
+
const keptConnections = connections.slice(0, maxConnections);
|
|
524
|
+
const removedConnections = connections.slice(maxConnections);
|
|
525
|
+
|
|
526
|
+
// Update this node's connections
|
|
527
|
+
node[level] = keptConnections;
|
|
528
|
+
// For removed connections, ensure there's still a path to them
|
|
529
|
+
for (const removed of removedConnections) {
|
|
530
|
+
let removedNode = updateNode(removed.id) ?? this.indexStore.getSync(removed.id, options);
|
|
531
|
+
if (removedNode) {
|
|
532
|
+
// Remove the reverse connection if it exists
|
|
533
|
+
if (removedNode[level]) {
|
|
534
|
+
removedNode = updateNode(removed.id, removedNode);
|
|
535
|
+
removedNode[level] = removedNode[level].filter(({ id }) => id !== fromId);
|
|
536
|
+
if (level === 0 && removedNode[level].length === 0) {
|
|
537
|
+
logger.info?.('should not remove last connection', fromId, toId);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if (node[level].find(({ id }) => id === toId)) {
|
|
544
|
+
logger.debug?.('already connected', fromId, toId);
|
|
545
|
+
} else {
|
|
546
|
+
node[level] = [...node[level], { id: toId, distance }]; // add
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
//this.indexStore.put(fromId, node, options);
|
|
550
|
+
//this.checkSymmetry(fromId, node, options);
|
|
551
|
+
}
|
|
552
|
+
validateConnectivity(startLevel: number = 0) {
|
|
553
|
+
const entryPoint = this.getEntryPoint();
|
|
554
|
+
const visited = new Set<number>();
|
|
555
|
+
|
|
556
|
+
// BFS from entry point to ensure all nodes are reachable
|
|
557
|
+
const queue = [entryPoint.id];
|
|
558
|
+
visited.add(entryPoint.id);
|
|
559
|
+
let connections = 0;
|
|
560
|
+
|
|
561
|
+
while (queue.length > 0) {
|
|
562
|
+
const currentId = queue.shift()!;
|
|
563
|
+
const current = this.indexStore.getSync(currentId);
|
|
564
|
+
|
|
565
|
+
for (let level = startLevel; level <= current.level; level++) {
|
|
566
|
+
for (const { id: neighborId } of current[level] || []) {
|
|
567
|
+
connections++;
|
|
568
|
+
if (!visited.has(neighborId)) {
|
|
569
|
+
visited.add(neighborId);
|
|
570
|
+
queue.push(neighborId);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Check if all nodes are reachable
|
|
577
|
+
// This would require maintaining a separate set/count of all nodes
|
|
578
|
+
if (visited.size !== this.totalNodes) {
|
|
579
|
+
console.log('visited', visited.size, 'total', this.totalNodes);
|
|
580
|
+
}
|
|
581
|
+
return {
|
|
582
|
+
isFullyConnected: visited.size === this.totalNodes,
|
|
583
|
+
averageConnections: connections / visited.size,
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
get totalNodes() {
|
|
587
|
+
return Array.from(this.indexStore.getKeys({ start: 0, end: Infinity })).length;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* This is used by the query planner to determine what order to apply conditions. It is our best guess at an estimated count.
|
|
592
|
+
* This unit is typically the number of records that need to be accessed to satisfy the query. We know that we will visit
|
|
593
|
+
* a minimum of efConstructionSearch nodes and a maximum of the total nodes (in absolute worst case).
|
|
594
|
+
* The original paper described the complexity as polylogarithmic. From my testing, the
|
|
595
|
+
* best and simplest guess at the number of nodes that need to be accessed is the geometric mean of the total number of nodes
|
|
596
|
+
* and the efConstruction parameter (for search), which clearly constrains the estimate to the correct range and is
|
|
597
|
+
* similar to polylogarithmic for realistic values.
|
|
598
|
+
*
|
|
599
|
+
* @returns
|
|
600
|
+
*/
|
|
601
|
+
estimateCountAsSort() {
|
|
602
|
+
return Math.sqrt(this.indexStore.getStats().entryCount * this.efConstructionSearch);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* This is used to resolve the vector property, which should be resolved to the distance when used in a sort comparator
|
|
607
|
+
* We also want to cache distance calculations so they can be accessed efficently later
|
|
608
|
+
* @param vector
|
|
609
|
+
* @param context
|
|
610
|
+
* @param entry
|
|
611
|
+
*/
|
|
612
|
+
propertyResolver(vector: number[], context: any, entry: any) {
|
|
613
|
+
const sortDefinition = context?.sort;
|
|
614
|
+
if (sortDefinition) {
|
|
615
|
+
// set up a cache for these so they can be accessed by $distance and not be recalculated during a sort
|
|
616
|
+
let vectorDistances = sortDefinition.vectorDistances;
|
|
617
|
+
if (vectorDistances) {
|
|
618
|
+
const difference = vectorDistances.get(entry);
|
|
619
|
+
if (difference) return difference;
|
|
620
|
+
} else vectorDistances = context.vectorDistances = sortDefinition.vectorDistances = new Map();
|
|
621
|
+
|
|
622
|
+
let distanceFunction = this.distance;
|
|
623
|
+
if (sortDefinition.type)
|
|
624
|
+
distanceFunction = sortDefinition.distance === 'euclidean' ? euclideanDistance : cosineDistance;
|
|
625
|
+
const distance = distanceFunction(sortDefinition.target, vector);
|
|
626
|
+
vectorDistances.set(entry, distance);
|
|
627
|
+
return distance;
|
|
628
|
+
}
|
|
629
|
+
return vector;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
type WithCopied = Connection[] & { copied: boolean };
|
|
633
|
+
type Candidate = {
|
|
634
|
+
id: number;
|
|
635
|
+
distance: number;
|
|
636
|
+
node: Node;
|
|
637
|
+
};
|
|
638
|
+
type SearchResults = Candidate[] & { visited: number };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export function euclideanDistance(a: number[], b: number[]): number {
|
|
2
|
+
// Euclidean distance
|
|
3
|
+
if (!Array.isArray(a) || !Array.isArray(b)) {
|
|
4
|
+
throw new Error('Euclidean distance comparison requires an array');
|
|
5
|
+
}
|
|
6
|
+
let distanceSquared = 0;
|
|
7
|
+
const length = Math.max(a.length, b.length);
|
|
8
|
+
for (let i = 0; i < length; i++) {
|
|
9
|
+
const va = a[i] || 0;
|
|
10
|
+
const vb = b[i] || 0;
|
|
11
|
+
const distance = va - vb;
|
|
12
|
+
distanceSquared += distance * distance;
|
|
13
|
+
}
|
|
14
|
+
return distanceSquared; // technically distance is the square root, but skipping that doesn't change the order
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function cosineDistance(a: number[], b: number[]): number {
|
|
18
|
+
// Cosine similarity, negated so it can be a "distance" function
|
|
19
|
+
if (!Array.isArray(a) || !Array.isArray(b)) {
|
|
20
|
+
throw new Error('Cosine distance comparison requires an array');
|
|
21
|
+
}
|
|
22
|
+
let dotProduct = 0;
|
|
23
|
+
let magnitudeA = 0;
|
|
24
|
+
let magnitudeB = 0;
|
|
25
|
+
const length = Math.max(a.length, b.length);
|
|
26
|
+
for (let i = 0; i < length; i++) {
|
|
27
|
+
const va = a[i] || 0;
|
|
28
|
+
const vb = b[i] || 0;
|
|
29
|
+
dotProduct += va * vb;
|
|
30
|
+
magnitudeA += va * va;
|
|
31
|
+
magnitudeB += vb * vb;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
magnitudeA = Math.sqrt(magnitudeA);
|
|
35
|
+
magnitudeB = Math.sqrt(magnitudeB);
|
|
36
|
+
|
|
37
|
+
return 1 - dotProduct / (magnitudeA * magnitudeB || 1);
|
|
38
|
+
}
|