@wirecat/cli-core 0.498.0 → 0.500.0
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/CHANGELOG.md +67 -0
- package/dist/calendar/mcp/index.d.ts +12 -12
- package/dist/cli/messenger/hearing-command.js +1 -1
- package/dist/cli/messenger/models-command.js +2 -2
- package/dist/cli/messenger/transcribe-command.js +14 -10
- package/dist/cli/runs/command.js +2 -1
- package/dist/domain/knowledge.d.ts +1 -1
- package/dist/domain/knowledge.js +1 -1
- package/dist/entities/fact.d.ts +15 -1
- package/dist/entities/fact.js +6 -1
- package/dist/entities/fields.d.ts +11 -0
- package/dist/entities/fields.js +11 -0
- package/dist/entities/index.d.ts +25 -1
- package/dist/entities/memory.d.ts +14 -0
- package/dist/entities/memory.js +6 -1
- package/dist/errors/reasons.d.ts +1 -1
- package/dist/errors/reasons.js +8 -0
- package/dist/meetings/transcription.d.ts +5 -0
- package/dist/meetings/transcription.js +27 -6
- package/dist/speech/decode.d.ts +11 -1
- package/dist/speech/decode.js +20 -15
- package/dist/speech/hearing.d.ts +3 -1
- package/dist/speech/hearing.js +4 -4
- package/dist/speech/index.d.ts +5 -2
- package/dist/speech/index.js +4 -2
- package/dist/speech/local.d.ts +8 -0
- package/dist/speech/local.js +22 -1
- package/dist/speech/models.d.ts +2 -0
- package/dist/speech/models.js +2 -0
- package/dist/speech/openai.d.ts +54 -0
- package/dist/speech/openai.js +199 -0
- package/dist/speech/recognize.d.ts +25 -1
- package/dist/speech/recognize.js +52 -21
- package/dist/speech/transcribe.d.ts +25 -9
- package/dist/speech/transcribe.js +72 -13
- package/dist/speech/wav.d.ts +2 -0
- package/dist/speech/wav.js +20 -0
- package/dist/store/index.d.ts +1 -0
- package/dist/store/project-transfer/request.d.ts +1 -1
- package/dist/store/project-transfer/schema.d.ts +2 -2
- package/dist/store/project-transfer/snapshot-model.d.ts +2 -2
- package/dist/store/sqlite/evidence-sources.d.ts +23 -0
- package/dist/store/sqlite/evidence-sources.js +59 -0
- package/dist/store/sqlite/facts.d.ts +16 -0
- package/dist/store/sqlite/facts.js +39 -5
- package/dist/store/sqlite/memories.d.ts +28 -0
- package/dist/store/sqlite/memories.js +62 -3
- package/dist/store/sqlite/project-transfer/snapshot.d.ts +1 -1
- package/package.json +1 -1
package/dist/speech/recognize.js
CHANGED
|
@@ -31,10 +31,9 @@ export const decodeOgg = async (bytes) => {
|
|
|
31
31
|
await decoder.ready;
|
|
32
32
|
try {
|
|
33
33
|
const { channelData, sampleRate, samplesDecoded } = await decoder.decodeFile(bytes);
|
|
34
|
-
|
|
35
|
-
if (!samples || samplesDecoded === 0)
|
|
34
|
+
if (!channelData[0] || samplesDecoded === 0)
|
|
36
35
|
throw new Error("no Ogg Opus audio in it");
|
|
37
|
-
return { samples, rate: sampleRate };
|
|
36
|
+
return channelData.map((samples) => ({ samples, rate: sampleRate }));
|
|
38
37
|
}
|
|
39
38
|
finally {
|
|
40
39
|
decoder.free();
|
|
@@ -59,6 +58,43 @@ export const detector = (vadModel) => ({
|
|
|
59
58
|
debug: 0,
|
|
60
59
|
bufferSizeInSeconds: 30,
|
|
61
60
|
});
|
|
61
|
+
/**
|
|
62
|
+
* `pcm` as `toModelRate` made it: the detector's sample offsets count its half-second lead, the recording does not.
|
|
63
|
+
* Each stretch goes to `each` as it is cut, so an hour of speech is never held twice.
|
|
64
|
+
*/
|
|
65
|
+
const cut = (vad, pcm, each) => {
|
|
66
|
+
// One detector can hear several recordings in a row; nothing of the last may leak into this one.
|
|
67
|
+
vad.reset();
|
|
68
|
+
const last = Math.max(0, pcm.length - LEAD);
|
|
69
|
+
const drain = () => {
|
|
70
|
+
for (; !vad.isEmpty(); vad.pop()) {
|
|
71
|
+
const { start, samples } = vad.front();
|
|
72
|
+
each({
|
|
73
|
+
start: Math.min(last, Math.max(0, start - LEAD)) / SAMPLE_RATE,
|
|
74
|
+
end: Math.min(last, Math.max(0, start + samples.length - LEAD)) / SAMPLE_RATE,
|
|
75
|
+
samples,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
for (let i = 0; i + WINDOW <= pcm.length; i += WINDOW) {
|
|
80
|
+
vad.acceptWaveform(pcm.subarray(i, i + WINDOW));
|
|
81
|
+
drain();
|
|
82
|
+
}
|
|
83
|
+
vad.flush();
|
|
84
|
+
drain();
|
|
85
|
+
};
|
|
86
|
+
/** The voice detector alone, for a recognizer that is not on this machine. */
|
|
87
|
+
export const openSpeechCutter = (vadModel) => {
|
|
88
|
+
const vad = loadSherpa().createVad(detector(vadModel));
|
|
89
|
+
return {
|
|
90
|
+
cut: (pcm) => {
|
|
91
|
+
const spans = [];
|
|
92
|
+
cut(vad, pcm, ({ start, end }) => spans.push({ start, end }));
|
|
93
|
+
return spans;
|
|
94
|
+
},
|
|
95
|
+
free: () => vad.free(),
|
|
96
|
+
};
|
|
97
|
+
};
|
|
62
98
|
/**
|
|
63
99
|
* The model, and a voice detector that cuts the audio at pauses into pieces of at most 20 seconds.
|
|
64
100
|
* The cutting is not optional: five minutes in one piece crash the WebAssembly build (measured).
|
|
@@ -107,25 +143,20 @@ export const openRecognizer = (model, path, vadModel) => {
|
|
|
107
143
|
stream.free();
|
|
108
144
|
}
|
|
109
145
|
};
|
|
146
|
+
const pieces = (pcm) => {
|
|
147
|
+
const heard = [];
|
|
148
|
+
cut(vad, pcm, ({ start, end, samples }) => {
|
|
149
|
+
const text = piece(samples);
|
|
150
|
+
if (text)
|
|
151
|
+
heard.push({ start, end, text });
|
|
152
|
+
});
|
|
153
|
+
return heard;
|
|
154
|
+
};
|
|
110
155
|
return {
|
|
111
|
-
recognize: (pcm) =>
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const drain = () => {
|
|
116
|
-
while (!vad.isEmpty()) {
|
|
117
|
-
parts.push(piece(vad.front().samples));
|
|
118
|
-
vad.pop();
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
for (let i = 0; i + WINDOW <= pcm.length; i += WINDOW) {
|
|
122
|
-
vad.acceptWaveform(pcm.subarray(i, i + WINDOW));
|
|
123
|
-
drain();
|
|
124
|
-
}
|
|
125
|
-
vad.flush();
|
|
126
|
-
drain();
|
|
127
|
-
return parts.filter(Boolean).join(" ");
|
|
128
|
-
},
|
|
156
|
+
recognize: (pcm) => pieces(pcm)
|
|
157
|
+
.map(({ text }) => text)
|
|
158
|
+
.join(" "),
|
|
159
|
+
pieces,
|
|
129
160
|
free: () => {
|
|
130
161
|
vad.free();
|
|
131
162
|
engine.free();
|
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
import { CliError } from "@wirecat/cli-base";
|
|
1
|
+
import { CliError, type KeyringStore } from "@wirecat/cli-base";
|
|
2
2
|
import type { Messenger } from "../cli/messenger/context.js";
|
|
3
3
|
import { type Settings } from "../cli/settings.js";
|
|
4
4
|
import { type MessengerAdapter, type RemoteFile, type Transcript } from "../ports/messenger.js";
|
|
5
5
|
import type { Ffmpeg } from "./decode.js";
|
|
6
6
|
import { type SpeechModel } from "./models.js";
|
|
7
7
|
import type { Recognizer } from "./recognize.js";
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* `messenger`: its own recognition (Telegram Premium). `local`: a model on this machine. `auto`: the first, then the
|
|
10
|
+
* second. `openai`: OpenAI's API, only when asked by name — `auto` never sends audio off the machine.
|
|
11
|
+
*/
|
|
12
|
+
export declare const TRANSCRIBE_WITH: readonly ["auto", "messenger", "local", "openai"];
|
|
10
13
|
export type TranscribeWith = (typeof TRANSCRIBE_WITH)[number];
|
|
11
14
|
export interface Heard extends Transcript {
|
|
12
15
|
messageId: string;
|
|
13
|
-
/** The messenger's provider, or `
|
|
16
|
+
/** The messenger's provider, `local` or `openai`. */
|
|
14
17
|
via: string;
|
|
15
|
-
/** The local model, when it was one. */
|
|
18
|
+
/** The local or OpenAI model, when it was one. */
|
|
16
19
|
model?: string;
|
|
17
20
|
}
|
|
18
21
|
export type OpenRecognizer = (model: SpeechModel, path: (name: string) => string, vadModel: string) => Recognizer;
|
|
@@ -24,17 +27,26 @@ export interface Choice {
|
|
|
24
27
|
/** The local recognizer, where a test hands one in. */
|
|
25
28
|
open?: OpenRecognizer;
|
|
26
29
|
ffmpeg?: Ffmpeg;
|
|
30
|
+
/** The profile's `transcribeModel` for `openai`, else `gpt-transcribe`. */
|
|
31
|
+
transcribeModel?: string;
|
|
32
|
+
apiKey?: string;
|
|
33
|
+
/** Why nothing can be heard: an unknown `transcribeWith`, or for `openai` no key or a read-only process. */
|
|
34
|
+
refusal?: CliError;
|
|
35
|
+
fetch?: (url: string, init: RequestInit) => Promise<Response>;
|
|
36
|
+
/** The command's own cancel and deadline, for a request that leaves the machine. */
|
|
37
|
+
signal?: AbortSignal;
|
|
27
38
|
}
|
|
28
39
|
/** A voice note, or an audio file someone sent — an iPhone recording reaches WhatsApp as `audio/mp4`. */
|
|
29
40
|
export declare const isAudioFile: ({ kind, mime }: {
|
|
30
41
|
kind: string;
|
|
31
42
|
mime?: string | undefined;
|
|
32
43
|
}) => boolean;
|
|
33
|
-
/** The flag, then the profile's `transcribeWith` and `
|
|
44
|
+
/** The flag, then the profile's `transcribeWith`, `speechModel` and `transcribeModel`, then `auto` and the CLI's first model. */
|
|
34
45
|
export declare const choose: (messenger: Messenger, settings: Pick<Settings, "configured" | "shared">, flags: {
|
|
35
46
|
local?: boolean;
|
|
36
47
|
model?: string;
|
|
37
|
-
|
|
48
|
+
with?: TranscribeWith;
|
|
49
|
+
}, env?: NodeJS.ProcessEnv, keyring?: KeyringStore) => Choice;
|
|
38
50
|
/** Never a download: a model is hundreds of MB, and whether to fetch one is the owner's call. */
|
|
39
51
|
export declare const notDownloaded: (messenger: Messenger, model: SpeechModel) => CliError;
|
|
40
52
|
/**
|
|
@@ -43,5 +55,9 @@ export declare const notDownloaded: (messenger: Messenger, model: SpeechModel) =
|
|
|
43
55
|
* `read` gets the recording once its bytes are all in, for a reference the messenger renewed while they were read.
|
|
44
56
|
*/
|
|
45
57
|
export declare const hearOnline: (messenger: Messenger, connection: MessengerAdapter, chat: string, messageId: string, choice: Choice, read?: (file: RemoteFile) => Promise<void>) => Promise<Heard | Uint8Array>;
|
|
46
|
-
/** The local model, on the bytes `hearOnline` fetched. */
|
|
47
|
-
export declare const hearLocally: (bytes: Uint8Array, messageId: string,
|
|
58
|
+
/** The local model, or OpenAI when chosen, on the bytes `hearOnline` fetched. */
|
|
59
|
+
export declare const hearLocally: (bytes: Uint8Array, messageId: string, choice: Choice) => Promise<Heard>;
|
|
60
|
+
/** Where a kept transcript came from, as `Kept` stores it: the messenger's provider, a local model, or `openai:<model>`. */
|
|
61
|
+
export declare const keptSource: (heard: Heard) => string;
|
|
62
|
+
/** The reverse of `keptSource`. */
|
|
63
|
+
export declare const heardVia: (source: string, provider: string) => Pick<Heard, "via" | "model">;
|
|
@@ -1,26 +1,52 @@
|
|
|
1
1
|
import { CliError, isCliError } from "@wirecat/cli-base";
|
|
2
2
|
import { fromFile } from "../cli/settings.js";
|
|
3
|
+
import { embeddingKeys } from "../embeddings/keys.js";
|
|
4
|
+
import { readOnlyLockOf, readOnlyRefusal } from "../ports/app.js";
|
|
3
5
|
import { capability } from "../ports/messenger.js";
|
|
4
6
|
import { typedCommand } from "../runs/scope.js";
|
|
5
7
|
import { installedBytes, isInstalled, megabytes, modelPath, modelsDirectory, vadPath } from "./install.js";
|
|
6
|
-
import { orderedModels, speechModel } from "./models.js";
|
|
7
|
-
/**
|
|
8
|
-
|
|
8
|
+
import { OPENAI_TRANSCRIBE_MODEL, orderedModels, speechModel } from "./models.js";
|
|
9
|
+
/**
|
|
10
|
+
* `messenger`: its own recognition (Telegram Premium). `local`: a model on this machine. `auto`: the first, then the
|
|
11
|
+
* second. `openai`: OpenAI's API, only when asked by name — `auto` never sends audio off the machine.
|
|
12
|
+
*/
|
|
13
|
+
export const TRANSCRIBE_WITH = ["auto", "messenger", "local", "openai"];
|
|
9
14
|
/** A recording is read into memory whole; minutes of Opus are a few megabytes. */
|
|
10
15
|
const LARGEST_VOICE = 32 * 1024 * 1024;
|
|
11
16
|
/** A voice note, or an audio file someone sent — an iPhone recording reaches WhatsApp as `audio/mp4`. */
|
|
12
17
|
export const isAudioFile = ({ kind, mime }) => kind === "voice" || kind === "audio" || (mime?.startsWith("audio/") ?? false);
|
|
13
|
-
/** The flag, then the profile's `transcribeWith` and `
|
|
14
|
-
export const choose = (messenger, settings, flags, env = process.env) => {
|
|
18
|
+
/** The flag, then the profile's `transcribeWith`, `speechModel` and `transcribeModel`, then `auto` and the CLI's first model. */
|
|
19
|
+
export const choose = (messenger, settings, flags, env = process.env, keyring) => {
|
|
15
20
|
const model = flags.model ?? fromFile(settings, "speechModel", undefined).value;
|
|
21
|
+
const chosen = flags.local || flags.model !== undefined
|
|
22
|
+
? "local"
|
|
23
|
+
: (flags.with ?? fromFile(settings, "transcribeWith", "auto").value);
|
|
24
|
+
// Refused only where something is heard: `models list` reads the same profile for its model alone.
|
|
25
|
+
const known = TRANSCRIBE_WITH.includes(chosen);
|
|
16
26
|
return {
|
|
17
27
|
directory: modelsDirectory(env),
|
|
18
|
-
with:
|
|
19
|
-
|
|
20
|
-
|
|
28
|
+
with: known ? chosen : "auto",
|
|
29
|
+
...(known
|
|
30
|
+
? {}
|
|
31
|
+
: {
|
|
32
|
+
refusal: new CliError("validation_error", `the profile's transcribeWith is one of ${TRANSCRIBE_WITH.join(", ")}`),
|
|
33
|
+
}),
|
|
21
34
|
model: model === undefined ? orderedModels(messenger.speechModels)[0] : speechModel(model),
|
|
35
|
+
transcribeModel: fromFile(settings, "transcribeModel", OPENAI_TRANSCRIBE_MODEL).value,
|
|
36
|
+
...(known && chosen === "openai" ? openaiAccess(messenger, env, keyring) : {}),
|
|
22
37
|
};
|
|
23
38
|
};
|
|
39
|
+
const openaiAccess = (messenger, env, keyring) => {
|
|
40
|
+
const lock = readOnlyLockOf(messenger.app, env);
|
|
41
|
+
if (lock)
|
|
42
|
+
return { refusal: readOnlyRefusal(lock, "send audio to OpenAI") };
|
|
43
|
+
const apiKey = embeddingKeys(messenger.app, env, keyring).read("openai")?.key;
|
|
44
|
+
return apiKey
|
|
45
|
+
? { apiKey }
|
|
46
|
+
: {
|
|
47
|
+
refusal: new CliError("authentication_error", `no OpenAI key — \`${typedCommand(messenger.app.command, "models keys set openai")}\`, or OPENAI_API_KEY`, { reason: "transcription.no-key" }),
|
|
48
|
+
};
|
|
49
|
+
};
|
|
24
50
|
/** Never a download: a model is hundreds of MB, and whether to fetch one is the owner's call. */
|
|
25
51
|
export const notDownloaded = (messenger, model) => new CliError("not_found", `the speech model ${model.id} is not on this machine — the owner downloads it once with ` +
|
|
26
52
|
`\`${typedCommand(messenger.app.command, `models download ${model.id}`)}\` (${megabytes(installedBytes(model))})`, { reason: "model.not-downloaded", model: model.id });
|
|
@@ -30,7 +56,9 @@ export const notDownloaded = (messenger, model) => new CliError("not_found", `th
|
|
|
30
56
|
* `read` gets the recording once its bytes are all in, for a reference the messenger renewed while they were read.
|
|
31
57
|
*/
|
|
32
58
|
export const hearOnline = async (messenger, connection, chat, messageId, choice, read) => {
|
|
33
|
-
if (choice.
|
|
59
|
+
if (choice.refusal)
|
|
60
|
+
throw choice.refusal;
|
|
61
|
+
if (choice.with === "auto" || choice.with === "messenger") {
|
|
34
62
|
const canAsk = typeof connection.transcribe === "function";
|
|
35
63
|
if (choice.with === "messenger" || canAsk) {
|
|
36
64
|
try {
|
|
@@ -44,7 +72,7 @@ export const hearOnline = async (messenger, connection, chat, messageId, choice,
|
|
|
44
72
|
}
|
|
45
73
|
}
|
|
46
74
|
}
|
|
47
|
-
if (!isInstalled(choice.model, choice.directory))
|
|
75
|
+
if (choice.with !== "openai" && !isInstalled(choice.model, choice.directory))
|
|
48
76
|
throw notDownloaded(messenger, choice.model);
|
|
49
77
|
const { files } = await capability(connection, "download", "download attachments")(chat, messageId);
|
|
50
78
|
const voice = files.find(isAudioFile);
|
|
@@ -54,11 +82,34 @@ export const hearOnline = async (messenger, connection, chat, messageId, choice,
|
|
|
54
82
|
await read?.(voice);
|
|
55
83
|
return bytes;
|
|
56
84
|
};
|
|
57
|
-
/** The local model, on the bytes `hearOnline` fetched. */
|
|
58
|
-
export const hearLocally = async (bytes, messageId,
|
|
59
|
-
const {
|
|
85
|
+
/** The local model, or OpenAI when chosen, on the bytes `hearOnline` fetched. */
|
|
86
|
+
export const hearLocally = async (bytes, messageId, choice) => {
|
|
87
|
+
const { model, directory, open, ffmpeg } = choice;
|
|
60
88
|
const { decodeAudio } = await import("./decode.js");
|
|
61
89
|
const { samples, rate } = await decodeAudio(bytes, ffmpeg);
|
|
90
|
+
if (choice.with === "openai") {
|
|
91
|
+
if (choice.refusal)
|
|
92
|
+
throw choice.refusal;
|
|
93
|
+
const { transcribeOpenAI } = await import("./openai.js");
|
|
94
|
+
const transcribeModel = choice.transcribeModel ?? OPENAI_TRANSCRIBE_MODEL;
|
|
95
|
+
const pieces = await transcribeOpenAI({ samples, rate }, {
|
|
96
|
+
apiKey: choice.apiKey ?? "",
|
|
97
|
+
model: transcribeModel,
|
|
98
|
+
vadModel: vadPath(directory),
|
|
99
|
+
// A voice message wants its text, not its turns: one request, and no voice detector needed.
|
|
100
|
+
bundle: true,
|
|
101
|
+
...(choice.fetch ? { fetch: choice.fetch } : {}),
|
|
102
|
+
...(choice.signal ? { signal: choice.signal } : {}),
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
messageId,
|
|
106
|
+
text: pieces.map(({ text }) => text).join(" "),
|
|
107
|
+
pending: false,
|
|
108
|
+
via: "openai",
|
|
109
|
+
model: transcribeModel,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const { openRecognizer, toModelRate } = await import("./recognize.js");
|
|
62
113
|
const recognizer = (open ?? openRecognizer)(model, modelPath(directory, model), vadPath(directory));
|
|
63
114
|
try {
|
|
64
115
|
return {
|
|
@@ -73,6 +124,14 @@ export const hearLocally = async (bytes, messageId, { model, directory, open, ff
|
|
|
73
124
|
recognizer.free();
|
|
74
125
|
}
|
|
75
126
|
};
|
|
127
|
+
/** Where a kept transcript came from, as `Kept` stores it: the messenger's provider, a local model, or `openai:<model>`. */
|
|
128
|
+
export const keptSource = (heard) => heard.via === "openai" ? `openai:${heard.model ?? OPENAI_TRANSCRIBE_MODEL}` : (heard.model ?? heard.via);
|
|
129
|
+
/** The reverse of `keptSource`. */
|
|
130
|
+
export const heardVia = (source, provider) => source === provider
|
|
131
|
+
? { via: source }
|
|
132
|
+
: source.startsWith("openai:")
|
|
133
|
+
? { via: "openai", model: source.slice("openai:".length) }
|
|
134
|
+
: { via: "local", model: source };
|
|
76
135
|
const readAll = async (file) => {
|
|
77
136
|
const chunks = [];
|
|
78
137
|
let total = 0;
|
package/dist/speech/wav.d.ts
CHANGED
|
@@ -25,3 +25,5 @@ export declare const openWavAudio: (plan: WavAudioPlan, input: WavAudioOptions)
|
|
|
25
25
|
windows(): AsyncIterable<WavPcmWindow>;
|
|
26
26
|
close(): Promise<void>;
|
|
27
27
|
}>;
|
|
28
|
+
/** PCM16 mono WAV of samples in [-1, 1], for a recognizer that takes a file. */
|
|
29
|
+
export declare const encodeWav: (samples: Float32Array, sampleRate?: number) => Uint8Array;
|
package/dist/speech/wav.js
CHANGED
|
@@ -196,3 +196,23 @@ export const openWavAudio = async (plan, input) => {
|
|
|
196
196
|
throw error;
|
|
197
197
|
}
|
|
198
198
|
};
|
|
199
|
+
/** PCM16 mono WAV of samples in [-1, 1], for a recognizer that takes a file. */
|
|
200
|
+
export const encodeWav = (samples, sampleRate = 16000) => {
|
|
201
|
+
const bytes = Buffer.alloc(44 + samples.length * 2);
|
|
202
|
+
bytes.write("RIFF", 0, "ascii");
|
|
203
|
+
bytes.writeUInt32LE(bytes.length - 8, 4);
|
|
204
|
+
bytes.write("WAVEfmt ", 8, "ascii");
|
|
205
|
+
bytes.writeUInt32LE(16, 16);
|
|
206
|
+
bytes.writeUInt16LE(1, 20);
|
|
207
|
+
bytes.writeUInt16LE(1, 22);
|
|
208
|
+
bytes.writeUInt32LE(sampleRate, 24);
|
|
209
|
+
bytes.writeUInt32LE(sampleRate * 2, 28);
|
|
210
|
+
bytes.writeUInt16LE(2, 32);
|
|
211
|
+
bytes.writeUInt16LE(16, 34);
|
|
212
|
+
bytes.write("data", 36, "ascii");
|
|
213
|
+
bytes.writeUInt32LE(samples.length * 2, 40);
|
|
214
|
+
samples.forEach((sample, index) => {
|
|
215
|
+
bytes.writeInt16LE(Math.round(Math.max(-1, Math.min(1, sample || 0)) * 32767), 44 + index * 2);
|
|
216
|
+
});
|
|
217
|
+
return new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.length);
|
|
218
|
+
};
|
package/dist/store/index.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ export { CATALOG_QUERY } from "./sqlite/catalog.js";
|
|
|
31
31
|
export type { ChatMetadata } from "./sqlite/chats.js";
|
|
32
32
|
export type { Email, EmailAddress, EmailAttachment, EmailFacts, EmailFilter, EmailInput, EmailInThread, EmailLocationInput, EmailRecipient, EmailRef, EmailThread, EmailVectorHit, FolderEmail, Mailbox, MailboxInput, MailFilter, MailStore, SenderScore, ThreadDetails, ThreadSave, } from "./sqlite/emails.js";
|
|
33
33
|
export { EVENT_FIELDS, type EventChunkToEmbed, type EventHit, type EventQuery, type EventSearch, type NearestEvent, } from "./sqlite/event-search.js";
|
|
34
|
+
export type { EvidenceSource } from "./sqlite/evidence-sources.js";
|
|
34
35
|
export type { Fact, FactInput, FactsStore } from "./sqlite/facts.js";
|
|
35
36
|
export type { FileListQuery, FilesStore, StoredFile } from "./sqlite/files.js";
|
|
36
37
|
export { IMPORTED_DOCUMENT_QUERY, type ImportedDocuments } from "./sqlite/imported-documents.js";
|
|
@@ -6,7 +6,7 @@ export declare const projectAnnotationRequest: (request: ProjectAnnotationTarget
|
|
|
6
6
|
ref: string;
|
|
7
7
|
type: "decision" | "fact" | "promise" | "question" | "statement";
|
|
8
8
|
statement: string;
|
|
9
|
-
status: "accepted" | "proposed" | "reversed" | "superseded";
|
|
9
|
+
status: "accepted" | "proposed" | "rejected" | "reversed" | "superseded";
|
|
10
10
|
subject: string | null;
|
|
11
11
|
occurred_at: number | null;
|
|
12
12
|
supersedes: string | null;
|
|
@@ -25,7 +25,7 @@ export declare const projectAnnotationsSchema: v.StrictObjectSchema<{
|
|
|
25
25
|
readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>;
|
|
26
26
|
readonly type: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise"], undefined>;
|
|
27
27
|
readonly statement: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>;
|
|
28
|
-
readonly status: v.PicklistSchema<readonly ["proposed", "accepted", "superseded", "reversed"], undefined>;
|
|
28
|
+
readonly status: v.PicklistSchema<readonly ["proposed", "accepted", "superseded", "reversed", "rejected"], undefined>;
|
|
29
29
|
readonly subject: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
30
30
|
readonly occurred_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.SafeIntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
|
|
31
31
|
readonly supersedes: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
@@ -40,7 +40,7 @@ export declare const projectAnnotationsSchema: v.StrictObjectSchema<{
|
|
|
40
40
|
ref: string;
|
|
41
41
|
type: "decision" | "fact" | "promise" | "question" | "statement";
|
|
42
42
|
statement: string;
|
|
43
|
-
status: "accepted" | "proposed" | "reversed" | "superseded";
|
|
43
|
+
status: "accepted" | "proposed" | "rejected" | "reversed" | "superseded";
|
|
44
44
|
subject: string | null;
|
|
45
45
|
occurred_at: number | null;
|
|
46
46
|
supersedes: string | null;
|
|
@@ -6,7 +6,7 @@ export declare const projectAnnotationSnapshotSchema: v.StrictObjectSchema<{
|
|
|
6
6
|
readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>;
|
|
7
7
|
readonly type: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise"], undefined>;
|
|
8
8
|
readonly statement: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>;
|
|
9
|
-
readonly status: v.PicklistSchema<readonly ["proposed", "accepted", "superseded", "reversed"], undefined>;
|
|
9
|
+
readonly status: v.PicklistSchema<readonly ["proposed", "accepted", "superseded", "reversed", "rejected"], undefined>;
|
|
10
10
|
readonly subject: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
11
11
|
readonly occurred_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.SafeIntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
|
|
12
12
|
readonly supersedes: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
@@ -21,7 +21,7 @@ export declare const projectAnnotationSnapshotSchema: v.StrictObjectSchema<{
|
|
|
21
21
|
ref: string;
|
|
22
22
|
type: "decision" | "fact" | "promise" | "question" | "statement";
|
|
23
23
|
statement: string;
|
|
24
|
-
status: "accepted" | "proposed" | "reversed" | "superseded";
|
|
24
|
+
status: "accepted" | "proposed" | "rejected" | "reversed" | "superseded";
|
|
25
25
|
subject: string | null;
|
|
26
26
|
occurred_at: number | null;
|
|
27
27
|
supersedes: string | null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CacheDatabase } from "../driver.js";
|
|
2
|
+
/** One reference a memory or fact rests on, read back whole enough to judge it without opening it. */
|
|
3
|
+
export interface EvidenceSource {
|
|
4
|
+
/** `null` when the row is gone from the store and its reference with it. */
|
|
5
|
+
ref: string | null;
|
|
6
|
+
kind: string;
|
|
7
|
+
title: string | null;
|
|
8
|
+
sender: string | null;
|
|
9
|
+
at: string | null;
|
|
10
|
+
excerpt: string | null;
|
|
11
|
+
/** The provider the source came through: `telegram`, `email`, `notes`, `zoom`… */
|
|
12
|
+
tool: string | null;
|
|
13
|
+
/** Deleted at its source since it was cited, or no longer in the store. */
|
|
14
|
+
missing: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Every `evidence` link from a memory or fact, in the order they were written, including one whose target was
|
|
18
|
+
* deleted since: `evidenceOf` drops those, so a reader would never learn the record lost a source.
|
|
19
|
+
*/
|
|
20
|
+
export declare const evidenceSourcesOf: (database: CacheDatabase, from: {
|
|
21
|
+
type: string;
|
|
22
|
+
id: number;
|
|
23
|
+
}) => EvidenceSource[];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { linkKind } from "./link-kinds.js";
|
|
2
|
+
import { referenceOfThing, stateOfThing, storedThing } from "./things.js";
|
|
3
|
+
const EXCERPT_CHARS = 280;
|
|
4
|
+
const excerptOf = (text) => {
|
|
5
|
+
if (text == null)
|
|
6
|
+
return null;
|
|
7
|
+
const flat = String(text).replace(/\s+/g, " ").trim();
|
|
8
|
+
if (!flat)
|
|
9
|
+
return null;
|
|
10
|
+
return flat.length > EXCERPT_CHARS ? `${flat.slice(0, EXCERPT_CHARS - 1)}…` : flat;
|
|
11
|
+
};
|
|
12
|
+
const iso = (value) => (value == null ? null : new Date(Number(value)).toISOString());
|
|
13
|
+
const textOf = (value) => (value == null || String(value).trim() === "" ? null : String(value));
|
|
14
|
+
const READS = {
|
|
15
|
+
message: "SELECT c.title, coalesce(m.sender_name, i.name) AS sender, m.sent_at AS at, m.text AS body, a.provider AS tool " +
|
|
16
|
+
"FROM messages m JOIN chats c ON c.id = m.chat_id JOIN accounts a ON a.id = c.account_id " +
|
|
17
|
+
"LEFT JOIN identities i ON i.id = m.sender_identity_id WHERE m.id = ?",
|
|
18
|
+
email: "SELECT e.subject AS title, coalesce(e.from_name, e.from_address) AS sender, e.sent_at AS at, " +
|
|
19
|
+
"coalesce(e.body_text, e.snippet) AS body, 'email' AS tool FROM emails e WHERE e.id = ?",
|
|
20
|
+
note: "SELECT title, NULL AS sender, updated_at AS at, body, 'notes' AS tool FROM notes WHERE id = ?",
|
|
21
|
+
document: "SELECT d.title, NULL AS sender, coalesce(d.external_updated_at, d.updated_at) AS at, d.body, a.provider AS tool " +
|
|
22
|
+
"FROM documents d JOIN accounts a ON a.id = d.account_id WHERE d.id = ?",
|
|
23
|
+
meeting: "SELECT m.title, NULL AS sender, m.started_at AS at, m.description AS body, a.provider AS tool " +
|
|
24
|
+
"FROM meetings m JOIN accounts a ON a.id = m.account_id WHERE m.id = ?",
|
|
25
|
+
meeting_transcript: "SELECT m.title, NULL AS sender, m.started_at AS at, NULL AS body, a.provider AS tool FROM meeting_transcripts t " +
|
|
26
|
+
"JOIN meetings m ON m.id = t.meeting_id JOIN accounts a ON a.id = m.account_id WHERE t.id = ?",
|
|
27
|
+
meeting_transcript_row: "SELECT m.title, r.speaker_name AS sender, m.started_at + coalesce(r.start_ms, 0) AS at, r.text AS body, " +
|
|
28
|
+
"a.provider AS tool FROM meeting_transcript_rows r JOIN meeting_transcripts t ON t.id = r.meeting_transcript_id " +
|
|
29
|
+
"JOIN meetings m ON m.id = t.meeting_id JOIN accounts a ON a.id = m.account_id WHERE r.id = ?",
|
|
30
|
+
};
|
|
31
|
+
const KIND = {
|
|
32
|
+
meeting_transcript: "meeting",
|
|
33
|
+
meeting_transcript_row: "meeting",
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Every `evidence` link from a memory or fact, in the order they were written, including one whose target was
|
|
37
|
+
* deleted since: `evidenceOf` drops those, so a reader would never learn the record lost a source.
|
|
38
|
+
*/
|
|
39
|
+
export const evidenceSourcesOf = (database, from) => database
|
|
40
|
+
.prepare(`SELECT to_type, to_id FROM links WHERE from_type = ? AND from_id = ? AND kind = ${linkKind("evidence")} ` +
|
|
41
|
+
"AND to_id IS NOT NULL ORDER BY id")
|
|
42
|
+
.all(from.type, from.id)
|
|
43
|
+
.map((link) => {
|
|
44
|
+
const thing = storedThing(link.to_type, link.to_id);
|
|
45
|
+
const state = stateOfThing(database, thing);
|
|
46
|
+
const read = READS[thing.type];
|
|
47
|
+
const row = read === undefined || state === "unavailable" ? undefined : database.prepare(read).get(thing.id);
|
|
48
|
+
const gone = state !== "available";
|
|
49
|
+
return {
|
|
50
|
+
ref: referenceOfThing(database, thing) ?? null,
|
|
51
|
+
kind: KIND[thing.type] ?? thing.type,
|
|
52
|
+
title: textOf(row?.title),
|
|
53
|
+
sender: textOf(row?.sender),
|
|
54
|
+
at: iso(row?.at),
|
|
55
|
+
excerpt: gone ? null : excerptOf(row?.body),
|
|
56
|
+
tool: textOf(row?.tool),
|
|
57
|
+
missing: gone,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type FACT_STATUSES, FACT_TYPES, MEMORY_SCOPES } from "../../domain/knowledge.js";
|
|
2
2
|
import type { Actor } from "./actors.js";
|
|
3
|
+
import { type EvidenceSource } from "./evidence-sources.js";
|
|
3
4
|
import { type Author } from "./memories.js";
|
|
4
5
|
import type { StoreContext } from "./open.js";
|
|
5
6
|
/** What happened, as its evidence shows; its evidence is `links` of kind `evidence`. */
|
|
@@ -12,6 +13,8 @@ export interface Fact {
|
|
|
12
13
|
status: (typeof FACT_STATUSES)[number];
|
|
13
14
|
project: string | null;
|
|
14
15
|
subject: string | null;
|
|
16
|
+
subjectName: string | null;
|
|
17
|
+
subjectIsOwner: boolean;
|
|
15
18
|
occurredAt: string | null;
|
|
16
19
|
supersedes: string | null;
|
|
17
20
|
/** `name` is a bot's handle; `null` for a person. */
|
|
@@ -61,6 +64,19 @@ export interface FactsStore {
|
|
|
61
64
|
accept(reference: string): Promise<Fact>;
|
|
62
65
|
/** An accepted decision no longer holds and nothing replaced it. */
|
|
63
66
|
reverse(reference: string): Promise<Fact>;
|
|
67
|
+
/** The owner turns down a proposed fact; it stays, marked `rejected`. */
|
|
68
|
+
reject(reference: string): Promise<Fact>;
|
|
69
|
+
/**
|
|
70
|
+
* Erases the fact: its row, its links, its tags, and the memory it was migrated from, which still held its text.
|
|
71
|
+
* A decision that replaced it no longer names it. A backup made before keeps it.
|
|
72
|
+
*/
|
|
73
|
+
remove(reference: string): Promise<{
|
|
74
|
+
ref: string;
|
|
75
|
+
removed: true;
|
|
76
|
+
links: number;
|
|
77
|
+
}>;
|
|
78
|
+
/** Each reference it rests on, read back; one deleted since is `missing`. */
|
|
79
|
+
sources(reference: string): Promise<EvidenceSource[]>;
|
|
64
80
|
}
|
|
65
81
|
type FactOperations = {
|
|
66
82
|
[K in keyof FactsStore]: FactsStore[K] extends (...args: infer A) => Promise<infer R> ? (...args: A) => R : never;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { FACT_TYPES, MEMORY_SCOPES } from "../../domain/knowledge.js";
|
|
3
3
|
import { calendarMoment } from "../../domain/moment.js";
|
|
4
|
+
import { evidenceSourcesOf } from "./evidence-sources.js";
|
|
4
5
|
import { linkKind } from "./link-kinds.js";
|
|
5
|
-
import { authorActor, authorOf, evidenceOf, linkEvidence } from "./memories.js";
|
|
6
|
+
import { authorActor, authorOf, dropLinksOf, erase, evidenceOf, linkEvidence, subjectOf, } from "./memories.js";
|
|
6
7
|
import { requiredThing } from "./notes.js";
|
|
7
|
-
import { referenceOfThing, storedThing } from "./things.js";
|
|
8
8
|
import { write } from "./transaction.js";
|
|
9
9
|
const NAMED = "SELECT f.*, b.name AS author_name FROM facts f LEFT JOIN bots b ON f.author_type = 'bot' AND b.id = f.author_id";
|
|
10
10
|
const iso = (value) => new Date(Number(value)).toISOString();
|
|
@@ -22,9 +22,7 @@ export const factOperationsOver = (context) => {
|
|
|
22
22
|
statement: String(row.statement),
|
|
23
23
|
status: row.status,
|
|
24
24
|
project: row.project_id == null ? null : `project:${row.project_id}`,
|
|
25
|
-
|
|
26
|
-
? null
|
|
27
|
-
: (referenceOfThing(database, storedThing(row.subject_type, row.subject_id)) ?? null),
|
|
25
|
+
...subjectOf(database, row),
|
|
28
26
|
occurredAt: row.occurred_at == null ? null : iso(row.occurred_at),
|
|
29
27
|
supersedes: row.supersedes_id == null ? null : `fact:${row.supersedes_id}`,
|
|
30
28
|
author: authorOf(row),
|
|
@@ -125,8 +123,41 @@ export const factOperationsOver = (context) => {
|
|
|
125
123
|
database.prepare("UPDATE facts SET status = 'reversed', updated_at = ? WHERE id = ?").run(now(), Number(row.id));
|
|
126
124
|
return factOf(rowOf(reference));
|
|
127
125
|
},
|
|
126
|
+
reject: (reference) => {
|
|
127
|
+
const row = rowOf(reference);
|
|
128
|
+
if (row.status !== "proposed")
|
|
129
|
+
throw new CliError("validation_error", `only a proposed fact can be rejected; this one is ${String(row.status)}`);
|
|
130
|
+
database.prepare("UPDATE facts SET status = 'rejected', updated_at = ? WHERE id = ?").run(now(), Number(row.id));
|
|
131
|
+
return factOf(rowOf(reference));
|
|
132
|
+
},
|
|
133
|
+
remove: (reference) => {
|
|
134
|
+
const row = rowOf(reference);
|
|
135
|
+
const id = Number(row.id);
|
|
136
|
+
const twin = row.source === "memory" && row.metadata != null ? migratedFrom(String(row.metadata)) : undefined;
|
|
137
|
+
const links = erase(database, () => {
|
|
138
|
+
database.prepare("UPDATE facts SET supersedes_id = NULL WHERE supersedes_id = ?").run(id);
|
|
139
|
+
const count = dropLinksOf(database, { type: "fact", id });
|
|
140
|
+
database.prepare("DELETE FROM links WHERE from_type = 'fact' AND from_id = ?").run(id);
|
|
141
|
+
database.prepare("DELETE FROM taggings WHERE taggable_type = 'fact' AND taggable_id = ?").run(id);
|
|
142
|
+
database.prepare("DELETE FROM facts WHERE id = ?").run(id);
|
|
143
|
+
if (twin !== undefined)
|
|
144
|
+
database.prepare("DELETE FROM memories WHERE id = ? AND deleted_at IS NOT NULL").run(twin);
|
|
145
|
+
return count;
|
|
146
|
+
});
|
|
147
|
+
return { ref: `fact:${id}`, removed: true, links };
|
|
148
|
+
},
|
|
149
|
+
sources: (reference) => evidenceSourcesOf(database, { type: "fact", id: Number(rowOf(reference).id) }),
|
|
128
150
|
};
|
|
129
151
|
};
|
|
152
|
+
const migratedFrom = (metadata) => {
|
|
153
|
+
try {
|
|
154
|
+
const { memory } = JSON.parse(metadata);
|
|
155
|
+
return Number.isInteger(memory) ? Number(memory) : undefined;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
130
161
|
export const factsStoreOver = (context) => {
|
|
131
162
|
const operations = factOperationsOver(context);
|
|
132
163
|
return {
|
|
@@ -135,5 +166,8 @@ export const factsStoreOver = (context) => {
|
|
|
135
166
|
list: async (options) => operations.list(options),
|
|
136
167
|
accept: async (reference) => operations.accept(reference),
|
|
137
168
|
reverse: async (reference) => operations.reverse(reference),
|
|
169
|
+
reject: async (reference) => operations.reject(reference),
|
|
170
|
+
remove: async (reference) => operations.remove(reference),
|
|
171
|
+
sources: async (reference) => operations.sources(reference),
|
|
138
172
|
};
|
|
139
173
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MEMORY_KINDS, MEMORY_SCOPES, type MEMORY_STATUSES } from "../../domain/knowledge.js";
|
|
2
2
|
import { type Actor } from "./actors.js";
|
|
3
|
+
import { type EvidenceSource } from "./evidence-sources.js";
|
|
3
4
|
import { type NoteIndexState } from "./note-index.js";
|
|
4
5
|
import type { StoreContext } from "./open.js";
|
|
5
6
|
/** Who wrote it: the owner, or an agent by its bot handle and the model it ran on. */
|
|
@@ -14,6 +15,10 @@ export interface Memory {
|
|
|
14
15
|
body: string;
|
|
15
16
|
/** What it is about, as a reference; `null` for a general fact. */
|
|
16
17
|
subject: string | null;
|
|
18
|
+
/** What the subject is called; `null` for the owner, who has no name in the store, and for a nameless row. */
|
|
19
|
+
subjectName: string | null;
|
|
20
|
+
/** The subject is the owner. */
|
|
21
|
+
subjectIsOwner: boolean;
|
|
17
22
|
/** `name` is a bot's handle; `null` for a person. */
|
|
18
23
|
author: {
|
|
19
24
|
type: Actor["type"];
|
|
@@ -59,6 +64,17 @@ export interface MemoriesStore {
|
|
|
59
64
|
confirm(reference: string): Promise<Memory>;
|
|
60
65
|
/** Its evidence no longer holds. */
|
|
61
66
|
markStale(reference: string): Promise<Memory>;
|
|
67
|
+
/**
|
|
68
|
+
* Erases the memory: its row, its text in the search and meaning indexes, and every link from or to it. A memory
|
|
69
|
+
* that replaced it no longer names it. A backup made before keeps it.
|
|
70
|
+
*/
|
|
71
|
+
remove(reference: string): Promise<{
|
|
72
|
+
ref: string;
|
|
73
|
+
removed: true;
|
|
74
|
+
links: number;
|
|
75
|
+
}>;
|
|
76
|
+
/** Each reference it rests on, read back; one deleted since is `missing`. */
|
|
77
|
+
sources(reference: string): Promise<EvidenceSource[]>;
|
|
62
78
|
/** Indexes what was written since, then finds memories holding every word or a longer one it starts, best first. */
|
|
63
79
|
search(text: string, options?: {
|
|
64
80
|
subject?: string;
|
|
@@ -82,5 +98,17 @@ export declare const evidenceOf: (context: StoreContext, from: {
|
|
|
82
98
|
type: string;
|
|
83
99
|
id: number;
|
|
84
100
|
}) => string[];
|
|
101
|
+
/** What a memory's or fact's subject is called, for a reader who should not have to look the reference up. */
|
|
102
|
+
export declare const subjectOf: (database: StoreContext["database"], row: Row) => Pick<Memory, "subject" | "subjectName" | "subjectIsOwner">;
|
|
103
|
+
/** Counts every link from or to a row and drops the ones its delete trigger does not: those pointing at it. */
|
|
104
|
+
export declare const dropLinksOf: (database: StoreContext["database"], thing: {
|
|
105
|
+
type: string;
|
|
106
|
+
id: number;
|
|
107
|
+
}) => number;
|
|
108
|
+
/**
|
|
109
|
+
* Runs a delete so the text does not linger in the file: freed pages are zeroed, the words indexes merged so no
|
|
110
|
+
* segment still holds the erased words, and the write-ahead log emptied when no reader holds it.
|
|
111
|
+
*/
|
|
112
|
+
export declare const erase: <T>(database: StoreContext["database"], work: () => T) => T;
|
|
85
113
|
export declare const memoriesStoreOver: (context: StoreContext) => MemoriesStore;
|
|
86
114
|
export {};
|