@harperfast/harper 5.3.0-alpha.1 → 5.3.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.
- package/bin/cliOperations.ts +12 -0
- package/bin/restart.ts +66 -6
- package/components/Application.ts +1134 -112
- package/components/OptionsWatcher.ts +368 -102
- package/components/Scope.ts +20 -1
- package/components/componentLoader.ts +43 -4
- package/components/deploymentOperations.ts +4 -1
- package/components/deploymentRecorder.ts +9 -2
- package/components/operations.js +284 -52
- package/components/operationsValidation.js +49 -2
- package/components/packageComponent.ts +25 -1
- package/components/requestRestart.ts +11 -0
- package/config/RootConfigWatcher.ts +191 -37
- package/config/configReadRetry.ts +62 -0
- package/config/configUtils.ts +78 -26
- package/config/parseConfigFile.ts +34 -0
- package/config/readConfigFileSync.ts +44 -0
- package/config/watcherArming.ts +59 -0
- package/config-root.schema.json +4 -0
- package/dataLayer/harperBridge/ResourceBridge.ts +28 -2
- package/dist/bin/cliOperations.js +13 -0
- package/dist/bin/cliOperations.js.map +1 -1
- package/dist/bin/restart.js +42 -6
- package/dist/bin/restart.js.map +1 -1
- package/dist/components/Application.d.ts +104 -9
- package/dist/components/Application.js +954 -102
- package/dist/components/Application.js.map +1 -1
- package/dist/components/OptionsWatcher.d.ts +4 -1
- package/dist/components/OptionsWatcher.js +378 -104
- package/dist/components/OptionsWatcher.js.map +1 -1
- package/dist/components/Scope.js +15 -1
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/componentLoader.js +35 -3
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/deploymentOperations.js +4 -1
- package/dist/components/deploymentOperations.js.map +1 -1
- package/dist/components/deploymentRecorder.d.ts +4 -2
- package/dist/components/deploymentRecorder.js +1 -0
- package/dist/components/deploymentRecorder.js.map +1 -1
- package/dist/components/operations.d.ts +28 -0
- package/dist/components/operations.js +263 -54
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.js +48 -2
- package/dist/components/operationsValidation.js.map +1 -1
- package/dist/components/packageComponent.js +24 -0
- package/dist/components/packageComponent.js.map +1 -1
- package/dist/components/requestRestart.d.ts +1 -0
- package/dist/components/requestRestart.js +7 -0
- package/dist/components/requestRestart.js.map +1 -1
- package/dist/config/RootConfigWatcher.d.ts +2 -0
- package/dist/config/RootConfigWatcher.js +189 -35
- package/dist/config/RootConfigWatcher.js.map +1 -1
- package/dist/config/configReadRetry.d.ts +8 -0
- package/dist/config/configReadRetry.js +62 -0
- package/dist/config/configReadRetry.js.map +1 -0
- package/dist/config/configUtils.d.ts +10 -9
- package/dist/config/configUtils.js +63 -27
- package/dist/config/configUtils.js.map +1 -1
- package/dist/config/parseConfigFile.d.ts +4 -0
- package/dist/config/parseConfigFile.js +35 -0
- package/dist/config/parseConfigFile.js.map +1 -0
- package/dist/config/readConfigFileSync.d.ts +1 -0
- package/dist/config/readConfigFileSync.js +47 -0
- package/dist/config/readConfigFileSync.js.map +1 -0
- package/dist/config/watcherArming.d.ts +15 -0
- package/dist/config/watcherArming.js +59 -0
- package/dist/config/watcherArming.js.map +1 -0
- package/dist/dataLayer/harperBridge/ResourceBridge.js +21 -2
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +3 -0
- package/dist/resources/DatabaseTransaction.d.ts +25 -0
- package/dist/resources/DatabaseTransaction.js +224 -18
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/LMDBTransaction.d.ts +2 -1
- package/dist/resources/LMDBTransaction.js +22 -3
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.js +22 -6
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
- package/dist/resources/RecordEncoder.d.ts +1 -1
- package/dist/resources/RecordEncoder.js +21 -5
- package/dist/resources/RecordEncoder.js.map +1 -1
- package/dist/resources/Resource.js +97 -13
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +8 -0
- package/dist/resources/RocksIndexStore.js +2 -1
- package/dist/resources/RocksIndexStore.js.map +1 -1
- package/dist/resources/RocksTransactionLogStore.d.ts +10 -0
- package/dist/resources/RocksTransactionLogStore.js +104 -33
- package/dist/resources/RocksTransactionLogStore.js.map +1 -1
- package/dist/resources/Table.d.ts +58 -7
- package/dist/resources/Table.js +1095 -346
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/write.js +10 -3
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/resources/auditStore.d.ts +170 -0
- package/dist/resources/auditStore.js +457 -11
- package/dist/resources/auditStore.js.map +1 -1
- package/dist/resources/dataLoader.js +3 -4
- package/dist/resources/dataLoader.js.map +1 -1
- package/dist/resources/databases.d.ts +16 -13
- package/dist/resources/databases.js +624 -176
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/derivedIndexRegistry.d.ts +5 -0
- package/dist/resources/derivedIndexRegistry.js +68 -0
- package/dist/resources/derivedIndexRegistry.js.map +1 -0
- package/dist/resources/derivedIndexRuntime.d.ts +215 -0
- package/dist/resources/derivedIndexRuntime.js +2027 -0
- package/dist/resources/derivedIndexRuntime.js.map +1 -0
- package/dist/resources/graphql.js +3 -2
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +102 -9
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +848 -39
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/indexes/hnswDerivedIndex.d.ts +67 -0
- package/dist/resources/indexes/hnswDerivedIndex.js +464 -0
- package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -0
- package/dist/resources/indexes/hnswPlaneBinding.d.ts +65 -0
- package/dist/resources/indexes/hnswPlaneBinding.js +91 -0
- package/dist/resources/indexes/hnswPlaneBinding.js.map +1 -0
- package/dist/resources/nodeIdMapping.d.ts +5 -0
- package/dist/resources/nodeIdMapping.js +49 -0
- package/dist/resources/nodeIdMapping.js.map +1 -1
- package/dist/resources/recordLock.d.ts +47 -4
- package/dist/resources/recordLock.js +138 -7
- package/dist/resources/recordLock.js.map +1 -1
- package/dist/resources/recordLockCoordinator.d.ts +557 -0
- package/dist/resources/recordLockCoordinator.js +2565 -0
- package/dist/resources/recordLockCoordinator.js.map +1 -0
- package/dist/resources/replayLogs.js +5 -0
- package/dist/resources/replayLogs.js.map +1 -1
- package/dist/resources/replicatedApplyFailure.d.ts +16 -0
- package/dist/resources/replicatedApplyFailure.js +63 -0
- package/dist/resources/replicatedApplyFailure.js.map +1 -0
- package/dist/resources/scheduler/scheduler.js +3 -3
- package/dist/resources/scheduler/scheduler.js.map +1 -1
- package/dist/resources/search.d.ts +10 -4
- package/dist/resources/search.js +160 -40
- package/dist/resources/search.js.map +1 -1
- package/dist/resources/tracked.d.ts +5 -1
- package/dist/resources/tracked.js +74 -23
- package/dist/resources/tracked.js.map +1 -1
- package/dist/security/jsLoader.js +6 -4
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/server/REST.js +33 -2
- package/dist/server/REST.js.map +1 -1
- package/dist/server/http.d.ts +5 -1
- package/dist/server/http.js +34 -2
- package/dist/server/http.js.map +1 -1
- package/dist/server/serverHelpers/Headers.d.ts +2 -0
- package/dist/server/serverHelpers/Headers.js +6 -0
- package/dist/server/serverHelpers/Headers.js.map +1 -1
- package/dist/server/serverHelpers/NodeAdapterResponse.d.ts +48 -0
- package/dist/server/serverHelpers/NodeAdapterResponse.js +220 -0
- package/dist/server/serverHelpers/NodeAdapterResponse.js.map +1 -0
- package/dist/server/serverHelpers/Request.d.ts +5 -10
- package/dist/server/serverHelpers/Request.js +38 -136
- package/dist/server/serverHelpers/Request.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.d.ts +2 -0
- package/dist/server/serverHelpers/contentTypes.js +189 -15
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.js +96 -17
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/storageReclamation.js +1 -1
- package/dist/server/storageReclamation.js.map +1 -1
- package/dist/server/threads/isolatedApplications.d.ts +47 -0
- package/dist/server/threads/isolatedApplications.js +171 -0
- package/dist/server/threads/isolatedApplications.js.map +1 -0
- package/dist/server/threads/logRotationTransport.d.ts +1 -0
- package/dist/server/threads/logRotationTransport.js +33 -0
- package/dist/server/threads/logRotationTransport.js.map +1 -0
- package/dist/server/threads/manageThreads.d.ts +64 -6
- package/dist/server/threads/manageThreads.js +261 -12
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/socketRouter.d.ts +1 -0
- package/dist/server/threads/socketRouter.js +196 -13
- package/dist/server/threads/socketRouter.js.map +1 -1
- package/dist/server/threads/threadServer.js +30 -7
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/utility/errors/hdbError.d.ts +24 -0
- package/dist/utility/errors/hdbError.js +58 -1
- package/dist/utility/errors/hdbError.js.map +1 -1
- package/dist/utility/hdbTerms.d.ts +2 -0
- package/dist/utility/hdbTerms.js +2 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/logging/harper_logger.js +217 -38
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/dist/utility/logging/logGenerationCoordinator.d.ts +35 -0
- package/dist/utility/logging/logGenerationCoordinator.js +184 -0
- package/dist/utility/logging/logGenerationCoordinator.js.map +1 -0
- package/dist/utility/logging/logRotation.d.ts +46 -0
- package/dist/utility/logging/logRotation.js +365 -0
- package/dist/utility/logging/logRotation.js.map +1 -0
- package/dist/utility/logging/logRotator.d.ts +1 -1
- package/dist/utility/logging/logRotator.js +172 -92
- package/dist/utility/logging/logRotator.js.map +1 -1
- package/dist/utility/npmUtilities.js +6 -4
- package/dist/utility/npmUtilities.js.map +1 -1
- package/dist/utility/watcherFallback.d.ts +0 -45
- package/dist/utility/watcherFallback.js +1 -125
- package/dist/utility/watcherFallback.js.map +1 -1
- package/dist/validation/configValidator.js +6 -3
- package/dist/validation/configValidator.js.map +1 -1
- package/index.ts +6 -0
- package/json/systemSchema.json +3 -0
- package/npm-shrinkwrap.json +131 -41
- package/package.json +10 -3
- package/resources/DESIGN.md +124 -19
- package/resources/DatabaseTransaction.ts +230 -17
- package/resources/LMDBTransaction.ts +21 -3
- package/resources/PrimaryRocksDatabase.ts +20 -7
- package/resources/RecordEncoder.ts +25 -5
- package/resources/Resource.ts +97 -13
- package/resources/ResourceInterface.ts +8 -0
- package/resources/RocksIndexStore.ts +2 -1
- package/resources/RocksTransactionLogStore.ts +111 -31
- package/resources/Table.ts +1224 -393
- package/resources/analytics/write.ts +10 -3
- package/resources/auditStore.ts +460 -11
- package/resources/dataLoader.ts +3 -4
- package/resources/databases.ts +610 -146
- package/resources/derivedIndexRegistry.ts +56 -0
- package/resources/derivedIndexRuntime.ts +2292 -0
- package/resources/graphql.ts +3 -2
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +905 -46
- package/resources/indexes/hnswDerivedIndex.ts +531 -0
- package/resources/indexes/hnswPlaneBinding.ts +174 -0
- package/resources/nodeIdMapping.ts +50 -0
- package/resources/recordLock.ts +173 -7
- package/resources/recordLockCoordinator.ts +3043 -0
- package/resources/replayLogs.ts +5 -0
- package/resources/replicatedApplyFailure.ts +77 -0
- package/resources/scheduler/scheduler.ts +4 -4
- package/resources/search.ts +169 -49
- package/resources/tracked.ts +73 -22
- package/security/jsLoader.ts +6 -4
- package/server/DESIGN.md +11 -0
- package/server/REST.ts +36 -3
- package/server/http.ts +34 -2
- package/server/serverHelpers/Headers.ts +5 -1
- package/server/serverHelpers/NodeAdapterResponse.ts +221 -0
- package/server/serverHelpers/Request.ts +33 -131
- package/server/serverHelpers/contentTypes.ts +188 -15
- package/server/serverHelpers/serverUtilities.ts +143 -24
- package/server/storageReclamation.ts +2 -2
- package/server/threads/isolatedApplications.ts +157 -0
- package/server/threads/logRotationTransport.ts +40 -0
- package/server/threads/manageThreads.js +254 -12
- package/server/threads/socketRouter.ts +217 -11
- package/server/threads/threadServer.js +30 -7
- package/studio/web/assets/{Chat-BnCBegQz.js → Chat-D3j-1yY1.js} +1 -1
- package/studio/web/assets/{FloatingChat-CoDW1ySS.js → FloatingChat-BxJGYcfB.js} +3 -3
- package/studio/web/assets/{apiToken-Bwk5BLXW.js → apiToken-CT55oWOe.js} +1 -1
- package/studio/web/assets/{applications-DHxGi7JH.js → applications-D9Ct9_vm.js} +1 -1
- package/studio/web/assets/{cssMode-s0cWI-_M.js → cssMode-DV8H7VwA.js} +1 -1
- package/studio/web/assets/{editor-DNcRHK54.js → editor-uatc0unt.js} +1 -1
- package/studio/web/assets/{html-Bdssedlg.js → html-Bm6D6paN.js} +1 -1
- package/studio/web/assets/{htmlMode-CoDlJ3fw.js → htmlMode-CEn7tpLG.js} +1 -1
- package/studio/web/assets/{index-D6sxmFLR.js → index-BIXW6Pu4.js} +5 -5
- package/studio/web/assets/{index.lazy-tmU5BS8s.js → index.lazy-UI7L-Vrk.js} +1 -1
- package/studio/web/assets/{javascript-B8meVSTH.js → javascript-CJ0G3AFZ.js} +1 -1
- package/studio/web/assets/{jsonMode-DpIPd35T.js → jsonMode-DQADAYEa.js} +1 -1
- package/studio/web/assets/{languageServices-C_5FMJzQ.js → languageServices-CAQJXWcI.js} +1 -1
- package/studio/web/assets/{lspLanguageFeatures-BIzNBzPK.js → lspLanguageFeatures-CCQ8P5sY.js} +1 -1
- package/studio/web/assets/{notifications-CQf18QKb.js → notifications-BbxTU6Aw.js} +1 -1
- package/studio/web/assets/{notifications-CvZivSbh.js → notifications-Cvb3P1lB.js} +1 -1
- package/studio/web/assets/{profile-DdOwtntb.js → profile-Yyb7gsvL.js} +1 -1
- package/studio/web/assets/{regions-n69fwagr.js → regions-OgjGHlU5.js} +1 -1
- package/studio/web/assets/{register-PfWTCXWB.js → register-6qwNEOY3.js} +2 -2
- package/studio/web/assets/{setComponentFile-Bg6O7X0S.js → setComponentFile-BilDMtgB.js} +1 -1
- package/studio/web/assets/{setup-CUx_aUDl.js → setup-J6qJ7OIU.js} +2 -2
- package/studio/web/assets/{status-D7BVKqX9.js → status-0RWGcfyD.js} +1 -1
- package/studio/web/assets/{toggleHighContrast-DBSyXzMr.js → toggleHighContrast-BIn-vErT.js} +1 -1
- package/studio/web/assets/{tsMode-BByKCjBS.js → tsMode-DgUXku4d.js} +1 -1
- package/studio/web/assets/{typescript-DDLnLpw9.js → typescript-C9orXcsM.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-31CHGaHk.js → useEntityRestURL-BEoXXbUB.js} +1 -1
- package/studio/web/assets/{workers-pR3jRY9D.js → workers-JVzSDmgx.js} +1 -1
- package/studio/web/assets/{xml-2iRnMhQO.js → xml-Cq-S8S4X.js} +1 -1
- package/studio/web/assets/{yaml-Bf92gJpd.js → yaml-sfoRdh1M.js} +1 -1
- package/studio/web/index.html +1 -1
- package/utility/errors/hdbError.ts +54 -0
- package/utility/hdbTerms.ts +2 -0
- package/utility/logging/harper_logger.ts +209 -30
- package/utility/logging/logGenerationCoordinator.ts +196 -0
- package/utility/logging/logRotation.ts +367 -0
- package/utility/logging/logRotator.ts +196 -91
- package/utility/npmUtilities.ts +6 -4
- package/utility/watcherFallback.ts +0 -122
- package/validation/configValidator.ts +6 -3
package/resources/Resource.ts
CHANGED
|
@@ -147,20 +147,55 @@ export class Resource<Record extends object = any> implements ResourceInterface<
|
|
|
147
147
|
static put = transactional(
|
|
148
148
|
function (resource: any, query: RequestTarget, request: Context, data: any) {
|
|
149
149
|
if (Array.isArray(data) && resource.#isCollection && resource.constructor.loadAsInstance !== false) {
|
|
150
|
+
const resourceClass = resource.constructor;
|
|
151
|
+
const primaryKey = resourceClass.primaryKey;
|
|
152
|
+
// Before dispatching anything: a malformed body must not race a sibling's write, and a bare
|
|
153
|
+
// dereference below would reach the client as a 500 carrying V8's wording. A primitive is
|
|
154
|
+
// malformed for the same reason — only a Table's own key validation rejects one later, so a
|
|
155
|
+
// plain Resource's override would otherwise be handed it.
|
|
156
|
+
for (let index = 0; index < data.length; index++) {
|
|
157
|
+
const element = data[index];
|
|
158
|
+
if (element == null || typeof element !== 'object')
|
|
159
|
+
throw new ClientError(
|
|
160
|
+
`Array element at index ${index} is ${element === null ? 'null' : typeof element}, expected a record`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
const elementTarget = elementTargetFactory(query);
|
|
150
164
|
const results = [];
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
165
|
+
try {
|
|
166
|
+
for (let index = 0; index < data.length; index++) {
|
|
167
|
+
const element = data[index];
|
|
168
|
+
const target = elementTarget(element[primaryKey]);
|
|
169
|
+
const elementResource = resourceClass.getResource(target, request, {
|
|
170
|
+
async: true,
|
|
171
|
+
});
|
|
172
|
+
// `target`, not `request`: this is the target slot, and Table's back-compat shift only
|
|
173
|
+
// recognizes a RequestTarget there — a context is taken for the record.
|
|
174
|
+
// `missingMethod` rather than an unguarded call, so a resource without `put` answers 405
|
|
175
|
+
// here exactly as it does on the single-record path below.
|
|
176
|
+
if (typeof elementResource.then === 'function')
|
|
177
|
+
results.push(
|
|
178
|
+
elementResource.then((resource) =>
|
|
179
|
+
resource.put ? resource.put(element, target) : missingMethod(resource, 'put')
|
|
180
|
+
)
|
|
181
|
+
);
|
|
182
|
+
else if (elementResource.put) results.push(elementResource.put(element, target));
|
|
183
|
+
else missingMethod(elementResource, 'put');
|
|
184
|
+
}
|
|
185
|
+
} catch (error) {
|
|
186
|
+
// Settle the already-dispatched elements or a rejection among them goes unhandled. Then
|
|
187
|
+
// report the earliest-index failure, the same rule `settleElements` applies: every sibling
|
|
188
|
+
// already dispatched precedes the element that threw, so the batch reports the same failure
|
|
189
|
+
// whether that element's dispatch resolved synchronously or asynchronously.
|
|
190
|
+
return Promise.allSettled(results).then((settled) => {
|
|
191
|
+
const failed = settled.find((sibling) => sibling.status === 'rejected');
|
|
192
|
+
if (!failed) throw error;
|
|
193
|
+
throw (failed as PromiseRejectedResult).reason;
|
|
158
194
|
});
|
|
159
|
-
if (typeof elementResource.then === 'function')
|
|
160
|
-
results.push(elementResource.then((resource) => resource.put(element, request)));
|
|
161
|
-
else results.push(elementResource.put(element, query));
|
|
162
195
|
}
|
|
163
|
-
|
|
196
|
+
// Not `Promise.all`: settling the batch while a slower element is still resolving lets that
|
|
197
|
+
// element commit outside the failed batch — a partially applied array PUT.
|
|
198
|
+
return settleElements(results);
|
|
164
199
|
}
|
|
165
200
|
return resource.put
|
|
166
201
|
? resource.constructor.loadAsInstance === false
|
|
@@ -571,6 +606,53 @@ export function snakeCase(camelCase: string) {
|
|
|
571
606
|
);
|
|
572
607
|
}
|
|
573
608
|
|
|
609
|
+
/**
|
|
610
|
+
* Mint per-element targets for a fanned-out batch write. Each element needs its own object — its id
|
|
611
|
+
* must not be visible to a sibling whose dispatch resolves later — but it also needs the request's
|
|
612
|
+
* query and route metadata, which a `put()` override can read. The request's contribution is
|
|
613
|
+
* therefore resolved once here rather than deep-cloned per element, and nested metadata is shared
|
|
614
|
+
* rather than copied. `checkPermission` never carries: the collection receiver's single verdict is
|
|
615
|
+
* authoritative and per-element dispatch must not re-arm it.
|
|
616
|
+
*/
|
|
617
|
+
function elementTargetFactory(query: any): (id: any) => RequestTarget {
|
|
618
|
+
const base = cloneRequestTarget(query);
|
|
619
|
+
const search = base.toString();
|
|
620
|
+
// Only what the element target would not otherwise have. `RequestTarget` declares `pathname`,
|
|
621
|
+
// `search` and `sort` without `declare`, so they are own properties on every instance and would
|
|
622
|
+
// otherwise always be "carried" — overwriting what this element's own construction just derived,
|
|
623
|
+
// and making the skip below dead. A nullish own value carries no metadata either.
|
|
624
|
+
const carried: Record<string, any> = {};
|
|
625
|
+
for (const key of Object.keys(base)) {
|
|
626
|
+
if (key === 'id' || key === 'isCollection' || key === 'pathname' || key === 'search') continue;
|
|
627
|
+
const value = (base as any)[key];
|
|
628
|
+
if (value != null) carried[key] = value;
|
|
629
|
+
}
|
|
630
|
+
const hasCarried = Object.keys(carried).length > 0;
|
|
631
|
+
return (id) => {
|
|
632
|
+
const target = search ? new RequestTarget(search) : new RequestTarget();
|
|
633
|
+
if (hasCarried) Object.assign(target, carried);
|
|
634
|
+
target.id = id;
|
|
635
|
+
target.isCollection = false;
|
|
636
|
+
return target;
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Report a fanned-out batch write's outcome only once every element has staged or failed its own
|
|
642
|
+
* write, so the enclosing transaction cannot unwind under one. Rejects with the first failure.
|
|
643
|
+
*/
|
|
644
|
+
function settleElements(results: any[]): Promise<any[]> {
|
|
645
|
+
return Promise.allSettled(results).then((settled) => {
|
|
646
|
+
const values = new Array(settled.length);
|
|
647
|
+
for (let index = 0; index < settled.length; index++) {
|
|
648
|
+
const element = settled[index];
|
|
649
|
+
if (element.status === 'rejected') throw element.reason;
|
|
650
|
+
values[index] = element.value;
|
|
651
|
+
}
|
|
652
|
+
return values;
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
|
|
574
656
|
/**
|
|
575
657
|
* This is responsible for arranging arguments in the main static methods and creating the appropriate context and default transaction wrapping
|
|
576
658
|
* @param action
|
|
@@ -733,8 +815,10 @@ function transactional(
|
|
|
733
815
|
if (!query) {
|
|
734
816
|
query = new RequestTarget();
|
|
735
817
|
query.id = id;
|
|
736
|
-
|
|
737
|
-
|
|
818
|
+
// The collection inferred from the batch's null id has to survive onto the synthesized target,
|
|
819
|
+
// or the array is dispatched as one record update against a null primary key. Array-only:
|
|
820
|
+
// a null id on non-array data means auto-generate, not collection.
|
|
821
|
+
if (isCollection && options.method === 'put' && Array.isArray(data)) query.isCollection = true;
|
|
738
822
|
}
|
|
739
823
|
if (options.method === 'query' && data && typeof data === 'object') {
|
|
740
824
|
// QUERY executes the independently parsed body target. Make its requested projection part of
|
|
@@ -201,6 +201,10 @@ interface TypedDirectCondition<Record extends object, Property extends keyof Rec
|
|
|
201
201
|
search_type?: Comparator;
|
|
202
202
|
value?: Record[Property] | Record[Property][];
|
|
203
203
|
search_value?: Record[Property] | Record[Property][];
|
|
204
|
+
/** Native HNSW coverage tolerance in milliseconds; defaults to 3000, with 0 requiring current coverage. */
|
|
205
|
+
maxIndexLagMilliseconds?: number;
|
|
206
|
+
/** Wait for coverage of prior committed writes, up to 30,000 ms; 0 (default) does not wait. */
|
|
207
|
+
waitForIndexMilliseconds?: number;
|
|
204
208
|
/**
|
|
205
209
|
* If true, the condition is negated. Phase 1: filter-only — forces a
|
|
206
210
|
* full scan unless paired with another indexed condition.
|
|
@@ -223,6 +227,10 @@ export type Conditions<Record extends object = any> = Condition<Record>[];
|
|
|
223
227
|
|
|
224
228
|
export interface Sort<Record extends object = any> {
|
|
225
229
|
attribute: keyof Record;
|
|
230
|
+
/** Native HNSW coverage tolerance in milliseconds; defaults to 3000, with 0 requiring current coverage. */
|
|
231
|
+
maxIndexLagMilliseconds?: number;
|
|
232
|
+
/** Wait for coverage of prior committed writes, up to 30,000 ms; 0 (default) does not wait. */
|
|
233
|
+
waitForIndexMilliseconds?: number;
|
|
226
234
|
descending?: boolean;
|
|
227
235
|
next?: Sort<Record>;
|
|
228
236
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { trackReadRange } from './DatabaseTransaction.ts';
|
|
1
2
|
import {
|
|
2
3
|
type CountEstimate,
|
|
3
4
|
type CountEstimateOptions,
|
|
@@ -48,7 +49,7 @@ export class RocksIndexStore extends RocksDatabase {
|
|
|
48
49
|
* @param options
|
|
49
50
|
*/
|
|
50
51
|
getRange(options: StoreIteratorOptions): Iterable<any> {
|
|
51
|
-
return super.getRange(translateIndexBounds(options)).map(({ key }) => {
|
|
52
|
+
return trackReadRange(options.transaction, () => super.getRange(translateIndexBounds(options))).map(({ key }) => {
|
|
52
53
|
return { key: key[0], value: key.length > 2 ? key.slice(1) : key[1] };
|
|
53
54
|
});
|
|
54
55
|
}
|
|
@@ -29,10 +29,16 @@ type TransactionLogIterator = Iterator<TransactionEntry | number> & {
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
type TrackedIterator = IterableIterator<TransactionEntry> & { lastVersion?: number; lastEndTxn?: boolean };
|
|
32
|
+
type NamedTransactionEntry = TransactionEntry & { logName?: string };
|
|
33
|
+
export type ExactStartFailure = 'missing' | 'incomplete' | 'duplicate';
|
|
32
34
|
|
|
33
35
|
export type TransactionLogIterable = Iterable<AuditRecord> & {
|
|
34
36
|
/** Corrupt frames that ended a log's iteration during this range. */
|
|
35
37
|
corruptFrameStop: CorruptFrameStop;
|
|
38
|
+
/** Physical logs whose iterators ended with an unexpected, non-corruption error. */
|
|
39
|
+
failedLogs: Set<string>;
|
|
40
|
+
/** Physical logs whose requested exact transaction could not form one unambiguous resume boundary. */
|
|
41
|
+
exactStartFailures: Map<string, ExactStartFailure>;
|
|
36
42
|
};
|
|
37
43
|
|
|
38
44
|
const reportCorruptFrame = createCorruptFrameReporter(harperLogger);
|
|
@@ -242,6 +248,7 @@ export class RocksTransactionLogStore extends EventEmitter {
|
|
|
242
248
|
getRange(options: {
|
|
243
249
|
start?: number;
|
|
244
250
|
exactStart?: boolean;
|
|
251
|
+
exclusiveStart?: boolean;
|
|
245
252
|
end?: number;
|
|
246
253
|
log?: string | number;
|
|
247
254
|
excludeLogs?: string[];
|
|
@@ -249,6 +256,10 @@ export class RocksTransactionLogStore extends EventEmitter {
|
|
|
249
256
|
startByLog?: Map<string, number>;
|
|
250
257
|
startFromLastFlushed?: boolean;
|
|
251
258
|
readUncommitted?: boolean;
|
|
259
|
+
/** Include the source transaction-log name on each returned audit record. */
|
|
260
|
+
includeLogName?: boolean;
|
|
261
|
+
/** Validate and consume one complete exact-start transaction per entry in `startByLog`. */
|
|
262
|
+
resumeAfterExactStart?: boolean;
|
|
252
263
|
/**
|
|
253
264
|
* Track which version a break truncated, in `corruptFrameStop.truncatedVersions`. Costs two
|
|
254
265
|
* property stores per yielded entry (see below), so it defaults off: only boot replay reads
|
|
@@ -262,6 +273,46 @@ export class RocksTransactionLogStore extends EventEmitter {
|
|
|
262
273
|
let singleLogIterator: TrackedIterator;
|
|
263
274
|
const attributeCorruption = options.trackCorruptTransactions === true;
|
|
264
275
|
const corruptFrameStop: CorruptFrameStop = { breaks: 0, truncatedVersions: new Set(), midLogBreak: false };
|
|
276
|
+
const failedLogs = new Set<string>();
|
|
277
|
+
const exactStartFailures = new Map<string, ExactStartFailure>();
|
|
278
|
+
const failedIterators = new WeakSet<IterableIterator<TransactionEntry>>();
|
|
279
|
+
const safeNext = (iterator: IterableIterator<TransactionEntry>, log: TransactionLog) => {
|
|
280
|
+
if (failedIterators.has(iterator)) return { value: undefined, done: true } as IteratorResult<TransactionEntry>;
|
|
281
|
+
try {
|
|
282
|
+
return iterator.next();
|
|
283
|
+
} catch (error) {
|
|
284
|
+
failedIterators.add(iterator);
|
|
285
|
+
failedLogs.add(log.name);
|
|
286
|
+
harperLogger.error('Transaction log iterator failed; terminating this log', error, {
|
|
287
|
+
log: log.name,
|
|
288
|
+
});
|
|
289
|
+
return { value: undefined, done: true } as IteratorResult<TransactionEntry>;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
const resumePastExactStart = (
|
|
293
|
+
result: IteratorResult<TransactionEntry>,
|
|
294
|
+
iterator: TrackedIterator,
|
|
295
|
+
log: TransactionLog,
|
|
296
|
+
expected: number
|
|
297
|
+
): IteratorResult<TransactionEntry> => {
|
|
298
|
+
if (result.done || result.value.timestamp !== expected) {
|
|
299
|
+
exactStartFailures.set(log.name, 'missing');
|
|
300
|
+
return { value: undefined, done: true };
|
|
301
|
+
}
|
|
302
|
+
while (!result.value.endTxn) {
|
|
303
|
+
result = safeNext(iterator, log);
|
|
304
|
+
if (result.done || result.value.timestamp !== expected) {
|
|
305
|
+
exactStartFailures.set(log.name, 'incomplete');
|
|
306
|
+
return { value: undefined, done: true };
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
result = safeNext(iterator, log);
|
|
310
|
+
if (!result.done && result.value.timestamp === expected) {
|
|
311
|
+
exactStartFailures.set(log.name, 'duplicate');
|
|
312
|
+
return { value: undefined, done: true };
|
|
313
|
+
}
|
|
314
|
+
return result;
|
|
315
|
+
};
|
|
265
316
|
// Each log's iterator carries the version and endTxn of the last entry it yielded, so a break
|
|
266
317
|
// can be attributed to the source transaction whose remaining entries it swallowed — unless
|
|
267
318
|
// that entry's own endTxn already closed the transaction, in which case the break fell after
|
|
@@ -299,9 +350,34 @@ export class RocksTransactionLogStore extends EventEmitter {
|
|
|
299
350
|
log = this.rootStore.useLog(options.log);
|
|
300
351
|
}
|
|
301
352
|
}
|
|
302
|
-
const
|
|
353
|
+
const resumeAfterExactStart =
|
|
354
|
+
options.resumeAfterExactStart === true && options.exactStart === true && options.start !== undefined;
|
|
355
|
+
const queryOptions = resumeAfterExactStart ? { ...options, exclusiveStart: false } : options;
|
|
356
|
+
const queryIterator = trackCorruptFrames(log, queryOptions);
|
|
303
357
|
singleLogIterator = queryIterator;
|
|
304
|
-
|
|
358
|
+
let exactStartObserved = !resumeAfterExactStart;
|
|
359
|
+
iterable.iterate =
|
|
360
|
+
options.includeLogName || resumeAfterExactStart
|
|
361
|
+
? () => ({
|
|
362
|
+
next: () => {
|
|
363
|
+
let result = safeNext(queryIterator, log);
|
|
364
|
+
if (!exactStartObserved) {
|
|
365
|
+
exactStartObserved = true;
|
|
366
|
+
if (resumeAfterExactStart) result = resumePastExactStart(result, queryIterator, log, options.start);
|
|
367
|
+
}
|
|
368
|
+
if (!result.done && options.includeLogName) (result.value as NamedTransactionEntry).logName = log.name;
|
|
369
|
+
return result;
|
|
370
|
+
},
|
|
371
|
+
[Symbol.iterator]() {
|
|
372
|
+
return this;
|
|
373
|
+
},
|
|
374
|
+
return: (value?: any) => queryIterator.return?.(value) ?? { value, done: true },
|
|
375
|
+
throw: (error?: any) => {
|
|
376
|
+
if (queryIterator.throw) return queryIterator.throw(error);
|
|
377
|
+
throw error;
|
|
378
|
+
},
|
|
379
|
+
})
|
|
380
|
+
: () => queryIterator;
|
|
305
381
|
} else {
|
|
306
382
|
const onlyKeys = options.onlyKeys;
|
|
307
383
|
let logs: TransactionLog[] = [];
|
|
@@ -309,30 +385,8 @@ export class RocksTransactionLogStore extends EventEmitter {
|
|
|
309
385
|
let nextEntries: any[];
|
|
310
386
|
let latestUpdates: number;
|
|
311
387
|
const iterators: TrackedIterator[] = [];
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
// updateIterators() never calls .next() on them again — otherwise every
|
|
315
|
-
// subsequent drain cycle would re-throw the same RangeError, spamming logs
|
|
316
|
-
// and burning CPU.
|
|
317
|
-
const failedIterators = new WeakSet<IterableIterator<TransactionEntry>>();
|
|
318
|
-
// Per-log advance that converts a thrown corrupt-entry error from rocksdb-js
|
|
319
|
-
// into a clean `done: true` for that iterator. The reader's RangeError leaves
|
|
320
|
-
// `position` at the bad entry; re-calling next() would re-throw indefinitely.
|
|
321
|
-
// Terminating just this log lets the aggregate keep draining the other peers'
|
|
322
|
-
// logs and prevents the throw from escaping into setImmediate-scheduled
|
|
323
|
-
// consumers (notifyFromTransactionData) where it becomes an uncaughtException.
|
|
324
|
-
const safeNext = (iterator: IterableIterator<TransactionEntry>, log?: TransactionLog) => {
|
|
325
|
-
if (failedIterators.has(iterator)) return { value: undefined, done: true };
|
|
326
|
-
try {
|
|
327
|
-
return iterator.next();
|
|
328
|
-
} catch (error) {
|
|
329
|
-
failedIterators.add(iterator);
|
|
330
|
-
harperLogger.error('Transaction log iterator failed; terminating this log', error, {
|
|
331
|
-
log: log?.name,
|
|
332
|
-
});
|
|
333
|
-
return { value: undefined, done: true };
|
|
334
|
-
}
|
|
335
|
-
};
|
|
388
|
+
const expectedExactStarts: Array<number | undefined> = [];
|
|
389
|
+
const observedExactStarts = new Set<string>();
|
|
336
390
|
const updateIterators = () => {
|
|
337
391
|
if (latestUpdates !== this.updates) {
|
|
338
392
|
const latestLogs = (this.nodeLogs || this.loadLogs()).filter(
|
|
@@ -342,9 +396,18 @@ export class RocksTransactionLogStore extends EventEmitter {
|
|
|
342
396
|
if (!logs.includes(log)) {
|
|
343
397
|
logs.push(log);
|
|
344
398
|
let queryOptions = options;
|
|
399
|
+
let expectedExactStart: number | undefined;
|
|
345
400
|
if (options.startByLog) {
|
|
346
|
-
|
|
347
|
-
|
|
401
|
+
if (options.startByLog.has(log.name)) {
|
|
402
|
+
expectedExactStart = options.startByLog.get(log.name)!;
|
|
403
|
+
queryOptions = {
|
|
404
|
+
...options,
|
|
405
|
+
start: expectedExactStart,
|
|
406
|
+
exclusiveStart: options.resumeAfterExactStart ? false : options.exclusiveStart,
|
|
407
|
+
};
|
|
408
|
+
} else {
|
|
409
|
+
queryOptions = { ...options, start: 0, exactStart: false, exclusiveStart: false };
|
|
410
|
+
}
|
|
348
411
|
} else if (latestUpdates >= 0) {
|
|
349
412
|
// if this is not the first update, that means that this is a brand new log and if start wasn't specified
|
|
350
413
|
// that means we are taking all future requests, so we need to start at zero so we don't introduce a race
|
|
@@ -352,6 +415,7 @@ export class RocksTransactionLogStore extends EventEmitter {
|
|
|
352
415
|
queryOptions = { ...options, start: options.start ?? 0 };
|
|
353
416
|
}
|
|
354
417
|
iterators.push(trackCorruptFrames(log, queryOptions));
|
|
418
|
+
expectedExactStarts.push(expectedExactStart);
|
|
355
419
|
}
|
|
356
420
|
}
|
|
357
421
|
latestUpdates = this.updates;
|
|
@@ -360,12 +424,22 @@ export class RocksTransactionLogStore extends EventEmitter {
|
|
|
360
424
|
let log = logs[i];
|
|
361
425
|
if (!latestLogs.includes(log)) {
|
|
362
426
|
logs.splice(i, 1);
|
|
363
|
-
iterators.splice(i
|
|
427
|
+
iterators.splice(i, 1);
|
|
428
|
+
expectedExactStarts.splice(i--, 1);
|
|
364
429
|
}
|
|
365
430
|
}
|
|
366
431
|
}
|
|
367
432
|
}
|
|
368
|
-
nextEntries = iterators.map((iterator, i) =>
|
|
433
|
+
nextEntries = iterators.map((iterator, i) => {
|
|
434
|
+
const result = safeNext(iterator, logs[i]);
|
|
435
|
+
const expected = expectedExactStarts[i];
|
|
436
|
+
if (expected !== undefined && !observedExactStarts.has(logs[i].name)) {
|
|
437
|
+
observedExactStarts.add(logs[i].name);
|
|
438
|
+
if (options.resumeAfterExactStart) return resumePastExactStart(result, iterator, logs[i], expected);
|
|
439
|
+
if (result.done || result.value.timestamp !== expected) exactStartFailures.set(logs[i].name, 'missing');
|
|
440
|
+
}
|
|
441
|
+
return result;
|
|
442
|
+
});
|
|
369
443
|
};
|
|
370
444
|
updateIterators();
|
|
371
445
|
|
|
@@ -408,6 +482,7 @@ export class RocksTransactionLogStore extends EventEmitter {
|
|
|
408
482
|
}
|
|
409
483
|
}
|
|
410
484
|
if (earliestIndex >= 0) {
|
|
485
|
+
if (options.includeLogName) (earliest as NamedTransactionEntry).logName = logs[earliestIndex].name;
|
|
411
486
|
if (attributeCorruption) {
|
|
412
487
|
// before the refill, which is where a break surfaces and needs this entry's version
|
|
413
488
|
iterators[earliestIndex].lastVersion = earliest.timestamp;
|
|
@@ -438,6 +513,7 @@ export class RocksTransactionLogStore extends EventEmitter {
|
|
|
438
513
|
if (index >= 0) {
|
|
439
514
|
logs.splice(index, 1);
|
|
440
515
|
iterators.splice(index, 1);
|
|
516
|
+
expectedExactStarts.splice(index, 1);
|
|
441
517
|
nextEntries.splice(index, 1);
|
|
442
518
|
options.excludeLogs.push(logName);
|
|
443
519
|
}
|
|
@@ -445,7 +521,7 @@ export class RocksTransactionLogStore extends EventEmitter {
|
|
|
445
521
|
};
|
|
446
522
|
iterable.iterate = () => aggregateIterator;
|
|
447
523
|
}
|
|
448
|
-
const mappedAggregateIterable = iterable.map(({ timestamp, data, endTxn }:
|
|
524
|
+
const mappedAggregateIterable = iterable.map(({ timestamp, data, endTxn, logName }: NamedTransactionEntry) => {
|
|
449
525
|
// A break surfaces on the pull after this entry, so recording it here is in time to attribute
|
|
450
526
|
// that break to this entry's transaction. The aggregate branch records its own, per source
|
|
451
527
|
// log, because there this callback cannot tell which log an entry came from.
|
|
@@ -478,6 +554,7 @@ export class RocksTransactionLogStore extends EventEmitter {
|
|
|
478
554
|
position += 8;
|
|
479
555
|
}
|
|
480
556
|
const auditRecord = readAuditEntry(data, position, undefined);
|
|
557
|
+
if (options.includeLogName) auditRecord.logName = logName;
|
|
481
558
|
auditRecord.txnLogKey = timestamp;
|
|
482
559
|
auditRecord.endTxn = endTxn;
|
|
483
560
|
auditRecord.previousResidencyId = previousResidencyId;
|
|
@@ -493,6 +570,7 @@ export class RocksTransactionLogStore extends EventEmitter {
|
|
|
493
570
|
// the log key is all this entry still yields; its record version is undecodable
|
|
494
571
|
version: timestamp,
|
|
495
572
|
txnLogKey: timestamp,
|
|
573
|
+
logName: options.includeLogName ? logName : undefined,
|
|
496
574
|
endTxn,
|
|
497
575
|
type: undefined,
|
|
498
576
|
tableId: undefined,
|
|
@@ -509,6 +587,8 @@ export class RocksTransactionLogStore extends EventEmitter {
|
|
|
509
587
|
mappedAggregateIterable.removeLog = aggregateIterator.removeLog;
|
|
510
588
|
}
|
|
511
589
|
mappedAggregateIterable.corruptFrameStop = corruptFrameStop;
|
|
590
|
+
mappedAggregateIterable.failedLogs = failedLogs;
|
|
591
|
+
mappedAggregateIterable.exactStartFailures = exactStartFailures;
|
|
512
592
|
return mappedAggregateIterable as TransactionLogIterable;
|
|
513
593
|
}
|
|
514
594
|
getKeys(_options?: any) {
|