om-memory-system 5.5.0 → 5.6.0-next.216
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/README.md +1 -1
- package/dist/adapters/pi/extension.js +5 -0
- package/dist/cli/space-command.d.ts +6 -5
- package/dist/cli/space-command.js +154 -13
- package/dist/cli/space-transfer-command.d.ts +25 -0
- package/dist/cli/space-transfer-command.js +79 -0
- package/dist/core/memory-operations.d.ts +12 -2
- package/dist/core/memory-operations.js +14 -4
- package/dist/core/recall-summary.js +7 -5
- package/dist/core/retrieval.js +5 -2
- package/dist/core/space-tool.d.ts +9 -4
- package/dist/core/space-tool.js +35 -3
- package/dist/services/active-space-access.d.ts +6 -0
- package/dist/services/active-space-access.js +8 -0
- package/dist/services/chat/chat-runtime.js +1 -1
- package/dist/services/client.d.ts +6 -16
- package/dist/services/client.js +54 -62
- package/dist/services/context.d.ts +2 -0
- package/dist/services/context.js +12 -1
- package/dist/services/linked-search.d.ts +108 -0
- package/dist/services/linked-search.js +219 -0
- package/dist/services/space-routes.d.ts +2 -0
- package/dist/services/space-routes.js +4 -3
- package/dist/services/space-transfer-routes.d.ts +10 -0
- package/dist/services/space-transfer-routes.js +344 -0
- package/dist/services/spaces/active-space.js +14 -7
- package/dist/services/spaces/delete.d.ts +2 -0
- package/dist/services/spaces/delete.js +4 -0
- package/dist/services/spaces/export-space.d.ts +60 -0
- package/dist/services/spaces/export-space.js +367 -0
- package/dist/services/spaces/identifier-scrub.d.ts +26 -0
- package/dist/services/spaces/identifier-scrub.js +40 -0
- package/dist/services/spaces/import-space.d.ts +85 -0
- package/dist/services/spaces/import-space.js +349 -0
- package/dist/services/spaces/operations.d.ts +57 -5
- package/dist/services/spaces/operations.js +161 -5
- package/dist/services/spaces/read-store-cache.d.ts +13 -0
- package/dist/services/spaces/read-store-cache.js +34 -0
- package/dist/services/spaces/registry-store.d.ts +4 -0
- package/dist/services/spaces/registry-store.js +15 -1
- package/dist/services/spaces/registry.d.ts +32 -0
- package/dist/services/spaces/registry.js +81 -7
- package/dist/services/spaces/space-store.d.ts +50 -0
- package/dist/services/spaces/space-store.js +72 -0
- package/dist/services/spaces/transfer-service.d.ts +41 -0
- package/dist/services/spaces/transfer-service.js +408 -0
- package/dist/services/spaces/transfer-types.d.ts +163 -0
- package/dist/services/spaces/transfer-types.js +10 -0
- package/dist/services/spaces/transfer.d.ts +31 -0
- package/dist/services/spaces/transfer.js +276 -0
- package/dist/services/turso/connection-manager.d.ts +4 -2
- package/dist/services/turso/connection-manager.js +11 -5
- package/dist/services/turso/cross-process-write-lock.d.ts +1 -1
- package/dist/services/turso/cross-process-write-lock.js +5 -5
- package/dist/services/turso/operation-lock.d.ts +2 -2
- package/dist/services/turso/operation-lock.js +5 -5
- package/dist/services/turso/shard-manager.d.ts +8 -0
- package/dist/services/turso/shard-manager.js +23 -22
- package/dist/services/turso/vector-search.d.ts +6 -2
- package/dist/services/turso/vector-search.js +8 -4
- package/dist/services/user-profile/user-profile-manager.d.ts +5 -1
- package/dist/services/user-profile/user-profile-manager.js +13 -10
- package/dist/services/web-route-modules.d.ts +5 -1
- package/dist/services/web-route-modules.js +4 -0
- package/dist/services/web-server.js +1 -1
- package/dist/shared/api/space-export-schemas.d.ts +159 -0
- package/dist/shared/api/space-export-schemas.js +72 -0
- package/dist/v2/adapter.d.ts +15 -0
- package/dist/v2/adapter.js +5 -0
- package/dist/web/assets/index-2BNRLGqe.css +2 -0
- package/dist/web/assets/index-C6WnDE6h.js +170 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/skills/omms-memory/SKILL.md +16 -14
- package/dist/web/assets/index-Dcvz3EXt.js +0 -170
- package/dist/web/assets/index-b8k5Tuei.css +0 -2
|
@@ -30,3 +30,7 @@ export declare const unarchiveStoredSpace: (files: SpaceFiles, idOrName: string)
|
|
|
30
30
|
export declare const switchStoredSpace: (files: SpaceFiles, idOrName: string) => Promise<SpaceRegistry>;
|
|
31
31
|
export declare const removeStoredSpace: (files: SpaceFiles, idOrName: string) => Promise<SpaceRegistry>;
|
|
32
32
|
export declare function renameStoredSpace(files: SpaceFiles, idOrName: string, name: string): Promise<SpaceRegistry>;
|
|
33
|
+
/** Adds a link between two stored spaces under the lock (design D5). */
|
|
34
|
+
export declare function linkStoredSpace(files: SpaceFiles, idOrName: string, targetIdOrName: string): Promise<SpaceRegistry>;
|
|
35
|
+
/** Removes a link between two stored spaces under the lock (design D5). */
|
|
36
|
+
export declare function unlinkStoredSpace(files: SpaceFiles, idOrName: string, targetIdOrName: string): Promise<SpaceRegistry>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
-
import { SpaceError, archiveSpace, createSpace, readRegistryFile, removeSpace, renameSpace, switchSpace, unarchiveSpace, } from "./registry.js";
|
|
3
|
+
import { SpaceError, archiveSpace, createSpace, linkSpace, readRegistryFile, removeSpace, renameSpace, switchSpace, unarchiveSpace, unlinkSpace, } from "./registry.js";
|
|
4
4
|
/**
|
|
5
5
|
* The one write path for `spaces.json` (design D5). The web server, the CLI and the
|
|
6
6
|
* `memory` tool all change the registry through `updateRegistry`. Readers never lock.
|
|
@@ -132,3 +132,17 @@ export async function renameStoredSpace(files, idOrName, name) {
|
|
|
132
132
|
result: 0,
|
|
133
133
|
}))).registry;
|
|
134
134
|
}
|
|
135
|
+
/** Adds a link between two stored spaces under the lock (design D5). */
|
|
136
|
+
export async function linkStoredSpace(files, idOrName, targetIdOrName) {
|
|
137
|
+
return (await updateRegistry(files, (registry) => ({
|
|
138
|
+
registry: linkSpace(registry, idOrName, targetIdOrName),
|
|
139
|
+
result: 0,
|
|
140
|
+
}))).registry;
|
|
141
|
+
}
|
|
142
|
+
/** Removes a link between two stored spaces under the lock (design D5). */
|
|
143
|
+
export async function unlinkStoredSpace(files, idOrName, targetIdOrName) {
|
|
144
|
+
return (await updateRegistry(files, (registry) => ({
|
|
145
|
+
registry: unlinkSpace(registry, idOrName, targetIdOrName),
|
|
146
|
+
result: 0,
|
|
147
|
+
}))).registry;
|
|
148
|
+
}
|
|
@@ -17,10 +17,14 @@ export interface SpaceEntry {
|
|
|
17
17
|
archived: boolean;
|
|
18
18
|
/** ISO 8601 time of the archive. Absent for a space archived before OMMS recorded it. */
|
|
19
19
|
archivedAt?: string;
|
|
20
|
+
/** Ids of linked spaces (design D5). A link is one-way and read-only. */
|
|
21
|
+
links?: string[];
|
|
20
22
|
}
|
|
21
23
|
export interface SpaceRegistry {
|
|
22
24
|
version: number;
|
|
23
25
|
activeSpaceId: string;
|
|
26
|
+
/** Custom display name of the Default space. Absent means "Default". */
|
|
27
|
+
defaultName?: string;
|
|
24
28
|
/** User-created spaces only. Default is added by `listSpaces`. */
|
|
25
29
|
spaces: SpaceEntry[];
|
|
26
30
|
}
|
|
@@ -56,6 +60,34 @@ export declare function unarchiveSpace(registry: SpaceRegistry, idOrName: string
|
|
|
56
60
|
export declare function switchSpace(registry: SpaceRegistry, idOrName: string): SpaceRegistry;
|
|
57
61
|
/** Removes an entry. The caller checks backup and folder removal (D8). */
|
|
58
62
|
export declare function removeSpace(registry: SpaceRegistry, idOrName: string): SpaceRegistry;
|
|
63
|
+
/**
|
|
64
|
+
* Adds a one-way link from one space to another (design D5). The linked space must
|
|
65
|
+
* exist, must not be the linking space itself and must not repeat. The link is
|
|
66
|
+
* stored on the linking space only, and the linked space's own links are not
|
|
67
|
+
* copied: the registry holds one level, and search follows no further.
|
|
68
|
+
*
|
|
69
|
+
* The Default space holds no links because it has no entry in `spaces.json`.
|
|
70
|
+
*/
|
|
71
|
+
export declare function linkSpace(registry: SpaceRegistry, idOrName: string, targetIdOrName: string): SpaceRegistry;
|
|
72
|
+
/**
|
|
73
|
+
* Removes a link. The linked space may be gone from the registry, so its id is
|
|
74
|
+
* matched by id or name when it still exists and as a raw id otherwise. Removing
|
|
75
|
+
* a link deletes no memory and leaves every other link alone.
|
|
76
|
+
*/
|
|
77
|
+
export declare function unlinkSpace(registry: SpaceRegistry, idOrName: string, targetIdOrName: string): SpaceRegistry;
|
|
78
|
+
export interface SpaceLinkInfo {
|
|
79
|
+
id: string;
|
|
80
|
+
/** The display name, or the id when the linked space is gone from the registry. */
|
|
81
|
+
name: string;
|
|
82
|
+
/** False when the linked space was deleted or archived. The link itself stays. */
|
|
83
|
+
available: boolean;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The links of a space with their availability, for the read paths (web layer, CLI
|
|
87
|
+
* listing). A link to a deleted or archived space stays in the registry and reads
|
|
88
|
+
* as unavailable here; search-time code skips it with a warning.
|
|
89
|
+
*/
|
|
90
|
+
export declare function spaceLinks(registry: SpaceRegistry, idOrName: string): SpaceLinkInfo[];
|
|
59
91
|
/** The folder of a space. Refuses an id that is not safe, so a path cannot leave the spaces root. */
|
|
60
92
|
export declare function resolveSpaceFolder(id: string, paths: SpacePaths): string;
|
|
61
93
|
export interface RegistryReadResult {
|
|
@@ -24,12 +24,17 @@ export class SpaceError extends Error {
|
|
|
24
24
|
export function emptyRegistry() {
|
|
25
25
|
return { version: REGISTRY_VERSION, activeSpaceId: DEFAULT_SPACE_ID, spaces: [] };
|
|
26
26
|
}
|
|
27
|
-
function defaultEntry() {
|
|
28
|
-
return {
|
|
27
|
+
function defaultEntry(registry) {
|
|
28
|
+
return {
|
|
29
|
+
id: DEFAULT_SPACE_ID,
|
|
30
|
+
name: registry.defaultName ?? DEFAULT_SPACE_NAME,
|
|
31
|
+
createdAt: "",
|
|
32
|
+
archived: false,
|
|
33
|
+
};
|
|
29
34
|
}
|
|
30
35
|
/** Every space in display order: Default first, then user spaces in creation order. */
|
|
31
36
|
export function listSpaces(registry) {
|
|
32
|
-
return [defaultEntry(), ...registry.spaces];
|
|
37
|
+
return [defaultEntry(registry), ...registry.spaces];
|
|
33
38
|
}
|
|
34
39
|
/** Lowercase letters, digits and single hyphens, at most 40 characters. Empty when the name has none. */
|
|
35
40
|
export function spaceIdFromName(name) {
|
|
@@ -103,14 +108,19 @@ function updateSpace(registry, id, change) {
|
|
|
103
108
|
}
|
|
104
109
|
export function renameSpace(registry, idOrName, name) {
|
|
105
110
|
const space = requireSpace(registry, idOrName);
|
|
106
|
-
if (space.id === DEFAULT_SPACE_ID) {
|
|
107
|
-
throw new SpaceError("protected", "The Default space cannot be renamed.");
|
|
108
|
-
}
|
|
109
111
|
const display = checkedName(name);
|
|
110
112
|
const clash = findByName(registry, display);
|
|
111
113
|
if (clash && clash.id !== space.id) {
|
|
112
114
|
throw new SpaceError("name-taken", `A space named "${clash.name}" already exists.`);
|
|
113
115
|
}
|
|
116
|
+
if (space.id === DEFAULT_SPACE_ID) {
|
|
117
|
+
if (display.toLowerCase() === DEFAULT_SPACE_NAME.toLowerCase()) {
|
|
118
|
+
// "Default" means no custom name: the field is removed, not stored (design D2).
|
|
119
|
+
const { defaultName: _drop, ...rest } = registry;
|
|
120
|
+
return rest;
|
|
121
|
+
}
|
|
122
|
+
return { ...registry, defaultName: display };
|
|
123
|
+
}
|
|
114
124
|
return updateSpace(registry, space.id, { name: display });
|
|
115
125
|
}
|
|
116
126
|
export function archiveSpace(registry, idOrName, now = new Date().toISOString()) {
|
|
@@ -148,6 +158,62 @@ export function removeSpace(registry, idOrName) {
|
|
|
148
158
|
}
|
|
149
159
|
return { ...registry, spaces: registry.spaces.filter((s) => s.id !== space.id) };
|
|
150
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Adds a one-way link from one space to another (design D5). The linked space must
|
|
163
|
+
* exist, must not be the linking space itself and must not repeat. The link is
|
|
164
|
+
* stored on the linking space only, and the linked space's own links are not
|
|
165
|
+
* copied: the registry holds one level, and search follows no further.
|
|
166
|
+
*
|
|
167
|
+
* The Default space holds no links because it has no entry in `spaces.json`.
|
|
168
|
+
*/
|
|
169
|
+
export function linkSpace(registry, idOrName, targetIdOrName) {
|
|
170
|
+
const space = requireSpace(registry, idOrName);
|
|
171
|
+
if (space.id === DEFAULT_SPACE_ID) {
|
|
172
|
+
throw new SpaceError("protected", "The Default space cannot link other spaces.");
|
|
173
|
+
}
|
|
174
|
+
const target = requireSpace(registry, targetIdOrName);
|
|
175
|
+
if (target.id === space.id) {
|
|
176
|
+
// Code "protected" follows the "cannot do this to itself/Default" refusals.
|
|
177
|
+
throw new SpaceError("protected", `"${space.name}" cannot link to itself.`);
|
|
178
|
+
}
|
|
179
|
+
const links = space.links ?? [];
|
|
180
|
+
if (links.includes(target.id)) {
|
|
181
|
+
// Code "name-taken" is the duplicate refusal; a repeated link is the same conflict.
|
|
182
|
+
throw new SpaceError("name-taken", `"${space.name}" already links "${target.name}".`);
|
|
183
|
+
}
|
|
184
|
+
return updateSpace(registry, space.id, { links: [...links, target.id] });
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Removes a link. The linked space may be gone from the registry, so its id is
|
|
188
|
+
* matched by id or name when it still exists and as a raw id otherwise. Removing
|
|
189
|
+
* a link deletes no memory and leaves every other link alone.
|
|
190
|
+
*/
|
|
191
|
+
export function unlinkSpace(registry, idOrName, targetIdOrName) {
|
|
192
|
+
const space = requireSpace(registry, idOrName);
|
|
193
|
+
if (space.id === DEFAULT_SPACE_ID) {
|
|
194
|
+
throw new SpaceError("protected", "The Default space cannot link other spaces.");
|
|
195
|
+
}
|
|
196
|
+
const target = findSpace(registry, targetIdOrName);
|
|
197
|
+
const linkedId = target ? target.id : targetIdOrName.trim();
|
|
198
|
+
const links = space.links ?? [];
|
|
199
|
+
if (!links.includes(linkedId)) {
|
|
200
|
+
throw new SpaceError("unknown", `"${space.name}" does not link "${targetIdOrName}".`);
|
|
201
|
+
}
|
|
202
|
+
const rest = links.filter((id) => id !== linkedId);
|
|
203
|
+
return updateSpace(registry, space.id, { links: rest.length > 0 ? rest : undefined });
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* The links of a space with their availability, for the read paths (web layer, CLI
|
|
207
|
+
* listing). A link to a deleted or archived space stays in the registry and reads
|
|
208
|
+
* as unavailable here; search-time code skips it with a warning.
|
|
209
|
+
*/
|
|
210
|
+
export function spaceLinks(registry, idOrName) {
|
|
211
|
+
const space = requireSpace(registry, idOrName);
|
|
212
|
+
return (space.links ?? []).map((id) => {
|
|
213
|
+
const target = findSpace(registry, id);
|
|
214
|
+
return { id, name: target?.name ?? id, available: target !== undefined && !target.archived };
|
|
215
|
+
});
|
|
216
|
+
}
|
|
151
217
|
/** The folder of a space. Refuses an id that is not safe, so a path cannot leave the spaces root. */
|
|
152
218
|
export function resolveSpaceFolder(id, paths) {
|
|
153
219
|
if (id === DEFAULT_SPACE_ID)
|
|
@@ -162,6 +228,9 @@ export function resolveSpaceFolder(id, paths) {
|
|
|
162
228
|
}
|
|
163
229
|
return join(paths.spacesRoot, id);
|
|
164
230
|
}
|
|
231
|
+
function isLinkIdList(value) {
|
|
232
|
+
return Array.isArray(value) && value.every((id) => typeof id === "string");
|
|
233
|
+
}
|
|
165
234
|
function parseEntry(raw) {
|
|
166
235
|
if (typeof raw !== "object" || raw === null)
|
|
167
236
|
return undefined;
|
|
@@ -173,7 +242,8 @@ function parseEntry(raw) {
|
|
|
173
242
|
entry.name.trim() === "" ||
|
|
174
243
|
typeof entry.createdAt !== "string" ||
|
|
175
244
|
typeof entry.archived !== "boolean" ||
|
|
176
|
-
(entry.archivedAt !== undefined && typeof entry.archivedAt !== "string")
|
|
245
|
+
(entry.archivedAt !== undefined && typeof entry.archivedAt !== "string") ||
|
|
246
|
+
(entry.links !== undefined && !isLinkIdList(entry.links))) {
|
|
177
247
|
return undefined;
|
|
178
248
|
}
|
|
179
249
|
return {
|
|
@@ -182,6 +252,7 @@ function parseEntry(raw) {
|
|
|
182
252
|
createdAt: entry.createdAt,
|
|
183
253
|
archived: entry.archived,
|
|
184
254
|
...(typeof entry.archivedAt === "string" ? { archivedAt: entry.archivedAt } : {}),
|
|
255
|
+
...(isLinkIdList(entry.links) ? { links: entry.links } : {}),
|
|
185
256
|
};
|
|
186
257
|
}
|
|
187
258
|
/**
|
|
@@ -219,6 +290,9 @@ export function readRegistryFile(path) {
|
|
|
219
290
|
version: typeof file.version === "number" ? file.version : REGISTRY_VERSION,
|
|
220
291
|
activeSpaceId: file.activeSpaceId,
|
|
221
292
|
spaces,
|
|
293
|
+
...(typeof file.defaultName === "string" && file.defaultName.trim() !== ""
|
|
294
|
+
? { defaultName: file.defaultName }
|
|
295
|
+
: {}),
|
|
222
296
|
};
|
|
223
297
|
if (!listSpaces(registry).some((space) => space.id === registry.activeSpaceId)) {
|
|
224
298
|
return unusable(`active space "${registry.activeSpaceId}" is not in the list`);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { TursoConnectionManager } from "../turso/connection-manager.js";
|
|
2
|
+
import { TursoShardManager } from "../turso/shard-manager.js";
|
|
3
|
+
import type { ShardInfo } from "../turso/types.js";
|
|
4
|
+
import type { TursoDb } from "../turso/turso-db.js";
|
|
5
|
+
export type SpaceStoreMode = "read" | "write";
|
|
6
|
+
/** What both modes share: the folder, a private connection manager, and a close. */
|
|
7
|
+
export interface SpaceStoreBase {
|
|
8
|
+
/** The space folder this store is bound to (design D1). */
|
|
9
|
+
readonly folder: string;
|
|
10
|
+
/** This store's own connections, allowed only inside `folder`. Closed by `close`. */
|
|
11
|
+
readonly connections: TursoConnectionManager;
|
|
12
|
+
/** Closes every connection this store opened. Call once, when the operation ends. */
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Write store (design D2): transfer targets, import targets and move sources.
|
|
17
|
+
* It creates shards on demand and takes the write and migration locks of `folder`,
|
|
18
|
+
* so it excludes a host that has the same space active.
|
|
19
|
+
*/
|
|
20
|
+
export interface SpaceWriteStore extends SpaceStoreBase {
|
|
21
|
+
readonly mode: "write";
|
|
22
|
+
/** Shard manager rooted at `folder`; its locks and migration check use the same root. */
|
|
23
|
+
readonly shards: TursoShardManager;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Read store (design D2): transfer copy sources, previews and linked searches.
|
|
27
|
+
* It opens only files that exist, sets `query_only`, creates no file, changes no
|
|
28
|
+
* schema and takes no lock. A missing metadata.db means an empty space.
|
|
29
|
+
*/
|
|
30
|
+
export interface SpaceReadStore extends SpaceStoreBase {
|
|
31
|
+
readonly mode: "read";
|
|
32
|
+
/** False when the folder holds no metadata.db: the space has no memories. */
|
|
33
|
+
readonly hasMetadata: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* The registered shards of one scope. An empty `scopeHash` lists every hash of
|
|
36
|
+
* the scope, like `TursoShardManager.getAllShards`.
|
|
37
|
+
*/
|
|
38
|
+
listShards(scope: "user" | "project", scopeHash: string): Promise<ShardInfo[]>;
|
|
39
|
+
/** Opens one shard database with `query_only` set. Refuses a file that is missing. */
|
|
40
|
+
openShard(dbPath: string): Promise<TursoDb>;
|
|
41
|
+
}
|
|
42
|
+
export type SpaceStore = SpaceWriteStore | SpaceReadStore;
|
|
43
|
+
/**
|
|
44
|
+
* Opens a store bound to one space folder (design D1 and D2). The store shares no
|
|
45
|
+
* state with the active space: it brings its own connections, shard manager and
|
|
46
|
+
* locks, all rooted at `folder`. Write stores live for one operation and are closed
|
|
47
|
+
* in `finally`; read stores of linked spaces are cached (see read-store-cache.ts).
|
|
48
|
+
*/
|
|
49
|
+
export declare function openSpaceStore(folder: string, mode: "write"): SpaceWriteStore;
|
|
50
|
+
export declare function openSpaceStore(folder: string, mode: "read"): SpaceReadStore;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createClient } from "@libsql/client";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { basename, join, resolve } from "node:path";
|
|
4
|
+
import { TursoConnectionManager } from "../turso/connection-manager.js";
|
|
5
|
+
import { TursoShardManager } from "../turso/shard-manager.js";
|
|
6
|
+
const METADATA_DB_NAME = "metadata.db";
|
|
7
|
+
/** Read mode keeps a connection read-only, so pragmas match the host defaults plus this. */
|
|
8
|
+
const READ_ONLY_PRAGMAS = [
|
|
9
|
+
"PRAGMA busy_timeout = 5000",
|
|
10
|
+
"PRAGMA foreign_keys = ON",
|
|
11
|
+
"PRAGMA query_only = ON",
|
|
12
|
+
];
|
|
13
|
+
/** Maps one metadata.db `shards` row onto a ShardInfo whose path is resolved at `folder`. */
|
|
14
|
+
function rowToShardInfo(folder, row) {
|
|
15
|
+
const scope = String(row.scope);
|
|
16
|
+
return {
|
|
17
|
+
id: Number(row.id),
|
|
18
|
+
scope,
|
|
19
|
+
scopeHash: String(row.scope_hash),
|
|
20
|
+
shardIndex: Number(row.shard_index),
|
|
21
|
+
dbPath: resolve(join(folder, `${scope}s`, basename(String(row.db_path)))),
|
|
22
|
+
vectorCount: Number(row.vector_count),
|
|
23
|
+
isActive: Number(row.is_active) === 1,
|
|
24
|
+
createdAt: Number(row.created_at),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function openSpaceStore(folder, mode) {
|
|
28
|
+
const root = resolve(folder);
|
|
29
|
+
const connections = new TursoConnectionManager(createClient, () => [root]);
|
|
30
|
+
if (mode === "write") {
|
|
31
|
+
const shards = new TursoShardManager({
|
|
32
|
+
root: () => root,
|
|
33
|
+
connections,
|
|
34
|
+
});
|
|
35
|
+
return {
|
|
36
|
+
mode,
|
|
37
|
+
folder: root,
|
|
38
|
+
connections,
|
|
39
|
+
shards,
|
|
40
|
+
close: () => connections.closeAll(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// Read mode never calls the shard manager: its initialisation writes schema.
|
|
44
|
+
const hasMetadata = existsSync(join(root, METADATA_DB_NAME));
|
|
45
|
+
const openReadExisting = async (dbPath) => {
|
|
46
|
+
const db = await connections.getConnection(dbPath);
|
|
47
|
+
await db.setConnectionPragmas(READ_ONLY_PRAGMAS);
|
|
48
|
+
return db;
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
mode,
|
|
52
|
+
folder: root,
|
|
53
|
+
connections,
|
|
54
|
+
hasMetadata,
|
|
55
|
+
listShards: async (scope, scopeHash) => {
|
|
56
|
+
if (!hasMetadata)
|
|
57
|
+
return [];
|
|
58
|
+
const db = await openReadExisting(join(root, METADATA_DB_NAME));
|
|
59
|
+
const rows = scopeHash === ""
|
|
60
|
+
? await db.all(`SELECT * FROM shards WHERE scope = ? ORDER BY shard_index ASC`, [scope])
|
|
61
|
+
: await db.all(`SELECT * FROM shards WHERE scope = ? AND scope_hash = ? ORDER BY shard_index ASC`, [scope, scopeHash]);
|
|
62
|
+
return rows.map((row) => rowToShardInfo(root, row));
|
|
63
|
+
},
|
|
64
|
+
openShard: async (dbPath) => {
|
|
65
|
+
if (!existsSync(dbPath)) {
|
|
66
|
+
throw new Error(`Space shard file is missing: ${dbPath}`);
|
|
67
|
+
}
|
|
68
|
+
return openReadExisting(resolve(dbPath));
|
|
69
|
+
},
|
|
70
|
+
close: () => connections.closeAll(),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type SpacePaths } from "./registry.js";
|
|
2
|
+
import type { SpaceFiles } from "./registry-store.js";
|
|
3
|
+
import type { EmbeddingInfo, TransferApplyResult, TransferFilter, TransferMode, TransferPreview } from "./transfer-types.js";
|
|
4
|
+
export type TransferRefusalCode = "unknown-space" | "same-space" | "archived-space" | "registry-problem";
|
|
5
|
+
export interface TransferRefusal {
|
|
6
|
+
ok: false;
|
|
7
|
+
code: TransferRefusalCode;
|
|
8
|
+
error: string;
|
|
9
|
+
}
|
|
10
|
+
export type SpaceTransferPreviewOutcome = TransferRefusal | {
|
|
11
|
+
ok: true;
|
|
12
|
+
preview: TransferPreview;
|
|
13
|
+
};
|
|
14
|
+
export type SpaceTransferApplyOutcome = TransferRefusal | {
|
|
15
|
+
ok: true;
|
|
16
|
+
result: TransferApplyResult;
|
|
17
|
+
};
|
|
18
|
+
export interface SpaceTransferPreviewRequest {
|
|
19
|
+
from: string;
|
|
20
|
+
to: string;
|
|
21
|
+
filter: TransferFilter;
|
|
22
|
+
}
|
|
23
|
+
export interface SpaceTransferApplyRequest extends SpaceTransferPreviewRequest {
|
|
24
|
+
mode: TransferMode;
|
|
25
|
+
previewMatched?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface SpaceTransferContext {
|
|
28
|
+
files: SpaceFiles;
|
|
29
|
+
paths: SpacePaths;
|
|
30
|
+
embedding: EmbeddingInfo;
|
|
31
|
+
maxVectorsPerShard: number;
|
|
32
|
+
embed?: (text: string) => Promise<Float32Array>;
|
|
33
|
+
}
|
|
34
|
+
/** Previews a transfer without changing either space: read stores, no locks. */
|
|
35
|
+
export declare function previewSpaceTransfer(request: SpaceTransferPreviewRequest, context: SpaceTransferContext): Promise<SpaceTransferPreviewOutcome>;
|
|
36
|
+
/**
|
|
37
|
+
* Runs a copy or a move. The target opens in write mode; the source opens in
|
|
38
|
+
* write mode only for a move. Engine failures land in `result.error`; the call
|
|
39
|
+
* throws only on an unexpected store failure, after logging its name.
|
|
40
|
+
*/
|
|
41
|
+
export declare function applySpaceTransfer(request: SpaceTransferApplyRequest, context: SpaceTransferContext): Promise<SpaceTransferApplyOutcome>;
|