@harperfast/harper 5.3.0-beta.1 → 5.3.0-beta.2
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/components/DESIGN.md +421 -0
- package/components/mcp/DESIGN.md +109 -0
- package/components/mcp/audit.ts +21 -17
- package/config/DESIGN.md +306 -0
- package/dataLayer/DESIGN.md +179 -0
- package/dataLayer/restoreMarker.ts +92 -25
- package/dist/components/mcp/audit.d.ts +2 -1
- package/dist/components/mcp/audit.js +21 -17
- package/dist/components/mcp/audit.js.map +1 -1
- package/dist/dataLayer/restoreMarker.d.ts +21 -8
- package/dist/dataLayer/restoreMarker.js +94 -27
- package/dist/dataLayer/restoreMarker.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/resources/RecordEncoder.js +25 -5
- package/dist/resources/RecordEncoder.js.map +1 -1
- package/dist/resources/Table.js +48 -2
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/write.d.ts +3 -0
- package/dist/resources/analytics/write.js +39 -13
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/resources/crdt.d.ts +10 -0
- package/dist/resources/crdt.js +22 -0
- package/dist/resources/crdt.js.map +1 -1
- package/dist/resources/databases.js +1 -1
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +2 -2
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +43 -24
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/indexes/fullTextDerivedIndex.d.ts +81 -0
- package/dist/resources/indexes/fullTextDerivedIndex.js +1004 -0
- package/dist/resources/indexes/fullTextDerivedIndex.js.map +1 -0
- package/dist/resources/indexes/fullTextNativeBinding.d.ts +78 -0
- package/dist/resources/indexes/fullTextNativeBinding.js +85 -0
- package/dist/resources/indexes/fullTextNativeBinding.js.map +1 -0
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.d.ts +24 -0
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js +149 -0
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js.map +1 -0
- package/dist/resources/recordLockCoordinator.d.ts +5 -5
- package/dist/resources/recordLockCoordinator.js +48 -16
- package/dist/resources/recordLockCoordinator.js.map +1 -1
- package/dist/resources/transactionBroadcast.js +4 -6
- package/dist/resources/transactionBroadcast.js.map +1 -1
- package/dist/security/auth.js +59 -23
- package/dist/security/auth.js.map +1 -1
- package/dist/security/deferredAuthentication.d.ts +11 -0
- package/dist/security/deferredAuthentication.js +25 -3
- package/dist/security/deferredAuthentication.js.map +1 -1
- package/dist/security/jsLoader.js +3 -0
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/server/REST.js +6 -3
- package/dist/server/REST.js.map +1 -1
- package/dist/server/mqtt.js +5 -1
- package/dist/server/mqtt.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.d.ts +3 -3
- package/dist/server/serverHelpers/serverUtilities.js +14 -3
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/serverHelpers/uwsServer.js +4 -1
- package/dist/server/serverHelpers/uwsServer.js.map +1 -1
- package/dist/server/serverHelpers/webSocketCloseReason.d.ts +2 -0
- package/dist/server/serverHelpers/webSocketCloseReason.js +29 -0
- package/dist/server/serverHelpers/webSocketCloseReason.js.map +1 -0
- package/dist/utility/logging/harper_logger.js +1 -0
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/index.ts +3 -0
- package/npm-shrinkwrap.json +104 -104
- package/package.json +6 -5
- package/resources/DESIGN.md +568 -3
- package/resources/RecordEncoder.ts +27 -5
- package/resources/Table.ts +49 -3
- package/resources/analytics/DESIGN.md +38 -0
- package/resources/analytics/write.ts +40 -14
- package/resources/crdt.ts +22 -0
- package/resources/databases.ts +1 -1
- package/resources/indexes/DESIGN.md +833 -0
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +44 -25
- package/resources/indexes/fullTextDerivedIndex.ts +1165 -0
- package/resources/indexes/fullTextNativeBinding.ts +146 -0
- package/resources/indexes/nativeFullTextDerivedIndexLifecycle.ts +181 -0
- package/resources/record-locks.md +1407 -0
- package/resources/recordLockCoordinator.ts +61 -22
- package/resources/scheduler/DESIGN.md +40 -0
- package/resources/transactionBroadcast.ts +4 -4
- package/security/DESIGN.md +175 -0
- package/security/auth.ts +53 -24
- package/security/deferredAuthentication.ts +24 -2
- package/security/jsLoader.ts +3 -0
- package/server/DESIGN.md +264 -0
- package/server/REST.ts +6 -3
- package/server/mqtt.ts +6 -4
- package/server/serverHelpers/serverUtilities.ts +14 -3
- package/server/serverHelpers/uwsServer.ts +4 -1
- package/server/serverHelpers/webSocketCloseReason.ts +25 -0
- package/studio/web/assets/{Chat-D3j-1yY1.js → Chat-DADFFGe_.js} +1 -1
- package/studio/web/assets/{FloatingChat-BxJGYcfB.js → FloatingChat-D_mI-rZ7.js} +3 -3
- package/studio/web/assets/{apiToken-CT55oWOe.js → apiToken-c2NiSDHa.js} +1 -1
- package/studio/web/assets/{applications-D9Ct9_vm.js → applications-DktUqh7G.js} +1 -1
- package/studio/web/assets/{cssMode-DV8H7VwA.js → cssMode-Cs_75Xhw.js} +1 -1
- package/studio/web/assets/{editor-uatc0unt.js → editor-19b-Y1IN.js} +1 -1
- package/studio/web/assets/{html-Bm6D6paN.js → html-DiYEQMpB.js} +1 -1
- package/studio/web/assets/{htmlMode-CEn7tpLG.js → htmlMode-CmR0y7P_.js} +1 -1
- package/studio/web/assets/{index-BIXW6Pu4.js → index-Dm0rfkJ7.js} +5 -5
- package/studio/web/assets/{index.lazy-UI7L-Vrk.js → index.lazy-7vqt2CC3.js} +1 -1
- package/studio/web/assets/{javascript-CJ0G3AFZ.js → javascript-BWtCFuOt.js} +1 -1
- package/studio/web/assets/{jsonMode-DQADAYEa.js → jsonMode-Buzzbv9y.js} +1 -1
- package/studio/web/assets/{languageServices-CAQJXWcI.js → languageServices-SqsFWfTM.js} +1 -1
- package/studio/web/assets/{lspLanguageFeatures-CCQ8P5sY.js → lspLanguageFeatures-EMV5cmjo.js} +1 -1
- package/studio/web/assets/{notifications-Cvb3P1lB.js → notifications-CAB-LZWT.js} +1 -1
- package/studio/web/assets/{notifications-BbxTU6Aw.js → notifications-DRzmSRxM.js} +1 -1
- package/studio/web/assets/{profile-Yyb7gsvL.js → profile-BNKAl79n.js} +1 -1
- package/studio/web/assets/{regions-OgjGHlU5.js → regions-CUow_Zw2.js} +1 -1
- package/studio/web/assets/{register-6qwNEOY3.js → register-Dkt3WUMp.js} +2 -2
- package/studio/web/assets/{setComponentFile-BilDMtgB.js → setComponentFile-BZRfMD0N.js} +1 -1
- package/studio/web/assets/{setup-J6qJ7OIU.js → setup-D_yiEPO2.js} +2 -2
- package/studio/web/assets/{status-0RWGcfyD.js → status-DhHh1Ge-.js} +1 -1
- package/studio/web/assets/{toggleHighContrast-BIn-vErT.js → toggleHighContrast-D7L1PDtV.js} +1 -1
- package/studio/web/assets/{tsMode-DgUXku4d.js → tsMode-CCwLk1YS.js} +1 -1
- package/studio/web/assets/{typescript-C9orXcsM.js → typescript-BP1j1mjn.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-BEoXXbUB.js → useEntityRestURL-D7bnYxLw.js} +1 -1
- package/studio/web/assets/{workers-JVzSDmgx.js → workers-tOuCNT17.js} +1 -1
- package/studio/web/assets/{xml-Cq-S8S4X.js → xml-BSG_3mQT.js} +1 -1
- package/studio/web/assets/{yaml-sfoRdh1M.js → yaml-DxiLprBB.js} +1 -1
- package/studio/web/index.html +1 -1
- package/utility/DESIGN.md +55 -0
- package/utility/logging/harper_logger.ts +1 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { FullTextDerivedIndexEngine } from './fullTextDerivedIndex.ts';
|
|
2
|
+
import { loggerWithTag } from '../../utility/logging/logger.ts';
|
|
3
|
+
|
|
4
|
+
const FULLTEXT_LIFECYCLE_API_VERSION = 1;
|
|
5
|
+
const FULLTEXT_MUTATION_BATCH_API_VERSION = 3;
|
|
6
|
+
const logger = loggerWithTag('fulltext-derived-index');
|
|
7
|
+
|
|
8
|
+
export interface NativeFullTextIndexConfiguration {
|
|
9
|
+
fields: Array<{ name: string; weight?: number }>;
|
|
10
|
+
analyzer: 'english@1';
|
|
11
|
+
stopWords?: boolean;
|
|
12
|
+
positions?: boolean;
|
|
13
|
+
surfaceTerms?: boolean;
|
|
14
|
+
limits: {
|
|
15
|
+
indexingThreads: number;
|
|
16
|
+
searchThreads: number;
|
|
17
|
+
writerMemoryBytes: number;
|
|
18
|
+
maxQueuedCommands: number;
|
|
19
|
+
maxQueuedBytes: number;
|
|
20
|
+
maxBatchBytes: number;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type NativeFullTextRuntimeInfo = {
|
|
25
|
+
packageVersion: string;
|
|
26
|
+
tantivyVersion: string;
|
|
27
|
+
nativeAbiVersion: number;
|
|
28
|
+
lifecycleApiVersion: number;
|
|
29
|
+
mutationBatchApiVersion: number;
|
|
30
|
+
storageBackends: ReadonlyArray<'native'>;
|
|
31
|
+
limits: { maxCommitPayloadBytes: number };
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export interface NativeFullTextModule {
|
|
35
|
+
NativeFullTextIndex: {
|
|
36
|
+
prototype: Pick<FullTextDerivedIndexEngine, 'applyMutationBatch' | 'publish' | 'close'>;
|
|
37
|
+
};
|
|
38
|
+
runtimeInfo(): Promise<NativeFullTextRuntimeInfo>;
|
|
39
|
+
validateNativeFullTextIndexOptions(
|
|
40
|
+
options: NativeFullTextIndexConfiguration & {
|
|
41
|
+
path: string;
|
|
42
|
+
indexId: string;
|
|
43
|
+
generation: string;
|
|
44
|
+
}
|
|
45
|
+
): void;
|
|
46
|
+
openNativeFullTextIndex(
|
|
47
|
+
options: NativeFullTextIndexConfiguration & {
|
|
48
|
+
path: string;
|
|
49
|
+
indexId: string;
|
|
50
|
+
generation: string;
|
|
51
|
+
}
|
|
52
|
+
): Promise<FullTextDerivedIndexEngine>;
|
|
53
|
+
inspectNativeFullTextIndex(
|
|
54
|
+
options: Omit<NativeFullTextIndexConfiguration, 'limits'> & {
|
|
55
|
+
path: string;
|
|
56
|
+
indexId: string;
|
|
57
|
+
generation: string;
|
|
58
|
+
}
|
|
59
|
+
):
|
|
60
|
+
| { state: 'missing' | 'cursorless' }
|
|
61
|
+
| { state: 'checkpointed'; committedPayload: string }
|
|
62
|
+
| { state: 'incompatible'; code: string };
|
|
63
|
+
resetNativeFullTextIndex(options: {
|
|
64
|
+
path: string;
|
|
65
|
+
indexId: string;
|
|
66
|
+
}): Promise<{ state: 'missing' } | { state: 'reset'; retiredPath: string }>;
|
|
67
|
+
reclaimRetiredNativeFullTextIndexes(options: {
|
|
68
|
+
path: string;
|
|
69
|
+
retiredPath?: string;
|
|
70
|
+
}): Promise<{ removed: number; failed: number }>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let bindingPromise: Promise<NativeFullTextModule> | undefined;
|
|
74
|
+
let bindingWarningLogged = false;
|
|
75
|
+
const validatedRuntimeInfo = new WeakMap<NativeFullTextModule, NativeFullTextRuntimeInfo>();
|
|
76
|
+
|
|
77
|
+
export async function loadFullTextNativeBinding(): Promise<NativeFullTextModule> {
|
|
78
|
+
if (!bindingPromise) {
|
|
79
|
+
const moduleName = '@harperfast/fulltext/native';
|
|
80
|
+
bindingPromise = import(moduleName).then(validateFullTextNativeBinding);
|
|
81
|
+
bindingPromise.catch((error) => {
|
|
82
|
+
bindingPromise = undefined;
|
|
83
|
+
if (!bindingWarningLogged) {
|
|
84
|
+
bindingWarningLogged = true;
|
|
85
|
+
const code = error && typeof error === 'object' && 'code' in error ? error.code : undefined;
|
|
86
|
+
try {
|
|
87
|
+
logger.warn?.(
|
|
88
|
+
`The @harperfast/fulltext native module is unavailable${typeof code === 'string' ? ` (${code})` : ''}`
|
|
89
|
+
);
|
|
90
|
+
} catch {}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return bindingPromise;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function validateFullTextNativeBinding(module: unknown): Promise<NativeFullTextModule> {
|
|
98
|
+
const prototype = (module as { NativeFullTextIndex?: { prototype?: Partial<FullTextDerivedIndexEngine> } })
|
|
99
|
+
?.NativeFullTextIndex?.prototype;
|
|
100
|
+
if (
|
|
101
|
+
!module ||
|
|
102
|
+
typeof module !== 'object' ||
|
|
103
|
+
!('runtimeInfo' in module) ||
|
|
104
|
+
typeof module.runtimeInfo !== 'function' ||
|
|
105
|
+
!('NativeFullTextIndex' in module) ||
|
|
106
|
+
typeof module.NativeFullTextIndex !== 'function' ||
|
|
107
|
+
typeof prototype?.applyMutationBatch !== 'function' ||
|
|
108
|
+
typeof prototype.publish !== 'function' ||
|
|
109
|
+
typeof prototype.close !== 'function' ||
|
|
110
|
+
!('openNativeFullTextIndex' in module) ||
|
|
111
|
+
typeof module.openNativeFullTextIndex !== 'function' ||
|
|
112
|
+
!('validateNativeFullTextIndexOptions' in module) ||
|
|
113
|
+
typeof module.validateNativeFullTextIndexOptions !== 'function' ||
|
|
114
|
+
!('inspectNativeFullTextIndex' in module) ||
|
|
115
|
+
typeof module.inspectNativeFullTextIndex !== 'function' ||
|
|
116
|
+
!('resetNativeFullTextIndex' in module) ||
|
|
117
|
+
typeof module.resetNativeFullTextIndex !== 'function' ||
|
|
118
|
+
!('reclaimRetiredNativeFullTextIndexes' in module) ||
|
|
119
|
+
typeof module.reclaimRetiredNativeFullTextIndexes !== 'function'
|
|
120
|
+
)
|
|
121
|
+
throw new TypeError('@harperfast/fulltext/native does not implement the required Harper binding contract');
|
|
122
|
+
const binding = module as NativeFullTextModule;
|
|
123
|
+
const info = await binding.runtimeInfo();
|
|
124
|
+
if (
|
|
125
|
+
!info ||
|
|
126
|
+
typeof info.packageVersion !== 'string' ||
|
|
127
|
+
typeof info.tantivyVersion !== 'string' ||
|
|
128
|
+
!Number.isSafeInteger(info.nativeAbiVersion) ||
|
|
129
|
+
info.lifecycleApiVersion !== FULLTEXT_LIFECYCLE_API_VERSION ||
|
|
130
|
+
info.mutationBatchApiVersion !== FULLTEXT_MUTATION_BATCH_API_VERSION ||
|
|
131
|
+
!Array.isArray(info.storageBackends) ||
|
|
132
|
+
!info.storageBackends.includes('native') ||
|
|
133
|
+
!info.limits ||
|
|
134
|
+
!Number.isSafeInteger(info.limits.maxCommitPayloadBytes) ||
|
|
135
|
+
info.limits.maxCommitPayloadBytes <= 0
|
|
136
|
+
)
|
|
137
|
+
throw new TypeError('@harperfast/fulltext/native reported incompatible runtime capabilities');
|
|
138
|
+
validatedRuntimeInfo.set(binding, info);
|
|
139
|
+
return binding;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function getValidatedFullTextRuntimeInfo(binding: NativeFullTextModule): NativeFullTextRuntimeInfo {
|
|
143
|
+
const info = validatedRuntimeInfo.get(binding);
|
|
144
|
+
if (!info) throw new Error('Full-text native binding has not been validated');
|
|
145
|
+
return info;
|
|
146
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
3
|
+
import { loggerWithTag } from '../../utility/logging/logger.ts';
|
|
4
|
+
import {
|
|
5
|
+
FullTextDerivedIndexBackend,
|
|
6
|
+
HARPER_FULLTEXT_MAX_CURSOR_PAYLOAD_BYTES,
|
|
7
|
+
type FullTextDerivedIndexBackendOptions,
|
|
8
|
+
type FullTextDerivedIndexEngine,
|
|
9
|
+
type FullTextDerivedIndexInspection,
|
|
10
|
+
} from './fullTextDerivedIndex.ts';
|
|
11
|
+
import {
|
|
12
|
+
getValidatedFullTextRuntimeInfo,
|
|
13
|
+
loadFullTextNativeBinding,
|
|
14
|
+
type NativeFullTextIndexConfiguration,
|
|
15
|
+
type NativeFullTextModule,
|
|
16
|
+
validateFullTextNativeBinding,
|
|
17
|
+
} from './fullTextNativeBinding.ts';
|
|
18
|
+
|
|
19
|
+
const logger = loggerWithTag('fulltext-derived-index');
|
|
20
|
+
|
|
21
|
+
export type NativeFullTextDerivedIndexLifecycleOptions = NativeFullTextIndexConfiguration & {
|
|
22
|
+
storePath: string;
|
|
23
|
+
storeName: string;
|
|
24
|
+
indexId: string;
|
|
25
|
+
sourceGeneration: string;
|
|
26
|
+
binding?: NativeFullTextModule | (() => Promise<NativeFullTextModule>);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type NativeFullTextDerivedIndexBackendOptions = Omit<FullTextDerivedIndexBackendOptions, 'lifecycle'> &
|
|
30
|
+
Omit<NativeFullTextDerivedIndexLifecycleOptions, 'indexId'>;
|
|
31
|
+
|
|
32
|
+
export class NativeFullTextDerivedIndexLifecycle {
|
|
33
|
+
readonly #options: NativeFullTextDerivedIndexLifecycleOptions;
|
|
34
|
+
readonly #path: string;
|
|
35
|
+
readonly #generation: string;
|
|
36
|
+
#binding?: NativeFullTextModule;
|
|
37
|
+
#maxCommitPayloadBytes?: number;
|
|
38
|
+
#reclaimOperation = Promise.resolve();
|
|
39
|
+
|
|
40
|
+
constructor(options: NativeFullTextDerivedIndexLifecycleOptions) {
|
|
41
|
+
if (!isAbsolute(options.storePath)) throw new TypeError('Full-text storePath must be absolute');
|
|
42
|
+
if (!options.storeName) throw new TypeError('Full-text storeName is required');
|
|
43
|
+
if (!options.indexId) throw new TypeError('Full-text indexId is required');
|
|
44
|
+
if (!options.sourceGeneration) throw new TypeError('Full-text sourceGeneration is required');
|
|
45
|
+
this.#options = {
|
|
46
|
+
...options,
|
|
47
|
+
fields: options.fields.map((field) => ({ ...field })),
|
|
48
|
+
limits: { ...options.limits },
|
|
49
|
+
};
|
|
50
|
+
// Hashing bounds filenames for arbitrary store names; the index id remains in native metadata and logs.
|
|
51
|
+
this.#path = join(resolve(options.storePath), `${digest(options.storeName)}.fulltext`);
|
|
52
|
+
this.#generation = digest(options.sourceGeneration);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get path(): string {
|
|
56
|
+
return this.#path;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async initialize(options: { reclaimRetired?: boolean } = {}): Promise<void> {
|
|
60
|
+
const binding = await this.#getBinding();
|
|
61
|
+
binding.validateNativeFullTextIndexOptions({
|
|
62
|
+
...this.#nativeOptions(),
|
|
63
|
+
limits: this.#options.limits,
|
|
64
|
+
});
|
|
65
|
+
this.#maxCommitPayloadBytes = getValidatedFullTextRuntimeInfo(binding).limits.maxCommitPayloadBytes;
|
|
66
|
+
if (options.reclaimRetired !== false) this.#queueReclaimRetired();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
startRetiredStorageReclamation(): void {
|
|
70
|
+
this.#requireBinding();
|
|
71
|
+
// Fulltext publishes unique retired paths and removes them with force, so workers may sweep concurrently.
|
|
72
|
+
this.#queueReclaimRetired();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get maxCommitPayloadBytes(): number {
|
|
76
|
+
if (this.#maxCommitPayloadBytes === undefined) throw new Error('Full-text lifecycle has not been initialized');
|
|
77
|
+
return this.#maxCommitPayloadBytes;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
inspect(): FullTextDerivedIndexInspection {
|
|
81
|
+
return this.#requireBinding().inspectNativeFullTextIndex(this.#nativeOptions());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async open(): Promise<FullTextDerivedIndexEngine> {
|
|
85
|
+
return this.#requireBinding().openNativeFullTextIndex({
|
|
86
|
+
...this.#nativeOptions(),
|
|
87
|
+
limits: this.#options.limits,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async reset(): Promise<void> {
|
|
92
|
+
const result = await this.#requireBinding().resetNativeFullTextIndex({
|
|
93
|
+
path: this.#path,
|
|
94
|
+
indexId: this.#options.indexId,
|
|
95
|
+
});
|
|
96
|
+
this.#queueReclaimRetired(result.state === 'reset' ? result.retiredPath : undefined);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#queueReclaimRetired(retiredPath?: string): void {
|
|
100
|
+
const reclaim = () => this.#reclaimRetired(retiredPath);
|
|
101
|
+
this.#reclaimOperation = this.#reclaimOperation.then(reclaim, reclaim).catch((error) => {
|
|
102
|
+
logWarning(`Could not complete reclamation for full-text index '${this.#options.indexId}'`, error);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async #reclaimRetired(retiredPath?: string): Promise<void> {
|
|
107
|
+
try {
|
|
108
|
+
const result = await this.#requireBinding().reclaimRetiredNativeFullTextIndexes({
|
|
109
|
+
path: this.#path,
|
|
110
|
+
retiredPath,
|
|
111
|
+
});
|
|
112
|
+
if (result.failed > 0)
|
|
113
|
+
logWarning(`Could not remove ${result.failed} retired paths for full-text index '${this.#options.indexId}'`);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
logWarning(`Could not reclaim retired paths for full-text index '${this.#options.indexId}'`, error);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#nativeOptions(): Omit<NativeFullTextIndexConfiguration, 'limits'> & {
|
|
120
|
+
path: string;
|
|
121
|
+
indexId: string;
|
|
122
|
+
generation: string;
|
|
123
|
+
} {
|
|
124
|
+
return {
|
|
125
|
+
path: this.#path,
|
|
126
|
+
indexId: this.#options.indexId,
|
|
127
|
+
generation: this.#generation,
|
|
128
|
+
fields: this.#options.fields,
|
|
129
|
+
analyzer: this.#options.analyzer,
|
|
130
|
+
stopWords: this.#options.stopWords,
|
|
131
|
+
positions: this.#options.positions,
|
|
132
|
+
surfaceTerms: this.#options.surfaceTerms,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async #getBinding(): Promise<NativeFullTextModule> {
|
|
137
|
+
if (this.#binding) return this.#binding;
|
|
138
|
+
const configured = this.#options.binding;
|
|
139
|
+
this.#binding = configured
|
|
140
|
+
? await validateFullTextNativeBinding(typeof configured === 'function' ? await configured() : configured)
|
|
141
|
+
: await loadFullTextNativeBinding();
|
|
142
|
+
return this.#binding;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#requireBinding(): NativeFullTextModule {
|
|
146
|
+
if (!this.#binding) throw new Error('Full-text native binding has not been initialized');
|
|
147
|
+
return this.#binding;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function createNativeFullTextDerivedIndexBackend(
|
|
152
|
+
options: NativeFullTextDerivedIndexBackendOptions
|
|
153
|
+
): Promise<FullTextDerivedIndexBackend> {
|
|
154
|
+
const maxCursorPayloadBytes = options.maxCursorPayloadBytes ?? HARPER_FULLTEXT_MAX_CURSOR_PAYLOAD_BYTES;
|
|
155
|
+
if (maxCursorPayloadBytes > HARPER_FULLTEXT_MAX_CURSOR_PAYLOAD_BYTES)
|
|
156
|
+
throw new RangeError(`Full-text maxCursorPayloadBytes must not exceed ${HARPER_FULLTEXT_MAX_CURSOR_PAYLOAD_BYTES}`);
|
|
157
|
+
const lifecycle = new NativeFullTextDerivedIndexLifecycle({ ...options, indexId: options.id });
|
|
158
|
+
await lifecycle.initialize({ reclaimRetired: false });
|
|
159
|
+
if (maxCursorPayloadBytes > lifecycle.maxCommitPayloadBytes)
|
|
160
|
+
throw new RangeError('Full-text maxCursorPayloadBytes exceeds the native commit payload capacity');
|
|
161
|
+
const backend = new FullTextDerivedIndexBackend({
|
|
162
|
+
...options,
|
|
163
|
+
id: options.id,
|
|
164
|
+
lifecycle,
|
|
165
|
+
maxCursorPayloadBytes,
|
|
166
|
+
});
|
|
167
|
+
lifecycle.startRetiredStorageReclamation();
|
|
168
|
+
return backend;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function digest(value: string): string {
|
|
172
|
+
return createHash('sha256').update(value).digest('hex');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function logWarning(message: string, error?: unknown): void {
|
|
176
|
+
const code =
|
|
177
|
+
error && typeof error === 'object' && 'code' in error && typeof error.code === 'string' ? error.code : undefined;
|
|
178
|
+
try {
|
|
179
|
+
logger.warn?.(`${message}${code ? ` (${code})` : ''}`);
|
|
180
|
+
} catch {}
|
|
181
|
+
}
|