-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathstorageBackend.ts
More file actions
132 lines (111 loc) · 5.48 KB
/
Copy pathstorageBackend.ts
File metadata and controls
132 lines (111 loc) · 5.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import type { ProjectData } from '../features/project/projectSlice';
import type { ProjectSnapshot, Settings, StoryCodex, StoryProject } from '../types';
/** Metadata for binder binary assets (PDF, images, audio) stored outside Redux. */
export interface BinderAssetMeta {
mimeType: string;
originalFileName: string;
byteSize: number;
}
export interface BinderAssetPayload {
data: ArrayBuffer;
meta: BinderAssetMeta;
}
// QNBS-v3: shared result keeps the affected identity and verified quarantine path explicit across backends.
/** Result of moving a corrupt desktop project out of the active project namespace. */
export interface ProjectQuarantineResult {
projectId: string;
path: string;
}
/** IndexedDB / filesystem key for binder blobs — stable delimiter avoids UUID clashes. */
export function makeBinderAssetStorageKey(projectId: string, assetId: string): string {
const safeProject = projectId.replace(/[\s:]/g, '_').slice(0, 200);
return `${safeProject}::${assetId}`;
}
/** Prefix for listing/deleting all binder assets of a project (`projectId::`). */
export function makeBinderAssetIdsPrefix(projectId: string): string {
const safeProject = projectId.replace(/[\s:]/g, '_').slice(0, 200);
return `${safeProject}::`;
}
/**
* Redux-undo / auto-save shape (not a flat `StoryProject` export).
* Kept separate from `StoryProject` so call-sites can type auto-save without casts.
*/
export interface SaveProjectEnvelope {
data?: ProjectData;
present?: { data: ProjectData };
}
/**
* Payload from auto-save / Redux (`{ data }` envelope) or a flat exported `StoryProject`.
*/
export type SaveProjectInput = StoryProject | SaveProjectEnvelope;
// QNBS-v3: restore carries pre-read project identity so filesystem ownership never comes from snapshot content.
/**
* Current project state captured before snapshot I/O; filesystem backends inspect only identity markers.
*/
export type SnapshotRestoreTarget = ProjectData | StoryProject;
/** Auto-save from the current `ProjectData` (listener middleware) — returns a properly typed envelope. */
export function saveEnvelopeFromProjectData(data: ProjectData): SaveProjectEnvelope {
return { data };
}
/** Single normalization path for filesystem / UI: flat `StoryProject` from any save input. */
export function normalizeSaveProjectInputToStoryProject(project: SaveProjectInput): StoryProject {
if ('present' in project && project.present?.data) {
return project.present.data as StoryProject;
}
if ('data' in project && project.data) {
return project.data as StoryProject;
}
return project as StoryProject;
}
/**
* Contract implemented by IndexedDB (`dbService`) and Tauri filesystem (`fileSystemService`).
* Single source of truth — import from here, not from `storageService`, to avoid circular deps.
*/
export interface StorageBackend {
saveProject(project: SaveProjectInput): Promise<void>;
loadProject(projectId: string): Promise<StoryProject | null>;
/** QNBS-v3: editable desktop admission must reject readable legacy projections before Redux hydration. */
loadProjectForEditing?(projectId: string): Promise<StoryProject | null>;
listProjects(): Promise<string[]>;
deleteProject(projectId: string): Promise<void>;
/** QNBS-v3 (#332): optional — only the multi-project Tauri filesystem backend implements this; IndexedDB's single-project contract has no "which one" ambiguity to resolve. */
getActiveProjectId?(): Promise<string | null>;
// QNBS-v3: optional recovery keeps the filesystem-only quarantine contract out of IndexedDB.
quarantineProject?(projectId: string): Promise<ProjectQuarantineResult>;
saveImage(id: string, base64Data: string): Promise<void>;
getImage(id: string): Promise<string | null>;
saveSettings(settings: Settings): Promise<void>;
loadSettings(): Promise<Settings | null>;
saveGeminiApiKey(apiKey: string): Promise<void>;
getGeminiApiKey(): Promise<string | null>;
clearGeminiApiKey(): Promise<void>;
saveApiKey(provider: string, apiKey: string): Promise<void>;
getApiKey(provider: string): Promise<string | null>;
clearApiKey(provider: string): Promise<void>;
/** Snapshot IDs: numeric (Date.now / IDB auto-increment). */
saveSnapshot(snapshotLabel: string, data: unknown): Promise<number>;
getSnapshotData(snapshotId: number): Promise<unknown>;
// QNBS-v3: filesystem restore validates this pre-read target; other backends retain their existing snapshot semantics.
restoreSnapshot?(snapshotId: number, currentProject: SnapshotRestoreTarget): Promise<unknown>;
listSnapshots(): Promise<ProjectSnapshot[]>;
deleteSnapshot(snapshotId: number): Promise<void>;
deleteImage(id: string): Promise<void>;
hasSavedData(): Promise<boolean>;
saveStoryCodex(codex: StoryCodex): Promise<void>;
getStoryCodex(projectId: string): Promise<StoryCodex | null>;
deleteStoryCodex(projectId: string): Promise<void>;
saveRagVectors(projectId: string, vectors: unknown[]): Promise<void>;
getRagVectors(projectId: string): Promise<unknown[]>;
deleteRagVectors(projectId: string): Promise<void>;
/** Large binder files (research PDFs, images); Redux keeps only asset IDs. */
saveBinderAsset(
projectId: string,
assetId: string,
data: ArrayBuffer,
meta: BinderAssetMeta,
): Promise<void>;
getBinderAsset(projectId: string, assetId: string): Promise<BinderAssetPayload | null>;
deleteBinderAsset(projectId: string, assetId: string): Promise<void>;
listBinderAssetIds(projectId: string): Promise<string[]>;
deleteAllBinderAssetsForProject(projectId: string): Promise<void>;
}