@harperfast/harper 5.3.0-beta.3 → 5.3.0-beta.4
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 +25 -3
- package/bin/run.ts +18 -0
- package/components/Application.ts +52 -78
- package/components/DESIGN.md +76 -3
- package/components/Scope.ts +10 -4
- package/components/anthropic/index.ts +10 -1
- package/components/bedrock/index.ts +10 -1
- package/components/componentLoader.ts +23 -14
- package/components/mcp/tools/operations.ts +3 -1
- package/components/ollama/index.ts +10 -1
- package/components/openai/index.ts +265 -18
- package/components/operations.js +40 -1
- package/config/configUtils.ts +3 -19
- package/dataLayer/DESIGN.md +29 -5
- package/dataLayer/harperBridge/ResourceBridge.ts +39 -3
- package/dataLayer/restoreMarker.ts +250 -14
- package/dataLayer/schema.ts +2 -7
- package/dataLayer/schemaDescribe.ts +10 -0
- package/dist/bin/cliOperations.js +28 -3
- package/dist/bin/cliOperations.js.map +1 -1
- package/dist/bin/run.js +19 -0
- package/dist/bin/run.js.map +1 -1
- package/dist/components/Application.d.ts +8 -2
- package/dist/components/Application.js +43 -69
- package/dist/components/Application.js.map +1 -1
- package/dist/components/Scope.d.ts +1 -1
- package/dist/components/Scope.js +9 -4
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/anthropic/index.d.ts +0 -10
- package/dist/components/anthropic/index.js +9 -1
- package/dist/components/anthropic/index.js.map +1 -1
- package/dist/components/bedrock/index.d.ts +0 -12
- package/dist/components/bedrock/index.js +9 -1
- package/dist/components/bedrock/index.js.map +1 -1
- package/dist/components/componentLoader.js +23 -14
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/mcp/tools/operations.js +1 -1
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/ollama/index.d.ts +0 -13
- package/dist/components/ollama/index.js +9 -1
- package/dist/components/ollama/index.js.map +1 -1
- package/dist/components/openai/index.d.ts +11 -15
- package/dist/components/openai/index.js +224 -12
- package/dist/components/openai/index.js.map +1 -1
- package/dist/components/operations.d.ts +9 -0
- package/dist/components/operations.js +32 -2
- package/dist/components/operations.js.map +1 -1
- package/dist/config/configUtils.d.ts +1 -2
- package/dist/config/configUtils.js +7 -22
- package/dist/config/configUtils.js.map +1 -1
- package/dist/dataLayer/harperBridge/ResourceBridge.js +29 -2
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/dataLayer/restoreMarker.d.ts +25 -3
- package/dist/dataLayer/restoreMarker.js +213 -13
- package/dist/dataLayer/restoreMarker.js.map +1 -1
- package/dist/dataLayer/schema.js +1 -4
- package/dist/dataLayer/schema.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +10 -0
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +28 -0
- package/dist/resources/DatabaseTransaction.d.ts +21 -1
- package/dist/resources/DatabaseTransaction.js +147 -8
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.js +1 -1
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
- package/dist/resources/Table.d.ts +31 -3
- package/dist/resources/Table.js +397 -79
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/auditStore.js +16 -10
- package/dist/resources/auditStore.js.map +1 -1
- package/dist/resources/blob.d.ts +1 -0
- package/dist/resources/blob.js +14 -5
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/branchDatabase.js +3 -3
- package/dist/resources/branchDatabase.js.map +1 -1
- package/dist/resources/databaseDropPreparation.d.ts +20 -0
- package/dist/resources/databaseDropPreparation.js +106 -0
- package/dist/resources/databaseDropPreparation.js.map +1 -0
- package/dist/resources/databases.d.ts +26 -21
- package/dist/resources/databases.js +1040 -204
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/derivedIndexRegistry.d.ts +23 -0
- package/dist/resources/derivedIndexRegistry.js +92 -0
- package/dist/resources/derivedIndexRegistry.js.map +1 -1
- package/dist/resources/derivedIndexRuntime.d.ts +10 -0
- package/dist/resources/derivedIndexRuntime.js +136 -25
- package/dist/resources/derivedIndexRuntime.js.map +1 -1
- package/dist/resources/derivedIndexes.d.ts +39 -0
- package/dist/resources/derivedIndexes.js +733 -0
- package/dist/resources/derivedIndexes.js.map +1 -0
- package/dist/resources/fullTextSchema.d.ts +8 -0
- package/dist/resources/fullTextSchema.js +42 -0
- package/dist/resources/fullTextSchema.js.map +1 -1
- package/dist/resources/fullTextSchemaLifecycle.d.ts +0 -5
- package/dist/resources/fullTextSchemaLifecycle.js +0 -9
- package/dist/resources/fullTextSchemaLifecycle.js.map +1 -1
- package/dist/resources/graphql.js +101 -9
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/hnswDerivedIndex.d.ts +0 -10
- package/dist/resources/indexes/hnswDerivedIndex.js +0 -230
- package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -1
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.d.ts +3 -0
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js +57 -8
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js.map +1 -1
- package/dist/resources/models/Models.d.ts +26 -3
- package/dist/resources/models/Models.js +256 -13
- package/dist/resources/models/Models.js.map +1 -1
- package/dist/resources/models/TestBackend.d.ts +3 -1
- package/dist/resources/models/TestBackend.js +35 -1
- package/dist/resources/models/TestBackend.js.map +1 -1
- package/dist/resources/models/analyticsTable.d.ts +2 -2
- package/dist/resources/models/analyticsTable.js +4 -2
- package/dist/resources/models/analyticsTable.js.map +1 -1
- package/dist/resources/models/backendHelpers.d.ts +24 -0
- package/dist/resources/models/backendHelpers.js +18 -1
- package/dist/resources/models/backendHelpers.js.map +1 -1
- package/dist/resources/models/backendRegistry.d.ts +10 -21
- package/dist/resources/models/backendRegistry.js +74 -18
- package/dist/resources/models/backendRegistry.js.map +1 -1
- package/dist/resources/models/bootstrap.d.ts +7 -0
- package/dist/resources/models/bootstrap.js +36 -4
- package/dist/resources/models/bootstrap.js.map +1 -1
- package/dist/resources/models/decideHook.d.ts +33 -0
- package/dist/resources/models/decideHook.js +105 -0
- package/dist/resources/models/decideHook.js.map +1 -0
- package/dist/resources/models/decision.d.ts +64 -0
- package/dist/resources/models/decision.js +507 -0
- package/dist/resources/models/decision.js.map +1 -0
- package/dist/resources/models/decisionStore.d.ts +134 -0
- package/dist/resources/models/decisionStore.js +350 -0
- package/dist/resources/models/decisionStore.js.map +1 -0
- package/dist/resources/models/embedHook.d.ts +66 -8
- package/dist/resources/models/embedHook.js +228 -61
- package/dist/resources/models/embedHook.js.map +1 -1
- package/dist/resources/models/generativeDecision.d.ts +58 -0
- package/dist/resources/models/generativeDecision.js +315 -0
- package/dist/resources/models/generativeDecision.js.map +1 -0
- package/dist/resources/models/types.d.ts +232 -4
- package/dist/security/authn/oidc/tokenExchange.js +3 -33
- package/dist/security/authn/oidc/tokenExchange.js.map +1 -1
- package/dist/security/authn/oidc/tokenUseTable.d.ts +9 -0
- package/dist/security/authn/oidc/tokenUseTable.js +28 -0
- package/dist/security/authn/oidc/tokenUseTable.js.map +1 -0
- package/dist/security/certificateVerification/certificateVerificationSource.d.ts +0 -1
- package/dist/security/certificateVerification/certificateVerificationSource.js +3 -3
- package/dist/security/certificateVerification/certificateVerificationSource.js.map +1 -1
- package/dist/security/certificateVerification/crlVerification.js +77 -165
- package/dist/security/certificateVerification/crlVerification.js.map +1 -1
- package/dist/security/certificateVerification/types.d.ts +0 -3
- package/dist/security/certificateVerification/verificationTables.d.ts +19 -0
- package/dist/security/certificateVerification/verificationTables.js +125 -0
- package/dist/security/certificateVerification/verificationTables.js.map +1 -0
- package/dist/security/certificateVerification/verificationUtils.d.ts +1 -1
- package/dist/security/certificateVerification/verificationUtils.js +6 -31
- package/dist/security/certificateVerification/verificationUtils.js.map +1 -1
- package/dist/security/jsLoader.d.ts +5 -0
- package/dist/security/jsLoader.js +8 -0
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/server/Server.d.ts +1 -0
- package/dist/server/Server.js.map +1 -1
- package/dist/server/http.js +2 -2
- package/dist/server/http.js.map +1 -1
- package/dist/server/itc/serverHandlers.js +21 -2
- package/dist/server/itc/serverHandlers.js.map +1 -1
- package/dist/server/jobs/jobOwnership.d.ts +25 -0
- package/dist/server/jobs/jobOwnership.js +127 -0
- package/dist/server/jobs/jobOwnership.js.map +1 -0
- package/dist/server/jobs/jobProcess.js +9 -2
- package/dist/server/jobs/jobProcess.js.map +1 -1
- package/dist/server/jobs/jobs.js +18 -5
- package/dist/server/jobs/jobs.js.map +1 -1
- package/dist/server/loadRootComponents.js +10 -0
- package/dist/server/loadRootComponents.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.js +11 -1
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/serverHandlers.js +12 -2
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/threads/itc.d.ts +4 -1
- package/dist/server/threads/itc.js +29 -6
- package/dist/server/threads/itc.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +12 -4
- package/dist/server/threads/manageThreads.js +241 -119
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/threadServer.d.ts +1 -0
- package/dist/server/threads/threadServer.js +15 -1
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/server/threads/windowsProcessTree.d.ts +91 -0
- package/dist/server/threads/windowsProcessTree.js +361 -0
- package/dist/server/threads/windowsProcessTree.js.map +1 -0
- package/dist/server/throttle.d.ts +1 -1
- package/dist/server/throttle.js +8 -1
- package/dist/server/throttle.js.map +1 -1
- package/dist/upgrade/directives/5-3-0.js +53 -46
- package/dist/upgrade/directives/5-3-0.js.map +1 -1
- package/dist/utility/errors/hdbError.d.ts +10 -1
- package/dist/utility/errors/hdbError.js +55 -11
- package/dist/utility/errors/hdbError.js.map +1 -1
- package/dist/utility/fsync.d.ts +2 -0
- package/dist/utility/fsync.js +20 -0
- package/dist/utility/fsync.js.map +1 -0
- package/dist/utility/globalSchema.d.ts +18 -0
- 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/signalling.d.ts +7 -1
- package/dist/utility/signalling.js +27 -3
- package/dist/utility/signalling.js.map +1 -1
- package/dist/utility/when.d.ts +1 -0
- package/dist/utility/when.js +21 -0
- package/dist/utility/when.js.map +1 -1
- package/dist/validation/configValidator.js +30 -3
- package/dist/validation/configValidator.js.map +1 -1
- package/index.ts +13 -0
- package/json/systemSchema.json +28 -0
- package/npm-shrinkwrap.json +151 -55
- package/package.json +14 -13
- package/resources/DESIGN.md +80 -100
- package/resources/DatabaseTransaction.ts +157 -10
- package/resources/PrimaryRocksDatabase.ts +2 -2
- package/resources/Table.ts +494 -101
- package/resources/auditStore.ts +18 -10
- package/resources/blob.ts +13 -6
- package/resources/branchDatabase.ts +3 -3
- package/resources/databaseDropPreparation.ts +120 -0
- package/resources/databases.ts +1155 -205
- package/resources/derivedIndexRegistry.ts +122 -0
- package/resources/derivedIndexRuntime.ts +157 -30
- package/resources/derivedIndexes.ts +856 -0
- package/resources/fullTextSchema.ts +55 -0
- package/resources/fullTextSchemaLifecycle.ts +0 -15
- package/resources/graphql.ts +107 -12
- package/resources/indexes/DESIGN.md +7 -0
- package/resources/indexes/hnswDerivedIndex.ts +0 -260
- package/resources/indexes/nativeFullTextDerivedIndexLifecycle.ts +72 -9
- package/resources/models/DESIGN.md +39 -0
- package/resources/models/Models.ts +361 -18
- package/resources/models/TestBackend.ts +56 -1
- package/resources/models/analyticsTable.ts +6 -4
- package/resources/models/backendHelpers.ts +17 -0
- package/resources/models/backendRegistry.ts +76 -21
- package/resources/models/bootstrap.ts +57 -7
- package/resources/models/decideHook.ts +135 -0
- package/resources/models/decision.ts +505 -0
- package/resources/models/decisionStore.ts +419 -0
- package/resources/models/embedHook.ts +272 -55
- package/resources/models/generativeDecision.ts +459 -0
- package/resources/models/types.ts +231 -4
- package/security/DESIGN.md +22 -2
- package/security/authn/oidc/tokenExchange.ts +6 -34
- package/security/authn/oidc/tokenUseTable.ts +27 -0
- package/security/certificateVerification/certificateVerificationSource.ts +2 -3
- package/security/certificateVerification/crlVerification.ts +96 -174
- package/security/certificateVerification/types.ts +0 -3
- package/security/certificateVerification/verificationTables.ts +124 -0
- package/security/certificateVerification/verificationUtils.ts +8 -32
- package/security/jsLoader.ts +9 -1
- package/server/DESIGN.md +27 -1
- package/server/Server.ts +2 -1
- package/server/http.ts +4 -2
- package/server/itc/serverHandlers.js +29 -1
- package/server/jobs/jobOwnership.ts +89 -0
- package/server/jobs/jobProcess.ts +8 -2
- package/server/jobs/jobs.ts +15 -4
- package/server/loadRootComponents.js +11 -0
- package/server/serverHelpers/contentTypes.ts +13 -1
- package/server/serverHelpers/serverHandlers.js +11 -2
- package/server/threads/itc.js +32 -6
- package/server/threads/manageThreads.js +268 -122
- package/server/threads/threadServer.js +13 -1
- package/server/threads/windowsProcessTree.ts +409 -0
- package/server/throttle.ts +11 -1
- package/studio/web/assets/{Chat-CveGbxe6.js → Chat-Bq60AO-g.js} +1 -1
- package/studio/web/assets/{FloatingChat-IpCFgYWL.js → FloatingChat-D4560T0p.js} +3 -3
- package/studio/web/assets/{apiToken-CS8fFTgL.js → apiToken-BEvbFPMc.js} +1 -1
- package/studio/web/assets/{applications-D7F4Jopl.js → applications-CSUVTH4E.js} +1 -1
- package/studio/web/assets/{cssMode-Daw_oLPq.js → cssMode-DJyc9mHj.js} +1 -1
- package/studio/web/assets/{editor-B3Bs1zWX.js → editor-DDIsQfj6.js} +1 -1
- package/studio/web/assets/{html-CDaQwWiE.js → html-g1BJHxLy.js} +1 -1
- package/studio/web/assets/{htmlMode-Cb6emO4U.js → htmlMode-Vh5IglCQ.js} +1 -1
- package/studio/web/assets/{index-B4g2Fu_2.js → index-DGRAzvO4.js} +5 -5
- package/studio/web/assets/{index.lazy-NwhvF1q1.js → index.lazy-vgSic7et.js} +1 -1
- package/studio/web/assets/{javascript-2_86gB9Q.js → javascript-5VIolqf7.js} +1 -1
- package/studio/web/assets/{jsonMode-DX-i86uQ.js → jsonMode-DG4zQraI.js} +1 -1
- package/studio/web/assets/{languageServices-B85WN-Ej.js → languageServices-t5FBpX-i.js} +1 -1
- package/studio/web/assets/{lspLanguageFeatures-JCWHIsKg.js → lspLanguageFeatures-DgVnNPh-.js} +1 -1
- package/studio/web/assets/{notifications-D9ejzt52.js → notifications-BeWcX04m.js} +1 -1
- package/studio/web/assets/{notifications-B7hjKhai.js → notifications-DUGWrWoO.js} +1 -1
- package/studio/web/assets/{profile-D8B6No3F.js → profile-BvUEDBzW.js} +1 -1
- package/studio/web/assets/{regions-DFazIfTK.js → regions-CB-_2m7M.js} +1 -1
- package/studio/web/assets/{register-CwFLviLF.js → register-1w1Zr3b2.js} +2 -2
- package/studio/web/assets/{setComponentFile-VnJ80SSJ.js → setComponentFile-z4-qJCbA.js} +1 -1
- package/studio/web/assets/{setup-BYQxFy-C.js → setup-CS50uJ4x.js} +2 -2
- package/studio/web/assets/{status-Cn5XjmsL.js → status-CI0wOdOE.js} +1 -1
- package/studio/web/assets/{toggleHighContrast-g9xIh6TX.js → toggleHighContrast-BsAE4jPe.js} +1 -1
- package/studio/web/assets/{tsMode-EC-n0Wqc.js → tsMode-D83Gg3cq.js} +1 -1
- package/studio/web/assets/{typescript-DHmM9JCO.js → typescript-Dfp-GrNq.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-BUdnVSVv.js → useEntityRestURL-BKetlINf.js} +1 -1
- package/studio/web/assets/{workers-BVl9bBB-.js → workers-CCvmDLH3.js} +1 -1
- package/studio/web/assets/{xml-C4_sqOkX.js → xml-06pZp00U.js} +1 -1
- package/studio/web/assets/{yaml-CVkbDqtW.js → yaml-iSnWMVlV.js} +1 -1
- package/studio/web/index.html +1 -1
- package/upgrade/directives/5-3-0.ts +61 -54
- package/utility/DESIGN.md +5 -1
- package/utility/errors/hdbError.ts +56 -8
- package/utility/fsync.ts +16 -0
- package/utility/hdbTerms.ts +3 -0
- package/utility/signalling.ts +33 -4
- package/utility/when.ts +22 -0
- package/validation/configValidator.ts +36 -7
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* a table registers for an `@embed` attribute; `buildEmbedBefore` produces the
|
|
4
|
-
* pre-commit callback that runs registered embedders and writes their vectors onto
|
|
5
|
-
* the record before it commits.
|
|
6
|
-
*/
|
|
1
|
+
import { ClientError } from '../../utility/errors/hdbError.ts';
|
|
2
|
+
import { validateDecisionSchema } from './decision.ts';
|
|
7
3
|
|
|
8
4
|
// Lazily resolved to avoid a require cycle on the unit-test load path; only needed on failure.
|
|
9
5
|
function getLogger(): { error?: (...args: any[]) => void } {
|
|
@@ -25,12 +21,17 @@ export type EmbedAttribute = {
|
|
|
25
21
|
embed: EmbedConfig;
|
|
26
22
|
};
|
|
27
23
|
|
|
28
|
-
|
|
24
|
+
/** The signal aborts when a sibling hook of the same write fails; a hook that honors it lets the write fail promptly. */
|
|
25
|
+
export type WriteHookContext = { signal: AbortSignal };
|
|
26
|
+
|
|
27
|
+
export type Embedder = (record: any, hook?: WriteHookContext) => Promise<number[] | Float32Array | null | undefined>;
|
|
28
|
+
|
|
29
|
+
/** A pre-commit callback of one write; `signal` is the write's, aborted when another hook of the write fails. */
|
|
30
|
+
export type WriteHook = (signal?: AbortSignal) => Promise<void>;
|
|
29
31
|
|
|
30
|
-
// Matches the public `Models.embed` signature; a named type so tests can inject a fake.
|
|
31
32
|
type EmbedFn = (
|
|
32
33
|
input: string | string[],
|
|
33
|
-
opts: { model?: string; inputType?: 'document' | 'query' }
|
|
34
|
+
opts: { model?: string; inputType?: 'document' | 'query'; signal?: AbortSignal }
|
|
34
35
|
) => Promise<Float32Array[]>;
|
|
35
36
|
|
|
36
37
|
// Lazy-imported so this module can be unit-tested without loading the transaction
|
|
@@ -49,14 +50,259 @@ export function __setEmbedFnForTest(fn: EmbedFn | undefined): void {
|
|
|
49
50
|
_embedFn = fn;
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Whether a write-time model hook runs for this write at all. Skip when the write already
|
|
55
|
+
* carries the derived attributes: a cluster-replication receiver (isNotification), REST
|
|
56
|
+
* x-replicate-from:none, or audit-log replay. Proactive source-subscribe pushes also set
|
|
57
|
+
* isNotification and so skip. One predicate for `@embed` and `@decide` so their trigger rules
|
|
58
|
+
* cannot drift.
|
|
59
|
+
*/
|
|
60
|
+
export function writeHookApplies(record: any, context: any, options: any): boolean {
|
|
61
|
+
if (options?.isNotification === true || context?.replicateFrom === false || context?.alreadyLogged === true)
|
|
62
|
+
return false;
|
|
63
|
+
return Boolean(record) && typeof record === 'object';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* How a hook treats one source field of the write payload: absent leaves the derived
|
|
68
|
+
* attributes alone (a PATCH that omits the source), null clears them, a CRDT op payload
|
|
69
|
+
* (`{__op__, value}`) is not a meaningful input and is skipped, and a value is derived from.
|
|
70
|
+
*/
|
|
71
|
+
export type SourceState = 'absent' | 'null' | 'op' | 'value';
|
|
72
|
+
export function sourceState(record: any, sourceKey: string | undefined): SourceState {
|
|
73
|
+
if (!sourceKey || !(sourceKey in record)) return 'absent';
|
|
74
|
+
const value = record[sourceKey];
|
|
75
|
+
if (value == null) return 'null';
|
|
76
|
+
if (typeof value === 'object' && (value as any).__op__) return 'op';
|
|
77
|
+
return 'value';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Run the jobs of one write. The first failure aborts the others, and every job settles before
|
|
82
|
+
* that failure is reported: a rejected write neither waits on a sibling that honors the signal
|
|
83
|
+
* nor keeps being mutated by one that has not finished. A job that ignores the signal is
|
|
84
|
+
* awaited regardless, so the model calls behind the built-in hooks forward it.
|
|
85
|
+
*/
|
|
86
|
+
export async function runWriteJobs(
|
|
87
|
+
jobs: Array<(signal: AbortSignal) => Promise<void>>,
|
|
88
|
+
signal?: AbortSignal
|
|
89
|
+
): Promise<void> {
|
|
90
|
+
const controller = new AbortController();
|
|
91
|
+
const onAbort = () => controller.abort(signal?.reason);
|
|
92
|
+
if (signal?.aborted) onAbort();
|
|
93
|
+
else signal?.addEventListener('abort', onAbort, { once: true });
|
|
94
|
+
let failed = false;
|
|
95
|
+
let failure: unknown;
|
|
96
|
+
try {
|
|
97
|
+
await Promise.all(
|
|
98
|
+
jobs.map((job) =>
|
|
99
|
+
job(controller.signal).catch((err) => {
|
|
100
|
+
if (!failed) {
|
|
101
|
+
failed = true;
|
|
102
|
+
failure = err;
|
|
103
|
+
}
|
|
104
|
+
controller.abort(err);
|
|
105
|
+
})
|
|
106
|
+
)
|
|
107
|
+
);
|
|
108
|
+
} finally {
|
|
109
|
+
signal?.removeEventListener('abort', onAbort);
|
|
110
|
+
}
|
|
111
|
+
if (failed) throw failure;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function combineWriteHooks(a: WriteHook | undefined, b: WriteHook | undefined): WriteHook | undefined {
|
|
115
|
+
if (!a) return b;
|
|
116
|
+
if (!b) return a;
|
|
117
|
+
return (signal) => runWriteJobs([a, b], signal);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const SAFE_ERROR_NAME = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Backend errors can carry URLs / key tails; log raw, return a sanitized error to throw. Only
|
|
124
|
+
* safe identifiers are included (an identifier-shaped error class name, a finite upstream HTTP
|
|
125
|
+
* status) so the failure is diagnosable without hunting through server logs (#1593).
|
|
126
|
+
* `upstreamStatus` is the provider's status — NOT ServerError's statusCode, which is Harper's
|
|
127
|
+
* own response status and would misleadingly read 500 here.
|
|
128
|
+
*/
|
|
129
|
+
export function sanitizedHookError(
|
|
130
|
+
actor: string,
|
|
131
|
+
product: string,
|
|
132
|
+
attributeName: string,
|
|
133
|
+
err: unknown,
|
|
134
|
+
log = true
|
|
135
|
+
): Error {
|
|
136
|
+
if (log) getLogger().error?.(`${actor} for attribute "${attributeName}" failed:`, err);
|
|
137
|
+
const status = (err as any)?.upstreamStatus;
|
|
138
|
+
const errName = (err as any)?.name;
|
|
139
|
+
const detail =
|
|
140
|
+
(typeof errName === 'string' && errName !== 'Error' && SAFE_ERROR_NAME.test(errName) ? ` [${errName}]` : '') +
|
|
141
|
+
(Number.isInteger(status) && status >= 100 && status <= 599 ? ` (backend HTTP ${status})` : '');
|
|
142
|
+
const error = new Error(
|
|
143
|
+
`Failed to compute ${product} for attribute "${attributeName}"${detail} — see server log for details`
|
|
144
|
+
);
|
|
145
|
+
if (errName === 'AbortError') error.name = 'AbortError';
|
|
146
|
+
return error;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
type DerivedAttribute = {
|
|
150
|
+
name: string;
|
|
151
|
+
type?: string;
|
|
152
|
+
nullable?: boolean;
|
|
153
|
+
isPrimaryKey?: boolean;
|
|
154
|
+
computed?: unknown;
|
|
155
|
+
embed?: { source: string };
|
|
156
|
+
decide?: { source: string; confidence?: string; decision?: string; schema?: unknown };
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const INT32_MIN = -2147483648;
|
|
160
|
+
const INT32_MAX = 2147483647;
|
|
161
|
+
|
|
162
|
+
// Table validation holds a String to strings and an Int to 32 bits, so a leaf outside either would
|
|
163
|
+
// load and then fail every write.
|
|
164
|
+
const LEAF_KIND_BY_TYPE: Record<string, (schema: any) => boolean> = {
|
|
165
|
+
String: (schema) => Array.isArray(schema?.enum) && schema.enum.every((value: unknown) => typeof value === 'string'),
|
|
166
|
+
Boolean: (schema) => schema?.type === 'boolean' && !Array.isArray(schema.enum),
|
|
167
|
+
Int: (schema) =>
|
|
168
|
+
schema?.type === 'integer' &&
|
|
169
|
+
!Array.isArray(schema.enum) &&
|
|
170
|
+
schema.minimum >= INT32_MIN &&
|
|
171
|
+
schema.maximum <= INT32_MAX,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Every derived field (an `@embed` target, a `@decide` target or confidence) has exactly one
|
|
176
|
+
* writer, and no directive derives from another directive's output: the hooks run concurrently
|
|
177
|
+
* within one write, so the stored pair would otherwise depend on scheduling. A source and a
|
|
178
|
+
* confidence field must be declared; a decided field and its confidence field are nullable,
|
|
179
|
+
* neither the primary key nor computed, the decided field's type matches its leaf and the leaf
|
|
180
|
+
* is a valid decision schema; and none of those fields may be named after an `Object.prototype`
|
|
181
|
+
* key, because `sourceState` tests presence with `in`. Run on the complete attribute list.
|
|
182
|
+
*/
|
|
183
|
+
export function assertDerivedFieldOwnership(attributes: DerivedAttribute[]): void {
|
|
184
|
+
const declared = new Map<string, DerivedAttribute>();
|
|
185
|
+
for (const attribute of attributes) if (attribute) declared.set(attribute.name, attribute);
|
|
186
|
+
const writers = new Map<string, string>();
|
|
187
|
+
const claim = (field: string, writer: string) => {
|
|
188
|
+
const prior = writers.get(field);
|
|
189
|
+
if (prior) throw new ClientError(`${writer} and ${prior} both write "${field}"`, 400);
|
|
190
|
+
writers.set(field, writer);
|
|
191
|
+
};
|
|
192
|
+
const directives = (
|
|
193
|
+
attribute: DerivedAttribute
|
|
194
|
+
): Array<[string, { source: string; confidence?: string; decision?: string; schema?: unknown }]> => {
|
|
195
|
+
const out: Array<[string, { source: string; confidence?: string; decision?: string; schema?: unknown }]> = [];
|
|
196
|
+
if (attribute.embed) out.push(['@embed', attribute.embed]);
|
|
197
|
+
if (attribute.decide) out.push(['@decide', attribute.decide]);
|
|
198
|
+
return out;
|
|
199
|
+
};
|
|
200
|
+
for (const attribute of attributes) {
|
|
201
|
+
if (!attribute) continue;
|
|
202
|
+
for (const [directive, config] of directives(attribute)) {
|
|
203
|
+
const writer = `${directive} on "${attribute.name}"`;
|
|
204
|
+
for (const [name, field] of [
|
|
205
|
+
['confidence', config.confidence],
|
|
206
|
+
['decision', config.decision],
|
|
207
|
+
] as const)
|
|
208
|
+
if (field !== undefined && (typeof field !== 'string' || field === ''))
|
|
209
|
+
throw new ClientError(`${writer}: "${name}" must name a field`, 400);
|
|
210
|
+
for (const field of [attribute.name, config.source, config.confidence, config.decision])
|
|
211
|
+
if (field && Object.hasOwn(Object.prototype, field))
|
|
212
|
+
throw new ClientError(
|
|
213
|
+
`${writer}: "${field}" is an Object.prototype key and cannot be a derived, source, confidence or decision field`,
|
|
214
|
+
400
|
|
215
|
+
);
|
|
216
|
+
if (!declared.has(config.source))
|
|
217
|
+
throw new ClientError(`${writer} references unknown source field "${config.source}"`, 400);
|
|
218
|
+
if (directive === '@decide') {
|
|
219
|
+
if (attribute.isPrimaryKey || attribute.computed)
|
|
220
|
+
throw new ClientError(`${writer} cannot combine with @primaryKey or @computed`, 400);
|
|
221
|
+
if (attribute.nullable === false)
|
|
222
|
+
throw new ClientError(`${writer} cannot be declared non-null: a null source clears the attribute`, 400);
|
|
223
|
+
try {
|
|
224
|
+
validateDecisionSchema(config.schema);
|
|
225
|
+
} catch (err) {
|
|
226
|
+
throw new ClientError(`${writer}: ${(err as Error).message}`, 400);
|
|
227
|
+
}
|
|
228
|
+
if (attribute.type !== undefined) {
|
|
229
|
+
const matches = LEAF_KIND_BY_TYPE[attribute.type];
|
|
230
|
+
if (!matches)
|
|
231
|
+
throw new ClientError(
|
|
232
|
+
`${writer} requires a String, Boolean or Int attribute type; got "${attribute.type === 'array' ? '[...]' : attribute.type}"`,
|
|
233
|
+
400
|
|
234
|
+
);
|
|
235
|
+
if (!matches(config.schema))
|
|
236
|
+
throw new ClientError(`${writer}: the decision schema does not fit a ${attribute.type} attribute`, 400);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
claim(attribute.name, writer);
|
|
240
|
+
if (config.confidence) {
|
|
241
|
+
const confidence = declared.get(config.confidence);
|
|
242
|
+
if (!confidence)
|
|
243
|
+
throw new ClientError(`${writer} references unknown confidence field "${config.confidence}"`, 400);
|
|
244
|
+
if (confidence.type !== undefined && confidence.type !== 'Float')
|
|
245
|
+
throw new ClientError(
|
|
246
|
+
`${writer} requires a Float confidence attribute; "${config.confidence}" is ${confidence.type === 'array' ? '[...]' : confidence.type}`,
|
|
247
|
+
400
|
|
248
|
+
);
|
|
249
|
+
if (confidence.nullable === false)
|
|
250
|
+
throw new ClientError(
|
|
251
|
+
`${writer}: confidence attribute "${config.confidence}" cannot be declared non-null: a null source clears it`,
|
|
252
|
+
400
|
|
253
|
+
);
|
|
254
|
+
if (confidence.isPrimaryKey || confidence.computed)
|
|
255
|
+
throw new ClientError(
|
|
256
|
+
`${writer}: confidence attribute "${config.confidence}" cannot be @primaryKey or @computed`,
|
|
257
|
+
400
|
|
258
|
+
);
|
|
259
|
+
claim(config.confidence, writer);
|
|
260
|
+
}
|
|
261
|
+
if (config.decision) {
|
|
262
|
+
const decisionField = declared.get(config.decision);
|
|
263
|
+
if (!decisionField)
|
|
264
|
+
throw new ClientError(`${writer} references unknown decision field "${config.decision}"`, 400);
|
|
265
|
+
if (decisionField.type !== undefined && decisionField.type !== 'String')
|
|
266
|
+
throw new ClientError(
|
|
267
|
+
`${writer} requires a String decision attribute; "${config.decision}" is ${decisionField.type === 'array' ? '[...]' : decisionField.type}`,
|
|
268
|
+
400
|
|
269
|
+
);
|
|
270
|
+
if (decisionField.nullable === false)
|
|
271
|
+
throw new ClientError(
|
|
272
|
+
`${writer}: decision attribute "${config.decision}" cannot be declared non-null: a null source clears it`,
|
|
273
|
+
400
|
|
274
|
+
);
|
|
275
|
+
if (decisionField.isPrimaryKey || decisionField.computed)
|
|
276
|
+
throw new ClientError(
|
|
277
|
+
`${writer}: decision attribute "${config.decision}" cannot be @primaryKey or @computed`,
|
|
278
|
+
400
|
|
279
|
+
);
|
|
280
|
+
claim(config.decision, writer);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
for (const attribute of attributes) {
|
|
285
|
+
if (!attribute) continue;
|
|
286
|
+
for (const [directive, config] of directives(attribute)) {
|
|
287
|
+
const sourceWriter = writers.get(config.source);
|
|
288
|
+
if (sourceWriter)
|
|
289
|
+
throw new ClientError(
|
|
290
|
+
`${directive} on "${attribute.name}" derives from "${config.source}", which ${sourceWriter} writes`,
|
|
291
|
+
400
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
52
297
|
export function createDefaultEmbedder(embedConfig: EmbedConfig): Embedder {
|
|
53
298
|
const { source, model } = embedConfig;
|
|
54
|
-
return async (record: any): Promise<number[] | null | undefined> => {
|
|
299
|
+
return async (record: any, hook?: WriteHookContext): Promise<number[] | null | undefined> => {
|
|
55
300
|
const sourceValue = record?.[source];
|
|
56
301
|
if (sourceValue == null) return null;
|
|
57
302
|
const vectors = await resolveEmbedFn()(String(sourceValue), {
|
|
58
303
|
model,
|
|
59
304
|
inputType: 'document',
|
|
305
|
+
signal: hook?.signal,
|
|
60
306
|
});
|
|
61
307
|
const v = vectors?.[0];
|
|
62
308
|
if (v == null) return undefined;
|
|
@@ -80,63 +326,34 @@ export function buildEmbedBefore(
|
|
|
80
326
|
options: any,
|
|
81
327
|
embedAttributes: EmbedAttribute[] | undefined,
|
|
82
328
|
userEmbedders: Record<string, Embedder>
|
|
83
|
-
):
|
|
329
|
+
): WriteHook | undefined {
|
|
84
330
|
if (!embedAttributes || embedAttributes.length === 0) return undefined;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (sourceKey && sourceKey in record) {
|
|
96
|
-
anySourcePresent = true;
|
|
97
|
-
break;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
if (!anySourcePresent) return undefined;
|
|
101
|
-
return async (): Promise<void> => {
|
|
102
|
-
// Parallel: each embedder mutates a distinct attribute, so there's no ordering hazard.
|
|
103
|
-
await Promise.all(
|
|
104
|
-
embedAttributes.map(async (attr) => {
|
|
105
|
-
const sourceKey = attr.embed?.source;
|
|
106
|
-
if (!sourceKey) return;
|
|
107
|
-
if (!(sourceKey in record)) return;
|
|
108
|
-
const sourceValue = record[sourceKey];
|
|
109
|
-
if (sourceValue == null) {
|
|
331
|
+
if (!writeHookApplies(record, context, options)) return undefined;
|
|
332
|
+
let present = false;
|
|
333
|
+
for (const attr of embedAttributes) if (sourceState(record, attr.embed?.source) !== 'absent') present = true;
|
|
334
|
+
if (!present) return undefined;
|
|
335
|
+
return (signal) =>
|
|
336
|
+
runWriteJobs(
|
|
337
|
+
embedAttributes.map((attr) => async (jobSignal) => {
|
|
338
|
+
const state = sourceState(record, attr.embed?.source);
|
|
339
|
+
if (state === 'absent' || state === 'op') return;
|
|
340
|
+
if (state === 'null') {
|
|
110
341
|
record[attr.name] = null;
|
|
111
342
|
return;
|
|
112
343
|
}
|
|
113
|
-
// CRDT op payloads (`{__op__, value}`) aren't a meaningful embed source; skip.
|
|
114
|
-
if (sourceValue && typeof sourceValue === 'object' && (sourceValue as any).__op__) return;
|
|
115
344
|
const embedder = userEmbedders[attr.name];
|
|
116
345
|
if (!embedder) return;
|
|
117
346
|
let vector;
|
|
118
347
|
try {
|
|
119
|
-
vector = await embedder(record);
|
|
348
|
+
vector = await embedder(record, { signal: jobSignal });
|
|
120
349
|
} catch (err) {
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
// diagnosable without hunting through server logs (#1593). upstreamStatus is the
|
|
124
|
-
// provider's status — NOT ServerError's statusCode, which is Harper's own response
|
|
125
|
-
// status and would misleadingly read 500 here.
|
|
126
|
-
getLogger().error?.(`Embedder for attribute "${attr.name}" failed:`, err);
|
|
127
|
-
const status = (err as any)?.upstreamStatus;
|
|
128
|
-
const errName = (err as any)?.name;
|
|
129
|
-
const detail =
|
|
130
|
-
(errName && errName !== 'Error' ? ` [${errName}]` : '') +
|
|
131
|
-
(typeof status === 'number' ? ` (backend HTTP ${status})` : '');
|
|
132
|
-
throw new Error(
|
|
133
|
-
`Failed to compute embedding for attribute "${attr.name}"${detail} — see server log for details`
|
|
134
|
-
);
|
|
350
|
+
// An aborted job failed because a sibling did; only that failure is worth a log line.
|
|
351
|
+
throw sanitizedHookError('Embedder', 'embedding', attr.name, err, !jobSignal.aborted);
|
|
135
352
|
}
|
|
136
353
|
record[attr.name] = normalizeVector(vector);
|
|
137
|
-
})
|
|
354
|
+
}),
|
|
355
|
+
signal
|
|
138
356
|
);
|
|
139
|
-
};
|
|
140
357
|
}
|
|
141
358
|
|
|
142
359
|
// Custom embedders may return any typed array; flatten to a plain array so it round-trips
|