om-memory-system 3.0.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/LICENSE +21 -0
- package/README.md +587 -0
- package/dist/adapters/opencode/auto-capture-host.d.ts +3 -0
- package/dist/adapters/opencode/auto-capture-host.js +29 -0
- package/dist/adapters/opencode/auto-capture-summary.d.ts +4 -0
- package/dist/adapters/opencode/auto-capture-summary.js +147 -0
- package/dist/adapters/opencode/conversation.d.ts +2 -0
- package/dist/adapters/opencode/conversation.js +50 -0
- package/dist/adapters/opencode/user-prompt.d.ts +9 -0
- package/dist/adapters/opencode/user-prompt.js +26 -0
- package/dist/adapters/pi/capture.d.ts +41 -0
- package/dist/adapters/pi/capture.js +91 -0
- package/dist/adapters/pi/conversation.d.ts +38 -0
- package/dist/adapters/pi/conversation.js +118 -0
- package/dist/adapters/pi/extension.d.ts +18 -0
- package/dist/adapters/pi/extension.js +234 -0
- package/dist/adapters/pi/import-command.d.ts +29 -0
- package/dist/adapters/pi/import-command.js +232 -0
- package/dist/adapters/pi/profile.d.ts +39 -0
- package/dist/adapters/pi/profile.js +148 -0
- package/dist/adapters/pi/provider.d.ts +50 -0
- package/dist/adapters/pi/provider.js +77 -0
- package/dist/config.d.ts +290 -0
- package/dist/config.js +742 -0
- package/dist/core/capture-context.d.ts +4 -0
- package/dist/core/capture-context.js +132 -0
- package/dist/core/capture.d.ts +15 -0
- package/dist/core/capture.js +68 -0
- package/dist/core/extraction.d.ts +80 -0
- package/dist/core/extraction.js +140 -0
- package/dist/core/host.d.ts +52 -0
- package/dist/core/host.js +1 -0
- package/dist/core/memory-operations.d.ts +27 -0
- package/dist/core/memory-operations.js +247 -0
- package/dist/core/retrieval.d.ts +12 -0
- package/dist/core/retrieval.js +72 -0
- package/dist/importer/discovery.d.ts +31 -0
- package/dist/importer/discovery.js +88 -0
- package/dist/importer/importer.d.ts +87 -0
- package/dist/importer/importer.js +260 -0
- package/dist/importer/ledger.d.ts +44 -0
- package/dist/importer/ledger.js +137 -0
- package/dist/importer/session-loader.d.ts +19 -0
- package/dist/importer/session-loader.js +12 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +601 -0
- package/dist/plugin.d.ts +9 -0
- package/dist/plugin.js +11 -0
- package/dist/services/ai/ai-provider-factory.d.ts +7 -0
- package/dist/services/ai/ai-provider-factory.js +41 -0
- package/dist/services/ai/api-key-placeholder.d.ts +1 -0
- package/dist/services/ai/api-key-placeholder.js +14 -0
- package/dist/services/ai/internal-capture-sessions.d.ts +6 -0
- package/dist/services/ai/internal-capture-sessions.js +33 -0
- package/dist/services/ai/opencode-diagnostics.d.ts +7 -0
- package/dist/services/ai/opencode-diagnostics.js +36 -0
- package/dist/services/ai/opencode-host-config.d.ts +10 -0
- package/dist/services/ai/opencode-host-config.js +51 -0
- package/dist/services/ai/opencode-provider-loader.d.ts +3 -0
- package/dist/services/ai/opencode-provider-loader.js +4 -0
- package/dist/services/ai/opencode-provider.d.ts +92 -0
- package/dist/services/ai/opencode-provider.js +419 -0
- package/dist/services/ai/opencode-sdk-client.d.ts +6 -0
- package/dist/services/ai/opencode-sdk-client.js +38 -0
- package/dist/services/ai/profile-llm-client.d.ts +2 -0
- package/dist/services/ai/profile-llm-client.js +27 -0
- package/dist/services/ai/provider-config.d.ts +17 -0
- package/dist/services/ai/provider-config.js +30 -0
- package/dist/services/ai/providers/anthropic-messages.d.ts +34 -0
- package/dist/services/ai/providers/anthropic-messages.js +220 -0
- package/dist/services/ai/providers/base-provider.d.ts +24 -0
- package/dist/services/ai/providers/base-provider.js +24 -0
- package/dist/services/ai/providers/google-gemini.d.ts +15 -0
- package/dist/services/ai/providers/google-gemini.js +228 -0
- package/dist/services/ai/providers/minimax.d.ts +35 -0
- package/dist/services/ai/providers/minimax.js +55 -0
- package/dist/services/ai/providers/openai-chat-completion.d.ts +22 -0
- package/dist/services/ai/providers/openai-chat-completion.js +387 -0
- package/dist/services/ai/providers/openai-responses.d.ts +13 -0
- package/dist/services/ai/providers/openai-responses.js +182 -0
- package/dist/services/ai/providers/orcarouter.d.ts +51 -0
- package/dist/services/ai/providers/orcarouter.js +66 -0
- package/dist/services/ai/session/ai-session-manager.d.ts +24 -0
- package/dist/services/ai/session/ai-session-manager.js +211 -0
- package/dist/services/ai/session/session-types.d.ts +42 -0
- package/dist/services/ai/session/session-types.js +1 -0
- package/dist/services/ai/tools/tool-schema.d.ts +40 -0
- package/dist/services/ai/tools/tool-schema.js +24 -0
- package/dist/services/ai/validators/user-profile-validator.d.ts +12 -0
- package/dist/services/ai/validators/user-profile-validator.js +114 -0
- package/dist/services/api-handlers.d.ts +186 -0
- package/dist/services/api-handlers.js +1284 -0
- package/dist/services/auth-token.d.ts +14 -0
- package/dist/services/auth-token.js +59 -0
- package/dist/services/auto-capture.d.ts +3 -0
- package/dist/services/auto-capture.js +127 -0
- package/dist/services/cleanup-service.d.ts +22 -0
- package/dist/services/cleanup-service.js +109 -0
- package/dist/services/client.d.ts +134 -0
- package/dist/services/client.js +327 -0
- package/dist/services/context.d.ts +10 -0
- package/dist/services/context.js +27 -0
- package/dist/services/cors.d.ts +12 -0
- package/dist/services/cors.js +52 -0
- package/dist/services/deduplication-service.d.ts +28 -0
- package/dist/services/deduplication-service.js +111 -0
- package/dist/services/embedding.d.ts +36 -0
- package/dist/services/embedding.js +200 -0
- package/dist/services/jsonc.d.ts +6 -0
- package/dist/services/jsonc.js +76 -0
- package/dist/services/language-detector.d.ts +2 -0
- package/dist/services/language-detector.js +33 -0
- package/dist/services/legacy-migration.d.ts +77 -0
- package/dist/services/legacy-migration.js +343 -0
- package/dist/services/logger.d.ts +1 -0
- package/dist/services/logger.js +119 -0
- package/dist/services/memory-portability-service.d.ts +37 -0
- package/dist/services/memory-portability-service.js +340 -0
- package/dist/services/memory-scope.d.ts +16 -0
- package/dist/services/memory-scope.js +43 -0
- package/dist/services/migration-service.d.ts +42 -0
- package/dist/services/migration-service.js +307 -0
- package/dist/services/onnxruntime-resolve.d.ts +24 -0
- package/dist/services/onnxruntime-resolve.js +174 -0
- package/dist/services/privacy.d.ts +26 -0
- package/dist/services/privacy.js +69 -0
- package/dist/services/request-timeouts.d.ts +9 -0
- package/dist/services/request-timeouts.js +11 -0
- package/dist/services/runtime-require.d.ts +17 -0
- package/dist/services/runtime-require.js +75 -0
- package/dist/services/secret-resolver.d.ts +1 -0
- package/dist/services/secret-resolver.js +65 -0
- package/dist/services/shard-inventory-service.d.ts +69 -0
- package/dist/services/shard-inventory-service.js +315 -0
- package/dist/services/shard-path-migration-service.d.ts +31 -0
- package/dist/services/shard-path-migration-service.js +367 -0
- package/dist/services/tag-prefix-migration.d.ts +60 -0
- package/dist/services/tag-prefix-migration.js +439 -0
- package/dist/services/tag-prefix-scan.d.ts +26 -0
- package/dist/services/tag-prefix-scan.js +91 -0
- package/dist/services/tags.d.ts +29 -0
- package/dist/services/tags.js +251 -0
- package/dist/services/turso/connection-manager.d.ts +15 -0
- package/dist/services/turso/connection-manager.js +163 -0
- package/dist/services/turso/cross-process-write-lock.d.ts +1 -0
- package/dist/services/turso/cross-process-write-lock.js +89 -0
- package/dist/services/turso/legacy-migrator.d.ts +1 -0
- package/dist/services/turso/legacy-migrator.js +540 -0
- package/dist/services/turso/lifecycle.d.ts +1 -0
- package/dist/services/turso/lifecycle.js +14 -0
- package/dist/services/turso/operation-lock.d.ts +2 -0
- package/dist/services/turso/operation-lock.js +61 -0
- package/dist/services/turso/ready.d.ts +2 -0
- package/dist/services/turso/ready.js +36 -0
- package/dist/services/turso/shard-manager.d.ts +38 -0
- package/dist/services/turso/shard-manager.js +496 -0
- package/dist/services/turso/sqlite-handle-release.d.ts +16 -0
- package/dist/services/turso/sqlite-handle-release.js +56 -0
- package/dist/services/turso/turso-db.d.ts +18 -0
- package/dist/services/turso/turso-db.js +53 -0
- package/dist/services/turso/types.d.ts +42 -0
- package/dist/services/turso/types.js +1 -0
- package/dist/services/turso/vector-search.d.ts +41 -0
- package/dist/services/turso/vector-search.js +384 -0
- package/dist/services/turso/vector-utils.d.ts +7 -0
- package/dist/services/turso/vector-utils.js +67 -0
- package/dist/services/user-memory-learning.d.ts +97 -0
- package/dist/services/user-memory-learning.js +673 -0
- package/dist/services/user-profile/ai-cleanup.d.ts +58 -0
- package/dist/services/user-profile/ai-cleanup.js +486 -0
- package/dist/services/user-profile/profile-context.d.ts +1 -0
- package/dist/services/user-profile/profile-context.js +105 -0
- package/dist/services/user-profile/profile-utils.d.ts +2 -0
- package/dist/services/user-profile/profile-utils.js +45 -0
- package/dist/services/user-profile/types.d.ts +92 -0
- package/dist/services/user-profile/types.js +1 -0
- package/dist/services/user-profile/user-profile-manager.d.ts +65 -0
- package/dist/services/user-profile/user-profile-manager.js +1660 -0
- package/dist/services/user-prompt/user-prompt-manager.d.ts +53 -0
- package/dist/services/user-prompt/user-prompt-manager.js +303 -0
- package/dist/services/web-api-auth.d.ts +3 -0
- package/dist/services/web-api-auth.js +25 -0
- package/dist/services/web-auth.d.ts +46 -0
- package/dist/services/web-auth.js +105 -0
- package/dist/services/web-server.d.ts +57 -0
- package/dist/services/web-server.js +659 -0
- package/dist/shared/api/index.d.ts +4 -0
- package/dist/shared/api/index.js +2 -0
- package/dist/shared/api/portability-schemas.d.ts +56 -0
- package/dist/shared/api/portability-schemas.js +39 -0
- package/dist/shared/api/schemas.d.ts +295 -0
- package/dist/shared/api/schemas.js +134 -0
- package/dist/shared/api/types.d.ts +28 -0
- package/dist/shared/api/types.js +1 -0
- package/dist/types/index.d.ts +27 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/context-limit.d.ts +10 -0
- package/dist/utils/context-limit.js +43 -0
- package/dist/utils/math.d.ts +7 -0
- package/dist/utils/math.js +48 -0
- package/dist/utils/profile.d.ts +2 -0
- package/dist/utils/profile.js +15 -0
- package/dist/v2/adapter.d.ts +5 -0
- package/dist/v2/adapter.js +218 -0
- package/dist/v2/legacy-client.d.ts +15 -0
- package/dist/v2/legacy-client.js +257 -0
- package/dist/v2/memory-bridge.d.ts +18 -0
- package/dist/v2/memory-bridge.js +29 -0
- package/dist/v2/plugin.d.ts +3 -0
- package/dist/v2/plugin.js +24 -0
- package/dist/web/assets/index-DPjEpD1h.css +2 -0
- package/dist/web/assets/index-do26c4JJ.js +122 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/index.html +14 -0
- package/dist/web/omms-icon.png +0 -0
- package/package.json +138 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
import { CONFIG } from "../../config.js";
|
|
4
|
+
import { log } from "../logger.js";
|
|
5
|
+
import { tursoConnectionManager } from "./connection-manager.js";
|
|
6
|
+
import { tursoShardManager } from "./shard-manager.js";
|
|
7
|
+
import { withSqliteFileLockRetry } from "./sqlite-handle-release.js";
|
|
8
|
+
import { tursoVectorSearch } from "./vector-search.js";
|
|
9
|
+
import { blobToFloat32Array } from "./vector-utils.js";
|
|
10
|
+
const MIGRATION_MARKER = ".turso-migrated";
|
|
11
|
+
const MIGRATION_LOCK = ".turso-migrate.lock";
|
|
12
|
+
const SIDECAR_SUFFIX = ".turso-migrate.json";
|
|
13
|
+
const BACKUP_SUFFIX = ".legacy.bak";
|
|
14
|
+
const REEMBED_SWAP_SUFFIX = ".reembed-swap.json";
|
|
15
|
+
function sidecarPath(dbPath) {
|
|
16
|
+
return `${dbPath}${SIDECAR_SUFFIX}`;
|
|
17
|
+
}
|
|
18
|
+
function backupPath(dbPath) {
|
|
19
|
+
return `${dbPath}${BACKUP_SUFFIX}`;
|
|
20
|
+
}
|
|
21
|
+
function readSidecar(dbPath) {
|
|
22
|
+
const path = sidecarPath(dbPath);
|
|
23
|
+
if (!existsSync(path))
|
|
24
|
+
return null;
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function writeSidecar(dbPath, sidecar) {
|
|
33
|
+
writeFileSync(sidecarPath(dbPath), JSON.stringify(sidecar, null, 2), "utf-8");
|
|
34
|
+
}
|
|
35
|
+
function readMarker(storagePath) {
|
|
36
|
+
const markerPath = join(storagePath, MIGRATION_MARKER);
|
|
37
|
+
if (!existsSync(markerPath))
|
|
38
|
+
return null;
|
|
39
|
+
try {
|
|
40
|
+
const raw = readFileSync(markerPath, "utf-8");
|
|
41
|
+
const parsed = JSON.parse(raw);
|
|
42
|
+
if (parsed.completedAt && Array.isArray(parsed.shards)) {
|
|
43
|
+
return parsed;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function writeMarker(storagePath, shards) {
|
|
52
|
+
const marker = {
|
|
53
|
+
completedAt: new Date().toISOString(),
|
|
54
|
+
shards,
|
|
55
|
+
};
|
|
56
|
+
writeFileSync(join(storagePath, MIGRATION_MARKER), JSON.stringify(marker, null, 2), "utf-8");
|
|
57
|
+
}
|
|
58
|
+
function isProcessAlive(pid) {
|
|
59
|
+
try {
|
|
60
|
+
process.kill(pid, 0);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function sleep(ms) {
|
|
68
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
69
|
+
}
|
|
70
|
+
function acquireMigrationLock(storagePath) {
|
|
71
|
+
const lockPath = join(storagePath, MIGRATION_LOCK);
|
|
72
|
+
if (existsSync(lockPath)) {
|
|
73
|
+
try {
|
|
74
|
+
const lock = JSON.parse(readFileSync(lockPath, "utf-8"));
|
|
75
|
+
if (lock.pid && isProcessAlive(lock.pid)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
unlinkSync(lockPath);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
try {
|
|
82
|
+
unlinkSync(lockPath);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// stale or corrupt lock — try atomic create below
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const lock = { pid: process.pid, timestamp: new Date().toISOString() };
|
|
90
|
+
try {
|
|
91
|
+
writeFileSync(lockPath, JSON.stringify(lock), { flag: "wx" });
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function acquireMigrationLockWithRetry(storagePath, maxAttempts = 3) {
|
|
99
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
100
|
+
if (acquireMigrationLock(storagePath)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
await sleep(200 + attempt * 150);
|
|
104
|
+
}
|
|
105
|
+
throw new Error("Turso legacy migration locked by another process");
|
|
106
|
+
}
|
|
107
|
+
function releaseMigrationLock(storagePath) {
|
|
108
|
+
const lockPath = join(storagePath, MIGRATION_LOCK);
|
|
109
|
+
if (!existsSync(lockPath))
|
|
110
|
+
return;
|
|
111
|
+
try {
|
|
112
|
+
const lock = JSON.parse(readFileSync(lockPath, "utf-8"));
|
|
113
|
+
if (lock.pid === process.pid) {
|
|
114
|
+
unlinkSync(lockPath);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
unlinkSync(lockPath);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function countMemories(db) {
|
|
122
|
+
const row = await db.get(`SELECT COUNT(*) as count FROM memories`);
|
|
123
|
+
return Number(row?.count ?? 0);
|
|
124
|
+
}
|
|
125
|
+
async function hasMemoriesTable(db) {
|
|
126
|
+
const row = await db.get(`SELECT name FROM sqlite_master WHERE type='table' AND name='memories'`);
|
|
127
|
+
return Boolean(row);
|
|
128
|
+
}
|
|
129
|
+
async function isTursoVectorShardReady(db) {
|
|
130
|
+
const indexRow = await db.get(`SELECT name FROM sqlite_master WHERE type='index' AND name='memories_vec_idx'`);
|
|
131
|
+
if (!indexRow) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
const metaRow = await db.get(`SELECT value FROM shard_metadata WHERE key = 'embedding_dimensions'`);
|
|
135
|
+
if (!metaRow?.value) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
const storedDimensions = Number(metaRow.value);
|
|
139
|
+
if (!Number.isInteger(storedDimensions) || storedDimensions <= 0) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
const count = await countMemories(db);
|
|
143
|
+
if (count === 0) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const probe = await db.get(`SELECT vector_extract(vector) AS extracted FROM memories LIMIT 1`);
|
|
148
|
+
if (probe?.extracted == null)
|
|
149
|
+
return false;
|
|
150
|
+
const extracted = JSON.parse(String(probe.extracted));
|
|
151
|
+
return Array.isArray(extracted) && extracted.length === storedDimensions;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function isShardMigrationComplete(dbPath) {
|
|
158
|
+
const sidecar = readSidecar(dbPath);
|
|
159
|
+
if (!sidecar || sidecar.status !== "complete") {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
if (!existsSync(dbPath)) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
const db = await tursoConnectionManager.getConnection(dbPath);
|
|
167
|
+
if (!(await hasMemoriesTable(db))) {
|
|
168
|
+
return sidecar.expectedCount === 0;
|
|
169
|
+
}
|
|
170
|
+
const count = await countMemories(db);
|
|
171
|
+
if (count !== sidecar.expectedCount) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
// Sidecar alone is not enough — require native vector index + readable vectors.
|
|
175
|
+
return isTursoVectorShardReady(db);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function restoreFromBackup(dbPath) {
|
|
182
|
+
const backup = backupPath(dbPath);
|
|
183
|
+
if (!existsSync(backup))
|
|
184
|
+
return;
|
|
185
|
+
await tursoConnectionManager.closeConnection(dbPath);
|
|
186
|
+
if (existsSync(dbPath)) {
|
|
187
|
+
await withSqliteFileLockRetry(() => unlinkSync(dbPath));
|
|
188
|
+
}
|
|
189
|
+
await withSqliteFileLockRetry(() => renameSync(backup, dbPath));
|
|
190
|
+
const sidecarPathFile = sidecarPath(dbPath);
|
|
191
|
+
if (existsSync(sidecarPathFile)) {
|
|
192
|
+
unlinkSync(sidecarPathFile);
|
|
193
|
+
}
|
|
194
|
+
log("Legacy migration restored shard from backup", { dbPath, backup });
|
|
195
|
+
}
|
|
196
|
+
async function shouldRestoreFromBackup(dbPath) {
|
|
197
|
+
const backup = backupPath(dbPath);
|
|
198
|
+
if (!existsSync(backup))
|
|
199
|
+
return false;
|
|
200
|
+
if (!existsSync(dbPath)) {
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const db = await tursoConnectionManager.getConnection(dbPath);
|
|
205
|
+
const sidecar = readSidecar(dbPath);
|
|
206
|
+
// A healthy Turso vector shard must not be overwritten by .legacy.bak,
|
|
207
|
+
// except when a pending rewrite clearly did not finish importing all rows.
|
|
208
|
+
if ((await hasMemoriesTable(db)) && (await isTursoVectorShardReady(db))) {
|
|
209
|
+
if (sidecar?.status === "pending") {
|
|
210
|
+
const count = await countMemories(db);
|
|
211
|
+
if (count < sidecar.expectedCount) {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
if (sidecar?.status === "complete") {
|
|
218
|
+
return !(await isShardMigrationComplete(dbPath));
|
|
219
|
+
}
|
|
220
|
+
if (!(await hasMemoriesTable(db))) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
const count = await countMemories(db);
|
|
224
|
+
const expected = sidecar?.expectedCount ?? null;
|
|
225
|
+
if (expected != null && count < expected) {
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
if (sidecar?.status === "pending") {
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
function rowToRecord(row) {
|
|
238
|
+
const vector = blobToFloat32Array(row.vector);
|
|
239
|
+
if (!vector)
|
|
240
|
+
return null;
|
|
241
|
+
const tagsVector = blobToFloat32Array(row.tags_vector) ?? undefined;
|
|
242
|
+
return {
|
|
243
|
+
id: String(row.id),
|
|
244
|
+
content: String(row.content),
|
|
245
|
+
vector,
|
|
246
|
+
tagsVector,
|
|
247
|
+
containerTag: String(row.container_tag),
|
|
248
|
+
tags: row.tags ? String(row.tags) : undefined,
|
|
249
|
+
type: row.type ? String(row.type) : undefined,
|
|
250
|
+
createdAt: Number(row.created_at),
|
|
251
|
+
updatedAt: Number(row.updated_at),
|
|
252
|
+
metadata: row.metadata ? String(row.metadata) : undefined,
|
|
253
|
+
displayName: row.display_name ? String(row.display_name) : undefined,
|
|
254
|
+
userName: row.user_name ? String(row.user_name) : undefined,
|
|
255
|
+
userEmail: row.user_email ? String(row.user_email) : undefined,
|
|
256
|
+
projectPath: row.project_path ? String(row.project_path) : undefined,
|
|
257
|
+
projectName: row.project_name ? String(row.project_name) : undefined,
|
|
258
|
+
gitRepoUrl: row.git_repo_url ? String(row.git_repo_url) : undefined,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
async function migrateMemoryShard(dbPath) {
|
|
262
|
+
if (await isShardMigrationComplete(dbPath)) {
|
|
263
|
+
const sidecar = readSidecar(dbPath);
|
|
264
|
+
return sidecar;
|
|
265
|
+
}
|
|
266
|
+
if (await shouldRestoreFromBackup(dbPath)) {
|
|
267
|
+
await restoreFromBackup(dbPath);
|
|
268
|
+
}
|
|
269
|
+
if (await isShardMigrationComplete(dbPath)) {
|
|
270
|
+
return readSidecar(dbPath);
|
|
271
|
+
}
|
|
272
|
+
const db = await tursoConnectionManager.getConnection(dbPath);
|
|
273
|
+
const hasTable = await hasMemoriesTable(db);
|
|
274
|
+
if (!hasTable) {
|
|
275
|
+
await tursoShardManager.initShardDb(db);
|
|
276
|
+
const sidecar = {
|
|
277
|
+
sourceCount: 0,
|
|
278
|
+
expectedCount: 0,
|
|
279
|
+
importedCount: 0,
|
|
280
|
+
skippedCount: 0,
|
|
281
|
+
status: "complete",
|
|
282
|
+
};
|
|
283
|
+
writeSidecar(dbPath, sidecar);
|
|
284
|
+
return sidecar;
|
|
285
|
+
}
|
|
286
|
+
if (await isTursoVectorShardReady(db)) {
|
|
287
|
+
const count = await countMemories(db);
|
|
288
|
+
const sidecar = {
|
|
289
|
+
sourceCount: count,
|
|
290
|
+
expectedCount: count,
|
|
291
|
+
importedCount: count,
|
|
292
|
+
skippedCount: 0,
|
|
293
|
+
status: "complete",
|
|
294
|
+
};
|
|
295
|
+
writeSidecar(dbPath, sidecar);
|
|
296
|
+
return sidecar;
|
|
297
|
+
}
|
|
298
|
+
const rows = await db.all(`SELECT * FROM memories`);
|
|
299
|
+
const records = [];
|
|
300
|
+
const pinnedIds = [];
|
|
301
|
+
const skippedIds = [];
|
|
302
|
+
for (const row of rows) {
|
|
303
|
+
const record = rowToRecord(row);
|
|
304
|
+
if (!record) {
|
|
305
|
+
skippedIds.push(String(row.id));
|
|
306
|
+
log("Legacy migration skipped memory without vector", { memoryId: row.id, dbPath });
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
records.push(record);
|
|
310
|
+
if (Number(row.is_pinned) === 1) {
|
|
311
|
+
pinnedIds.push(record.id);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const sourceCount = rows.length;
|
|
315
|
+
const skippedCount = skippedIds.length;
|
|
316
|
+
if (skippedCount > 0) {
|
|
317
|
+
throw new Error(`Legacy migration aborted for ${dbPath}: ${skippedCount} of ${sourceCount} memories have unreadable vectors (${skippedIds.join(", ")})`);
|
|
318
|
+
}
|
|
319
|
+
const sourceDimensions = records[0]?.vector.length ?? CONFIG.embeddingDimensions;
|
|
320
|
+
if (!Number.isInteger(sourceDimensions) || sourceDimensions <= 0 || sourceDimensions > 65536) {
|
|
321
|
+
throw new Error(`Legacy migration aborted for ${dbPath}: invalid source vector dimensions ${sourceDimensions}`);
|
|
322
|
+
}
|
|
323
|
+
const dimensionMismatches = records.filter((record) => record.vector.length !== sourceDimensions ||
|
|
324
|
+
(record.tagsVector != null && record.tagsVector.length !== sourceDimensions));
|
|
325
|
+
if (dimensionMismatches.length > 0) {
|
|
326
|
+
const sample = dimensionMismatches
|
|
327
|
+
.slice(0, 5)
|
|
328
|
+
.map((record) => `${record.id}(vector=${record.vector.length}` +
|
|
329
|
+
(record.tagsVector ? `,tags=${record.tagsVector.length}` : "") +
|
|
330
|
+
")")
|
|
331
|
+
.join(", ");
|
|
332
|
+
throw new Error(`Legacy migration aborted for ${dbPath}: ${dimensionMismatches.length} memories have inconsistent vector dimensions; expected ${sourceDimensions} (${sample})`);
|
|
333
|
+
}
|
|
334
|
+
const expectedCount = records.length;
|
|
335
|
+
writeSidecar(dbPath, {
|
|
336
|
+
sourceCount,
|
|
337
|
+
expectedCount,
|
|
338
|
+
importedCount: 0,
|
|
339
|
+
skippedCount,
|
|
340
|
+
status: "pending",
|
|
341
|
+
});
|
|
342
|
+
const backup = backupPath(dbPath);
|
|
343
|
+
await tursoConnectionManager.closeConnection(dbPath);
|
|
344
|
+
if (existsSync(dbPath)) {
|
|
345
|
+
await withSqliteFileLockRetry(() => renameSync(dbPath, backup));
|
|
346
|
+
}
|
|
347
|
+
const freshDb = await tursoConnectionManager.getConnection(dbPath);
|
|
348
|
+
const sourceModel = sourceDimensions === CONFIG.embeddingDimensions ? CONFIG.embeddingModel : "legacy-unknown";
|
|
349
|
+
await tursoShardManager.initShardDb(freshDb, sourceDimensions, sourceModel);
|
|
350
|
+
const BATCH_SIZE = 50;
|
|
351
|
+
await freshDb.transaction("write", async (tx) => {
|
|
352
|
+
for (let offset = 0; offset < records.length; offset += BATCH_SIZE) {
|
|
353
|
+
const batch = records.slice(offset, offset + BATCH_SIZE);
|
|
354
|
+
for (const record of batch) {
|
|
355
|
+
await tursoVectorSearch.insertVectorInTransaction(tx, record);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
for (const memoryId of pinnedIds) {
|
|
359
|
+
await tx.execute({
|
|
360
|
+
sql: `UPDATE memories SET is_pinned = 1 WHERE id = ?`,
|
|
361
|
+
args: [memoryId],
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
const importedCount = await countMemories(freshDb);
|
|
366
|
+
if (importedCount !== expectedCount) {
|
|
367
|
+
throw new Error(`Legacy migration count mismatch for ${dbPath}: expected ${expectedCount}, got ${importedCount}`);
|
|
368
|
+
}
|
|
369
|
+
const completedSidecar = {
|
|
370
|
+
sourceCount,
|
|
371
|
+
expectedCount,
|
|
372
|
+
importedCount,
|
|
373
|
+
skippedCount,
|
|
374
|
+
status: "complete",
|
|
375
|
+
};
|
|
376
|
+
writeSidecar(dbPath, completedSidecar);
|
|
377
|
+
log("Legacy memory shard migrated to Turso vectors", {
|
|
378
|
+
dbPath,
|
|
379
|
+
memories: expectedCount,
|
|
380
|
+
backupPath: backup,
|
|
381
|
+
});
|
|
382
|
+
return completedSidecar;
|
|
383
|
+
}
|
|
384
|
+
async function migrateDirectory(dirName) {
|
|
385
|
+
const dir = join(CONFIG.storagePath, dirName);
|
|
386
|
+
const results = [];
|
|
387
|
+
if (!existsSync(dir))
|
|
388
|
+
return results;
|
|
389
|
+
for (const file of readdirSync(dir)) {
|
|
390
|
+
if (!file.endsWith(".db") || file.includes(BACKUP_SUFFIX))
|
|
391
|
+
continue;
|
|
392
|
+
const dbPath = join(dir, file);
|
|
393
|
+
const sidecar = await migrateMemoryShard(dbPath);
|
|
394
|
+
if (sidecar) {
|
|
395
|
+
results.push({ path: dbPath, sidecar });
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return results;
|
|
399
|
+
}
|
|
400
|
+
function listAllShardDbPaths() {
|
|
401
|
+
const paths = [];
|
|
402
|
+
for (const dirName of ["users", "projects"]) {
|
|
403
|
+
const dir = join(CONFIG.storagePath, dirName);
|
|
404
|
+
if (!existsSync(dir))
|
|
405
|
+
continue;
|
|
406
|
+
for (const file of readdirSync(dir)) {
|
|
407
|
+
if (!file.endsWith(".db") || file.includes(BACKUP_SUFFIX))
|
|
408
|
+
continue;
|
|
409
|
+
paths.push(join(dir, file));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return paths;
|
|
413
|
+
}
|
|
414
|
+
async function allShardsComplete() {
|
|
415
|
+
const paths = listAllShardDbPaths();
|
|
416
|
+
if (paths.length === 0)
|
|
417
|
+
return true;
|
|
418
|
+
for (const dbPath of paths) {
|
|
419
|
+
if (!(await isShardMigrationComplete(dbPath))) {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
function recoverInterruptedReembedSwaps() {
|
|
426
|
+
for (const dirName of ["users", "projects"]) {
|
|
427
|
+
const dir = join(CONFIG.storagePath, dirName);
|
|
428
|
+
if (!existsSync(dir))
|
|
429
|
+
continue;
|
|
430
|
+
for (const file of readdirSync(dir)) {
|
|
431
|
+
if (!file.endsWith(REEMBED_SWAP_SUFFIX))
|
|
432
|
+
continue;
|
|
433
|
+
const statePath = join(dir, file);
|
|
434
|
+
const expectedDbPath = statePath.slice(0, -REEMBED_SWAP_SUFFIX.length);
|
|
435
|
+
try {
|
|
436
|
+
const state = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
437
|
+
const stagedPath = state.stagedPath;
|
|
438
|
+
const backupPath = state.backupPath;
|
|
439
|
+
const valid = state.dbPath === expectedDbPath &&
|
|
440
|
+
typeof stagedPath === "string" &&
|
|
441
|
+
stagedPath.startsWith(`${expectedDbPath}.reembed-`) &&
|
|
442
|
+
stagedPath.endsWith(".tmp") &&
|
|
443
|
+
typeof backupPath === "string" &&
|
|
444
|
+
backupPath.startsWith(`${expectedDbPath}.pre-reembed-`) &&
|
|
445
|
+
backupPath.endsWith(".bak");
|
|
446
|
+
if (!valid) {
|
|
447
|
+
throw new Error("invalid re-embed swap state paths");
|
|
448
|
+
}
|
|
449
|
+
if (existsSync(expectedDbPath)) {
|
|
450
|
+
if (existsSync(stagedPath))
|
|
451
|
+
unlinkSync(stagedPath);
|
|
452
|
+
}
|
|
453
|
+
else if (existsSync(stagedPath)) {
|
|
454
|
+
renameSync(stagedPath, expectedDbPath);
|
|
455
|
+
}
|
|
456
|
+
else if (existsSync(backupPath)) {
|
|
457
|
+
renameSync(backupPath, expectedDbPath);
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
throw new Error("neither staged replacement nor source backup exists");
|
|
461
|
+
}
|
|
462
|
+
unlinkSync(statePath);
|
|
463
|
+
log("Recovered interrupted re-embed shard swap", { dbPath: expectedDbPath });
|
|
464
|
+
}
|
|
465
|
+
catch (error) {
|
|
466
|
+
throw new Error(`Failed to recover re-embed swap ${statePath}: ${String(error)}`, {
|
|
467
|
+
cause: error,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
async function reconcileShardRegistry() {
|
|
474
|
+
const parsedShards = [];
|
|
475
|
+
for (const path of listAllShardDbPaths()) {
|
|
476
|
+
const match = /^(user|project)_([a-f0-9]{16})_shard_(\d+)\.db$/.exec(basename(path));
|
|
477
|
+
if (!match) {
|
|
478
|
+
log("Legacy migration left unrecognized shard filename unregistered", { path });
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
const db = await tursoConnectionManager.getConnection(path);
|
|
482
|
+
parsedShards.push({
|
|
483
|
+
path,
|
|
484
|
+
scope: match[1],
|
|
485
|
+
scopeHash: match[2],
|
|
486
|
+
shardIndex: Number(match[3]),
|
|
487
|
+
vectorCount: await countMemories(db),
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
const highestIndexByScope = new Map();
|
|
491
|
+
for (const shard of parsedShards) {
|
|
492
|
+
const key = `${shard.scope}:${shard.scopeHash}`;
|
|
493
|
+
highestIndexByScope.set(key, Math.max(highestIndexByScope.get(key) ?? -1, shard.shardIndex));
|
|
494
|
+
}
|
|
495
|
+
for (const shard of parsedShards) {
|
|
496
|
+
const key = `${shard.scope}:${shard.scopeHash}`;
|
|
497
|
+
await tursoShardManager.registerExistingShard(shard.scope, shard.scopeHash, shard.shardIndex, shard.path, shard.vectorCount, shard.shardIndex === highestIndexByScope.get(key));
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
export async function runLegacyTursoMigration() {
|
|
501
|
+
if (!existsSync(CONFIG.storagePath)) {
|
|
502
|
+
mkdirSync(CONFIG.storagePath, { recursive: true });
|
|
503
|
+
}
|
|
504
|
+
recoverInterruptedReembedSwaps();
|
|
505
|
+
const marker = readMarker(CONFIG.storagePath);
|
|
506
|
+
if (marker && (await allShardsComplete())) {
|
|
507
|
+
await reconcileShardRegistry();
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
await acquireMigrationLockWithRetry(CONFIG.storagePath);
|
|
511
|
+
try {
|
|
512
|
+
const dbFiles = ["metadata.db", "user-prompts.db", "user-profiles.db", "ai-sessions.db"];
|
|
513
|
+
for (const file of dbFiles) {
|
|
514
|
+
const path = join(CONFIG.storagePath, file);
|
|
515
|
+
if (!existsSync(path))
|
|
516
|
+
continue;
|
|
517
|
+
await tursoConnectionManager.getConnection(path);
|
|
518
|
+
}
|
|
519
|
+
const migratedShards = [];
|
|
520
|
+
for (const dirName of ["users", "projects"]) {
|
|
521
|
+
const dirResults = await migrateDirectory(dirName);
|
|
522
|
+
for (const { path, sidecar } of dirResults) {
|
|
523
|
+
migratedShards.push({
|
|
524
|
+
path,
|
|
525
|
+
expectedCount: sidecar.expectedCount,
|
|
526
|
+
importedCount: sidecar.importedCount,
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (!(await allShardsComplete())) {
|
|
531
|
+
throw new Error("Turso legacy migration incomplete: not all shards verified");
|
|
532
|
+
}
|
|
533
|
+
await reconcileShardRegistry();
|
|
534
|
+
writeMarker(CONFIG.storagePath, migratedShards);
|
|
535
|
+
log("Turso legacy migration complete", { storagePath: CONFIG.storagePath });
|
|
536
|
+
}
|
|
537
|
+
finally {
|
|
538
|
+
releaseMigrationLock(CONFIG.storagePath);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function closeTursoAndInvalidateCaches(): Promise<void>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { tursoConnectionManager } from "./connection-manager.js";
|
|
2
|
+
import { tursoShardManager } from "./shard-manager.js";
|
|
3
|
+
import { resetTursoReady } from "./ready.js";
|
|
4
|
+
import { userPromptManager } from "../user-prompt/user-prompt-manager.js";
|
|
5
|
+
import { userProfileManager } from "../user-profile/user-profile-manager.js";
|
|
6
|
+
import { aiSessionManager } from "../ai/session/ai-session-manager.js";
|
|
7
|
+
export async function closeTursoAndInvalidateCaches() {
|
|
8
|
+
await tursoConnectionManager.closeAll();
|
|
9
|
+
tursoShardManager.reset();
|
|
10
|
+
resetTursoReady();
|
|
11
|
+
userPromptManager.reset();
|
|
12
|
+
userProfileManager.reset();
|
|
13
|
+
aiSessionManager.reset();
|
|
14
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { CONFIG } from "../../config.js";
|
|
4
|
+
const OPERATION_LOCK = ".turso-operation.lock";
|
|
5
|
+
const LEGACY_MIGRATION_LOCK = ".turso-migrate.lock";
|
|
6
|
+
function isProcessAlive(pid) {
|
|
7
|
+
try {
|
|
8
|
+
process.kill(pid, 0);
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function readLiveLock(path) {
|
|
16
|
+
if (!existsSync(path))
|
|
17
|
+
return null;
|
|
18
|
+
try {
|
|
19
|
+
const state = JSON.parse(readFileSync(path, "utf-8"));
|
|
20
|
+
if (Number.isInteger(state.pid) && state.pid > 0 && isProcessAlive(state.pid)) {
|
|
21
|
+
return state;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Corrupt locks are stale and removed below.
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
unlinkSync(path);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Already removed (race) or held open (Windows) — either way the lock is
|
|
32
|
+
// gone or unowned; do not let cleanup failure block writes.
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
export function assertNoTursoMigrationInProgress() {
|
|
37
|
+
for (const file of [OPERATION_LOCK, LEGACY_MIGRATION_LOCK]) {
|
|
38
|
+
const path = join(CONFIG.storagePath, file);
|
|
39
|
+
const state = readLiveLock(path);
|
|
40
|
+
if (state) {
|
|
41
|
+
throw new Error(`Database migration is in progress${state.operation ? ` (${state.operation})` : ""} ` +
|
|
42
|
+
`in process ${state.pid}; writes are temporarily blocked`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function acquireTursoOperationLock(operation) {
|
|
47
|
+
assertNoTursoMigrationInProgress();
|
|
48
|
+
const path = join(CONFIG.storagePath, OPERATION_LOCK);
|
|
49
|
+
const state = {
|
|
50
|
+
pid: process.pid,
|
|
51
|
+
timestamp: new Date().toISOString(),
|
|
52
|
+
operation,
|
|
53
|
+
};
|
|
54
|
+
writeFileSync(path, JSON.stringify(state), { flag: "wx" });
|
|
55
|
+
return () => {
|
|
56
|
+
const current = readLiveLock(path);
|
|
57
|
+
if (current?.pid === process.pid && existsSync(path)) {
|
|
58
|
+
unlinkSync(path);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { runLegacyTursoMigration } from "./legacy-migrator.js";
|
|
2
|
+
import { runStartupTagPrefixGate } from "../tag-prefix-migration.js";
|
|
3
|
+
import { tursoShardManager } from "./shard-manager.js";
|
|
4
|
+
import { log } from "../logger.js";
|
|
5
|
+
let initPromise = null;
|
|
6
|
+
let isReady = false;
|
|
7
|
+
export async function ensureTursoReady() {
|
|
8
|
+
if (isReady)
|
|
9
|
+
return;
|
|
10
|
+
if (initPromise)
|
|
11
|
+
return initPromise;
|
|
12
|
+
initPromise = (async () => {
|
|
13
|
+
try {
|
|
14
|
+
await runLegacyTursoMigration();
|
|
15
|
+
// Gate every read/write on the one-time container tag prefix migration:
|
|
16
|
+
// either the store is fully on omms_, or initialisation fails loudly
|
|
17
|
+
// (no silent dual-prefix fallback). Skipped under
|
|
18
|
+
// OMMS_SKIP_TAG_PREFIX_MIGRATION=1 (tests).
|
|
19
|
+
await runStartupTagPrefixGate();
|
|
20
|
+
const { shardPathMigrationService } = await import("../shard-path-migration-service.js");
|
|
21
|
+
await shardPathMigrationService.recoverInterruptedSwap();
|
|
22
|
+
await tursoShardManager.getAllShards("user", "");
|
|
23
|
+
isReady = true;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
initPromise = null;
|
|
27
|
+
log("Turso ready gate failed", { error: String(error) });
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
})();
|
|
31
|
+
return initPromise;
|
|
32
|
+
}
|
|
33
|
+
export function resetTursoReady() {
|
|
34
|
+
isReady = false;
|
|
35
|
+
initPromise = null;
|
|
36
|
+
}
|