@syncular/client 0.0.6-125 → 0.0.6-135
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/dist/blobs/index.d.ts +0 -1
- package/dist/blobs/index.d.ts.map +1 -1
- package/dist/blobs/index.js +0 -1
- package/dist/blobs/index.js.map +1 -1
- package/dist/migrate.d.ts +1 -1
- package/dist/migrate.d.ts.map +1 -1
- package/dist/migrate.js +0 -126
- package/dist/migrate.js.map +1 -1
- package/dist/mutations.d.ts.map +1 -1
- package/dist/mutations.js +9 -1
- package/dist/mutations.js.map +1 -1
- package/dist/sync-loop.d.ts.map +1 -1
- package/dist/sync-loop.js +29 -19
- package/dist/sync-loop.js.map +1 -1
- package/package.json +3 -3
- package/src/blobs/index.ts +0 -1
- package/src/migrate.ts +1 -190
- package/src/mutations.ts +9 -1
- package/src/sync-loop.ts +29 -19
- package/dist/blobs/manager.d.ts +0 -345
- package/dist/blobs/manager.d.ts.map +0 -1
- package/dist/blobs/manager.js +0 -749
- package/dist/blobs/manager.js.map +0 -1
- package/src/blobs/manager.ts +0 -1027
package/src/migrate.ts
CHANGED
|
@@ -2,195 +2,9 @@
|
|
|
2
2
|
* @syncular/client - Sync migrations (SQLite reference)
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import type { Kysely } from 'kysely';
|
|
6
6
|
import type { SyncClientDb } from './schema';
|
|
7
7
|
|
|
8
|
-
type SyncInternalTable =
|
|
9
|
-
| 'sync_subscription_state'
|
|
10
|
-
| 'sync_outbox_commits'
|
|
11
|
-
| 'sync_conflicts';
|
|
12
|
-
|
|
13
|
-
function toErrorMessage(error: unknown): string {
|
|
14
|
-
return error instanceof Error ? error.message : String(error);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function isMissingTableError(message: string): boolean {
|
|
18
|
-
const normalized = message.toLowerCase();
|
|
19
|
-
return (
|
|
20
|
-
normalized.includes('no such table') ||
|
|
21
|
-
(normalized.includes('relation') && normalized.includes('does not exist'))
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function isMissingColumnError(message: string): boolean {
|
|
26
|
-
const normalized = message.toLowerCase();
|
|
27
|
-
return (
|
|
28
|
-
normalized.includes('no such column') ||
|
|
29
|
-
(normalized.includes('column') && normalized.includes('does not exist'))
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function isDuplicateColumnError(message: string): boolean {
|
|
34
|
-
const normalized = message.toLowerCase();
|
|
35
|
-
return (
|
|
36
|
-
normalized.includes('duplicate column name') ||
|
|
37
|
-
(normalized.includes('column') && normalized.includes('already exists'))
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async function getColumnNames<DB extends SyncClientDb>(
|
|
42
|
-
db: Kysely<DB>,
|
|
43
|
-
tableName: SyncInternalTable
|
|
44
|
-
): Promise<Set<string> | null> {
|
|
45
|
-
try {
|
|
46
|
-
const sqlite = await sql<{ name: string }>`
|
|
47
|
-
select name from pragma_table_info(${sql.val(tableName)})
|
|
48
|
-
`.execute(db);
|
|
49
|
-
return new Set(sqlite.rows.map((row) => String(row.name)));
|
|
50
|
-
} catch {
|
|
51
|
-
// Not SQLite or pragma unavailable.
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
const postgres = await sql<{ name: string }>`
|
|
56
|
-
select column_name as name
|
|
57
|
-
from information_schema.columns
|
|
58
|
-
where table_name = ${sql.val(tableName)}
|
|
59
|
-
`.execute(db);
|
|
60
|
-
return new Set(postgres.rows.map((row) => String(row.name)));
|
|
61
|
-
} catch {
|
|
62
|
-
// Introspection unavailable; caller falls back to probing.
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async function hasColumn<DB extends SyncClientDb>(
|
|
69
|
-
db: Kysely<DB>,
|
|
70
|
-
tableName: SyncInternalTable,
|
|
71
|
-
columnName: string
|
|
72
|
-
): Promise<boolean> {
|
|
73
|
-
const columns = await getColumnNames(db, tableName);
|
|
74
|
-
if (columns) {
|
|
75
|
-
return columns.has(columnName);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
await sql`select ${sql.ref(columnName)} from ${sql.table(tableName)} limit 1`.execute(
|
|
80
|
-
db
|
|
81
|
-
);
|
|
82
|
-
return true;
|
|
83
|
-
} catch (error) {
|
|
84
|
-
const message = toErrorMessage(error);
|
|
85
|
-
if (isMissingTableError(message) || isMissingColumnError(message)) {
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
throw error;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function addColumnIfMissing<DB extends SyncClientDb>(
|
|
93
|
-
db: Kysely<DB>,
|
|
94
|
-
tableName: SyncInternalTable,
|
|
95
|
-
columnName: string,
|
|
96
|
-
addColumn: () => Promise<void>
|
|
97
|
-
): Promise<void> {
|
|
98
|
-
if (await hasColumn(db, tableName, columnName)) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
try {
|
|
102
|
-
await addColumn();
|
|
103
|
-
} catch (error) {
|
|
104
|
-
const message = toErrorMessage(error);
|
|
105
|
-
if (isDuplicateColumnError(message)) {
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
throw error;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function ensureClientSyncSchemaCompat<DB extends SyncClientDb>(
|
|
113
|
-
db: Kysely<DB>
|
|
114
|
-
): Promise<void> {
|
|
115
|
-
const hasTableColumn = await hasColumn(
|
|
116
|
-
db,
|
|
117
|
-
'sync_subscription_state',
|
|
118
|
-
'table'
|
|
119
|
-
);
|
|
120
|
-
if (
|
|
121
|
-
!hasTableColumn &&
|
|
122
|
-
(await hasColumn(db, 'sync_subscription_state', 'shape'))
|
|
123
|
-
) {
|
|
124
|
-
try {
|
|
125
|
-
await sql`alter table ${sql.table('sync_subscription_state')} rename column ${sql.ref('shape')} to ${sql.ref('table')}`.execute(
|
|
126
|
-
db
|
|
127
|
-
);
|
|
128
|
-
} catch {
|
|
129
|
-
await addColumnIfMissing(
|
|
130
|
-
db,
|
|
131
|
-
'sync_subscription_state',
|
|
132
|
-
'table',
|
|
133
|
-
async () => {
|
|
134
|
-
await db.schema
|
|
135
|
-
.alterTable('sync_subscription_state')
|
|
136
|
-
.addColumn('table', 'text', (col) => col.notNull().defaultTo(''))
|
|
137
|
-
.execute();
|
|
138
|
-
}
|
|
139
|
-
);
|
|
140
|
-
await sql`update ${sql.table('sync_subscription_state')}
|
|
141
|
-
set ${sql.ref('table')} = ${sql.ref('shape')}
|
|
142
|
-
where ${sql.ref('table')} = ${sql.val('')}`.execute(db);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
await addColumnIfMissing(
|
|
147
|
-
db,
|
|
148
|
-
'sync_subscription_state',
|
|
149
|
-
'bootstrap_state_json',
|
|
150
|
-
async () => {
|
|
151
|
-
await db.schema
|
|
152
|
-
.alterTable('sync_subscription_state')
|
|
153
|
-
.addColumn('bootstrap_state_json', 'text')
|
|
154
|
-
.execute();
|
|
155
|
-
}
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
await addColumnIfMissing(
|
|
159
|
-
db,
|
|
160
|
-
'sync_outbox_commits',
|
|
161
|
-
'schema_version',
|
|
162
|
-
async () => {
|
|
163
|
-
await db.schema
|
|
164
|
-
.alterTable('sync_outbox_commits')
|
|
165
|
-
.addColumn('schema_version', 'integer', (col) =>
|
|
166
|
-
col.notNull().defaultTo(1)
|
|
167
|
-
)
|
|
168
|
-
.execute();
|
|
169
|
-
}
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
await addColumnIfMissing(db, 'sync_conflicts', 'resolved_at', async () => {
|
|
173
|
-
await db.schema
|
|
174
|
-
.alterTable('sync_conflicts')
|
|
175
|
-
.addColumn('resolved_at', 'bigint')
|
|
176
|
-
.execute();
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
await addColumnIfMissing(db, 'sync_conflicts', 'resolution', async () => {
|
|
180
|
-
await db.schema
|
|
181
|
-
.alterTable('sync_conflicts')
|
|
182
|
-
.addColumn('resolution', 'text')
|
|
183
|
-
.execute();
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
await db.schema
|
|
187
|
-
.createIndex('idx_sync_outbox_commits_status_updated_at')
|
|
188
|
-
.ifNotExists()
|
|
189
|
-
.on('sync_outbox_commits')
|
|
190
|
-
.columns(['status', 'updated_at', 'created_at'])
|
|
191
|
-
.execute();
|
|
192
|
-
}
|
|
193
|
-
|
|
194
8
|
/**
|
|
195
9
|
* Ensures the client sync schema exists in the database.
|
|
196
10
|
* Safe to call multiple times (idempotent).
|
|
@@ -249,9 +63,6 @@ export async function ensureClientSyncSchema<DB extends SyncClientDb>(
|
|
|
249
63
|
.addColumn('resolution', 'text')
|
|
250
64
|
.execute();
|
|
251
65
|
|
|
252
|
-
// Apply framework-managed compatibility upgrades for legacy sync tables.
|
|
253
|
-
await ensureClientSyncSchemaCompat(db);
|
|
254
|
-
|
|
255
66
|
await db.schema
|
|
256
67
|
.createIndex('idx_sync_subscription_state_state_sub')
|
|
257
68
|
.ifNotExists()
|
package/src/mutations.ts
CHANGED
|
@@ -168,7 +168,7 @@ function coerceBaseVersion(value: unknown): number | null {
|
|
|
168
168
|
if (value === null || value === undefined) return null;
|
|
169
169
|
const n = typeof value === 'number' ? value : Number(value);
|
|
170
170
|
if (!Number.isFinite(n)) return null;
|
|
171
|
-
if (n
|
|
171
|
+
if (n < 0) return null;
|
|
172
172
|
return n;
|
|
173
173
|
}
|
|
174
174
|
|
|
@@ -498,6 +498,9 @@ export function createOutboxCommit<DB extends SyncClientDb>(
|
|
|
498
498
|
},
|
|
499
499
|
|
|
500
500
|
async insertMany(rows) {
|
|
501
|
+
if (rows.length === 0) {
|
|
502
|
+
throw new Error('insertMany requires at least one row');
|
|
503
|
+
}
|
|
501
504
|
const ids: string[] = [];
|
|
502
505
|
const toInsert: Record<string, unknown>[] = [];
|
|
503
506
|
|
|
@@ -672,6 +675,7 @@ export function createOutboxCommit<DB extends SyncClientDb>(
|
|
|
672
675
|
get(_target, prop) {
|
|
673
676
|
if (prop === 'then') return undefined;
|
|
674
677
|
if (typeof prop !== 'string') return undefined;
|
|
678
|
+
validateTableName(prop);
|
|
675
679
|
return makeTxTable(prop);
|
|
676
680
|
},
|
|
677
681
|
}
|
|
@@ -777,6 +781,9 @@ export function createPushCommit<DB = AnyDb>(
|
|
|
777
781
|
},
|
|
778
782
|
|
|
779
783
|
async insertMany(rows) {
|
|
784
|
+
if (rows.length === 0) {
|
|
785
|
+
throw new Error('insertMany requires at least one row');
|
|
786
|
+
}
|
|
780
787
|
const ids: string[] = [];
|
|
781
788
|
const toUpsert: Record<string, unknown>[] = [];
|
|
782
789
|
|
|
@@ -885,6 +892,7 @@ export function createPushCommit<DB = AnyDb>(
|
|
|
885
892
|
get(_target, prop) {
|
|
886
893
|
if (prop === 'then') return undefined;
|
|
887
894
|
if (typeof prop !== 'string') return undefined;
|
|
895
|
+
validateTableName(prop);
|
|
888
896
|
return makeTxTable(prop);
|
|
889
897
|
},
|
|
890
898
|
}
|
package/src/sync-loop.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type {
|
|
8
|
+
SyncCombinedResponse,
|
|
8
9
|
SyncPullResponse,
|
|
9
10
|
SyncPullSubscriptionResponse,
|
|
10
11
|
SyncPushRequest,
|
|
@@ -244,25 +245,34 @@ async function syncOnceCombined<DB extends SyncClientDb>(
|
|
|
244
245
|
}
|
|
245
246
|
}
|
|
246
247
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
248
|
+
let combined: SyncCombinedResponse;
|
|
249
|
+
try {
|
|
250
|
+
combined = await transport.sync({
|
|
251
|
+
clientId,
|
|
252
|
+
...(pushRequest && !wsPushResponse
|
|
253
|
+
? {
|
|
254
|
+
push: {
|
|
255
|
+
clientCommitId: pushRequest.clientCommitId,
|
|
256
|
+
operations: pushRequest.operations,
|
|
257
|
+
schemaVersion: pushRequest.schemaVersion,
|
|
258
|
+
},
|
|
259
|
+
}
|
|
260
|
+
: {}),
|
|
261
|
+
pull: {
|
|
262
|
+
limitCommits: pullState.request.limitCommits,
|
|
263
|
+
limitSnapshotRows: pullState.request.limitSnapshotRows,
|
|
264
|
+
maxSnapshotPages: pullState.request.maxSnapshotPages,
|
|
265
|
+
dedupeRows: pullState.request.dedupeRows,
|
|
266
|
+
subscriptions: pullState.request.subscriptions,
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
} catch (err) {
|
|
270
|
+
if (outbox) {
|
|
271
|
+
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
272
|
+
await markOutboxCommitPending(db, { id: outbox.id, error: message });
|
|
273
|
+
}
|
|
274
|
+
throw err;
|
|
275
|
+
}
|
|
266
276
|
|
|
267
277
|
// Process push response
|
|
268
278
|
let pushedCommits = 0;
|
package/dist/blobs/manager.d.ts
DELETED
|
@@ -1,345 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @syncular/client - Client-side blob manager
|
|
3
|
-
*
|
|
4
|
-
* Handles blob upload/download with:
|
|
5
|
-
* - Local caching for offline access
|
|
6
|
-
* - Upload queue for offline uploads
|
|
7
|
-
* - SHA-256 hash computation
|
|
8
|
-
* - Optional client-side encryption
|
|
9
|
-
*/
|
|
10
|
-
import type { BlobTransport } from '@syncular/core';
|
|
11
|
-
import type { Kysely } from 'kysely';
|
|
12
|
-
import type { BlobUploadStatus, SyncBlobClientDb } from './types';
|
|
13
|
-
export type { BlobTransport } from '@syncular/core';
|
|
14
|
-
interface BlobEncryption {
|
|
15
|
-
/**
|
|
16
|
-
* Encrypt blob content.
|
|
17
|
-
* Returns encrypted bytes and the key ID used.
|
|
18
|
-
*/
|
|
19
|
-
encrypt(data: Uint8Array, options?: {
|
|
20
|
-
keyId?: string;
|
|
21
|
-
}): Promise<{
|
|
22
|
-
encrypted: Uint8Array;
|
|
23
|
-
keyId: string;
|
|
24
|
-
}>;
|
|
25
|
-
/**
|
|
26
|
-
* Decrypt blob content.
|
|
27
|
-
*/
|
|
28
|
-
decrypt(data: Uint8Array, keyId: string): Promise<Uint8Array>;
|
|
29
|
-
}
|
|
30
|
-
export interface ClientBlobManagerOptions {
|
|
31
|
-
/** Kysely database instance */
|
|
32
|
-
db: Kysely<SyncBlobClientDb>;
|
|
33
|
-
/** Blob transport for server communication */
|
|
34
|
-
transport: BlobTransport;
|
|
35
|
-
/** Optional encryption handler */
|
|
36
|
-
encryption?: BlobEncryption;
|
|
37
|
-
/** Maximum cache size in bytes. Default: 100MB */
|
|
38
|
-
maxCacheSize?: number;
|
|
39
|
-
/** Maximum retry attempts for uploads. Default: 3 */
|
|
40
|
-
maxUploadRetries?: number;
|
|
41
|
-
/** Custom fetch function for blob uploads/downloads. Default: globalThis.fetch */
|
|
42
|
-
fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
43
|
-
}
|
|
44
|
-
export interface UploadOptions {
|
|
45
|
-
/** Encrypt the blob before uploading */
|
|
46
|
-
encrypt?: boolean;
|
|
47
|
-
/** Specific encryption key ID to use */
|
|
48
|
-
keyId?: string;
|
|
49
|
-
/** Skip queuing and upload immediately (blocks until complete) */
|
|
50
|
-
immediate?: boolean;
|
|
51
|
-
}
|
|
52
|
-
export interface DownloadOptions {
|
|
53
|
-
/** Skip cache lookup and always fetch from server */
|
|
54
|
-
skipCache?: boolean;
|
|
55
|
-
/** Update last_accessed_at in cache */
|
|
56
|
-
updateAccessTime?: boolean;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Create a client-side blob manager.
|
|
60
|
-
*
|
|
61
|
-
* @example
|
|
62
|
-
* ```typescript
|
|
63
|
-
* const blobManager = createClientBlobManager({
|
|
64
|
-
* db,
|
|
65
|
-
* transport: {
|
|
66
|
-
* async initiateUpload(args) {
|
|
67
|
-
* const res = await fetch('/api/sync/blobs/upload', {
|
|
68
|
-
* method: 'POST',
|
|
69
|
-
* body: JSON.stringify(args),
|
|
70
|
-
* });
|
|
71
|
-
* return res.json();
|
|
72
|
-
* },
|
|
73
|
-
* async completeUpload(hash) {
|
|
74
|
-
* const res = await fetch(`/api/sync/blobs/${hash}/complete`, { method: 'POST' });
|
|
75
|
-
* return res.json();
|
|
76
|
-
* },
|
|
77
|
-
* async getDownloadUrl(hash) {
|
|
78
|
-
* const res = await fetch(`/api/sync/blobs/${hash}/url`);
|
|
79
|
-
* return res.json();
|
|
80
|
-
* },
|
|
81
|
-
* },
|
|
82
|
-
* });
|
|
83
|
-
*
|
|
84
|
-
* // Upload a file
|
|
85
|
-
* const blobRef = await blobManager.upload(file);
|
|
86
|
-
*
|
|
87
|
-
* // Download a blob
|
|
88
|
-
* const blob = await blobManager.download(blobRef);
|
|
89
|
-
* ```
|
|
90
|
-
*/
|
|
91
|
-
export declare function createClientBlobManager(options: ClientBlobManagerOptions): {
|
|
92
|
-
/**
|
|
93
|
-
* Upload a blob to the server.
|
|
94
|
-
*
|
|
95
|
-
* If `immediate` is false (default), the blob is queued for background upload.
|
|
96
|
-
* If `immediate` is true, the upload blocks until complete.
|
|
97
|
-
*/
|
|
98
|
-
upload(data: Blob | File | Uint8Array<ArrayBufferLike>, opts?: UploadOptions | undefined): Promise<{
|
|
99
|
-
hash: string;
|
|
100
|
-
size: number;
|
|
101
|
-
mimeType: string;
|
|
102
|
-
encrypted?: boolean | undefined;
|
|
103
|
-
keyId?: string | undefined;
|
|
104
|
-
}>;
|
|
105
|
-
/**
|
|
106
|
-
* Download a blob.
|
|
107
|
-
*
|
|
108
|
-
* First checks the local cache, then fetches from server if needed.
|
|
109
|
-
* Automatically decrypts if the blob was encrypted.
|
|
110
|
-
*/
|
|
111
|
-
download(ref: {
|
|
112
|
-
hash: string;
|
|
113
|
-
size: number;
|
|
114
|
-
mimeType: string;
|
|
115
|
-
encrypted?: boolean | undefined;
|
|
116
|
-
keyId?: string | undefined;
|
|
117
|
-
}, opts?: DownloadOptions | undefined): Promise<Uint8Array<ArrayBufferLike>>;
|
|
118
|
-
/**
|
|
119
|
-
* Check if a blob is cached locally.
|
|
120
|
-
*/
|
|
121
|
-
isCached(hash: string): Promise<boolean>;
|
|
122
|
-
/**
|
|
123
|
-
* Get a blob URL for display.
|
|
124
|
-
*
|
|
125
|
-
* Returns a blob: URL if cached locally, or fetches and creates one.
|
|
126
|
-
*/
|
|
127
|
-
getBlobUrl(ref: {
|
|
128
|
-
hash: string;
|
|
129
|
-
size: number;
|
|
130
|
-
mimeType: string;
|
|
131
|
-
encrypted?: boolean | undefined;
|
|
132
|
-
keyId?: string | undefined;
|
|
133
|
-
}): Promise<string>;
|
|
134
|
-
/**
|
|
135
|
-
* Preload blobs into the cache.
|
|
136
|
-
*/
|
|
137
|
-
preload(refs: {
|
|
138
|
-
hash: string;
|
|
139
|
-
size: number;
|
|
140
|
-
mimeType: string;
|
|
141
|
-
encrypted?: boolean | undefined;
|
|
142
|
-
keyId?: string | undefined;
|
|
143
|
-
}[]): Promise<void>;
|
|
144
|
-
/**
|
|
145
|
-
* Process pending uploads in the outbox.
|
|
146
|
-
*
|
|
147
|
-
* Call this periodically or when online to sync pending uploads.
|
|
148
|
-
* Returns the number of blobs processed.
|
|
149
|
-
*/
|
|
150
|
-
processUploadQueue(): Promise<{
|
|
151
|
-
uploaded: number;
|
|
152
|
-
failed: number;
|
|
153
|
-
errors: {
|
|
154
|
-
hash: string;
|
|
155
|
-
error: string;
|
|
156
|
-
}[];
|
|
157
|
-
}>;
|
|
158
|
-
/**
|
|
159
|
-
* Get the status of a pending upload.
|
|
160
|
-
*/
|
|
161
|
-
getUploadStatus(hash: string): Promise<{
|
|
162
|
-
status: BlobUploadStatus;
|
|
163
|
-
error?: string | undefined;
|
|
164
|
-
} | null>;
|
|
165
|
-
/**
|
|
166
|
-
* Clear failed uploads from the outbox.
|
|
167
|
-
*/
|
|
168
|
-
clearFailedUploads(): Promise<number>;
|
|
169
|
-
/**
|
|
170
|
-
* Retry a failed upload.
|
|
171
|
-
*/
|
|
172
|
-
retryUpload(hash: string): Promise<boolean>;
|
|
173
|
-
/**
|
|
174
|
-
* Prune the cache to stay under maxCacheSize.
|
|
175
|
-
* Uses LRU (least recently used) eviction.
|
|
176
|
-
*/
|
|
177
|
-
pruneCache(): Promise<{
|
|
178
|
-
evicted: number;
|
|
179
|
-
freedBytes: number;
|
|
180
|
-
}>;
|
|
181
|
-
/**
|
|
182
|
-
* Clear the entire cache.
|
|
183
|
-
*/
|
|
184
|
-
clearCache(): Promise<number>;
|
|
185
|
-
/**
|
|
186
|
-
* Get cache statistics.
|
|
187
|
-
*/
|
|
188
|
-
getCacheStats(): Promise<{
|
|
189
|
-
count: number;
|
|
190
|
-
totalSize: number;
|
|
191
|
-
maxSize: number;
|
|
192
|
-
}>;
|
|
193
|
-
/**
|
|
194
|
-
* Get upload queue statistics.
|
|
195
|
-
*/
|
|
196
|
-
getUploadQueueStats(): Promise<{
|
|
197
|
-
pending: number;
|
|
198
|
-
uploading: number;
|
|
199
|
-
failed: number;
|
|
200
|
-
total: number;
|
|
201
|
-
}>;
|
|
202
|
-
};
|
|
203
|
-
type ClientBlobManager = ReturnType<typeof createClientBlobManager>;
|
|
204
|
-
interface BlobCachePruneSchedulerOptions {
|
|
205
|
-
/** Client blob manager instance */
|
|
206
|
-
blobManager: ClientBlobManager;
|
|
207
|
-
/** Interval between prune runs in milliseconds. Default: 300000 (5 minutes) */
|
|
208
|
-
intervalMs?: number;
|
|
209
|
-
/** Optional: Called after each prune run */
|
|
210
|
-
onPrune?: (result: {
|
|
211
|
-
evicted: number;
|
|
212
|
-
freedBytes: number;
|
|
213
|
-
error?: Error;
|
|
214
|
-
}) => void;
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Create a cache pruning scheduler for the client blob manager.
|
|
218
|
-
*
|
|
219
|
-
* Periodically prunes the cache to stay under maxCacheSize using LRU eviction.
|
|
220
|
-
*
|
|
221
|
-
* @example
|
|
222
|
-
* ```typescript
|
|
223
|
-
* const scheduler = createBlobCachePruneScheduler({
|
|
224
|
-
* blobManager,
|
|
225
|
-
* intervalMs: 5 * 60 * 1000, // 5 minutes
|
|
226
|
-
* onPrune: (result) => {
|
|
227
|
-
* if (result.evicted > 0) {
|
|
228
|
-
* console.log(`Cache pruned: ${result.evicted} blobs, ${result.freedBytes} bytes freed`);
|
|
229
|
-
* }
|
|
230
|
-
* },
|
|
231
|
-
* });
|
|
232
|
-
*
|
|
233
|
-
* // Start the scheduler
|
|
234
|
-
* scheduler.start();
|
|
235
|
-
*
|
|
236
|
-
* // Stop when unmounting/shutting down
|
|
237
|
-
* scheduler.stop();
|
|
238
|
-
* ```
|
|
239
|
-
*/
|
|
240
|
-
export declare function createBlobCachePruneScheduler(options: BlobCachePruneSchedulerOptions): {
|
|
241
|
-
/**
|
|
242
|
-
* Start the prune scheduler.
|
|
243
|
-
* Optionally runs an immediate prune before starting the interval.
|
|
244
|
-
*/
|
|
245
|
-
start(options?: {
|
|
246
|
-
immediate?: boolean | undefined;
|
|
247
|
-
} | undefined): void;
|
|
248
|
-
/**
|
|
249
|
-
* Stop the prune scheduler.
|
|
250
|
-
*/
|
|
251
|
-
stop(): void;
|
|
252
|
-
/**
|
|
253
|
-
* Run a single prune manually.
|
|
254
|
-
*/
|
|
255
|
-
runOnce(): Promise<{
|
|
256
|
-
evicted: number;
|
|
257
|
-
freedBytes: number;
|
|
258
|
-
error?: Error | undefined;
|
|
259
|
-
}>;
|
|
260
|
-
/**
|
|
261
|
-
* Check if the scheduler is currently active.
|
|
262
|
-
*/
|
|
263
|
-
readonly active: boolean;
|
|
264
|
-
/**
|
|
265
|
-
* Check if a prune is currently in progress.
|
|
266
|
-
*/
|
|
267
|
-
readonly running: boolean;
|
|
268
|
-
};
|
|
269
|
-
interface BlobUploadQueueSchedulerOptions {
|
|
270
|
-
/** Client blob manager instance */
|
|
271
|
-
blobManager: ClientBlobManager;
|
|
272
|
-
/** Interval between processing runs in milliseconds. Default: 30000 (30 seconds) */
|
|
273
|
-
intervalMs?: number;
|
|
274
|
-
/** Optional: Called after each processing run */
|
|
275
|
-
onProcess?: (result: {
|
|
276
|
-
uploaded: number;
|
|
277
|
-
failed: number;
|
|
278
|
-
errors: Array<{
|
|
279
|
-
hash: string;
|
|
280
|
-
error: string;
|
|
281
|
-
}>;
|
|
282
|
-
error?: Error;
|
|
283
|
-
}) => void;
|
|
284
|
-
}
|
|
285
|
-
/**
|
|
286
|
-
* Create an upload queue processor scheduler for the client blob manager.
|
|
287
|
-
*
|
|
288
|
-
* Periodically processes pending uploads when online.
|
|
289
|
-
*
|
|
290
|
-
* @example
|
|
291
|
-
* ```typescript
|
|
292
|
-
* const scheduler = createBlobUploadQueueScheduler({
|
|
293
|
-
* blobManager,
|
|
294
|
-
* intervalMs: 30 * 1000, // 30 seconds
|
|
295
|
-
* onProcess: (result) => {
|
|
296
|
-
* if (result.uploaded > 0) {
|
|
297
|
-
* console.log(`Uploaded ${result.uploaded} blobs`);
|
|
298
|
-
* }
|
|
299
|
-
* if (result.failed > 0) {
|
|
300
|
-
* console.warn(`Failed to upload ${result.failed} blobs`);
|
|
301
|
-
* }
|
|
302
|
-
* },
|
|
303
|
-
* });
|
|
304
|
-
*
|
|
305
|
-
* // Start when online
|
|
306
|
-
* scheduler.start();
|
|
307
|
-
*
|
|
308
|
-
* // Stop when offline or shutting down
|
|
309
|
-
* scheduler.stop();
|
|
310
|
-
* ```
|
|
311
|
-
*/
|
|
312
|
-
export declare function createBlobUploadQueueScheduler(options: BlobUploadQueueSchedulerOptions): {
|
|
313
|
-
/**
|
|
314
|
-
* Start the upload queue processor.
|
|
315
|
-
* Optionally runs an immediate processing before starting the interval.
|
|
316
|
-
*/
|
|
317
|
-
start(options?: {
|
|
318
|
-
immediate?: boolean | undefined;
|
|
319
|
-
} | undefined): void;
|
|
320
|
-
/**
|
|
321
|
-
* Stop the upload queue processor.
|
|
322
|
-
*/
|
|
323
|
-
stop(): void;
|
|
324
|
-
/**
|
|
325
|
-
* Run a single processing manually.
|
|
326
|
-
*/
|
|
327
|
-
runOnce(): Promise<{
|
|
328
|
-
uploaded: number;
|
|
329
|
-
failed: number;
|
|
330
|
-
errors: {
|
|
331
|
-
hash: string;
|
|
332
|
-
error: string;
|
|
333
|
-
}[];
|
|
334
|
-
error?: Error | undefined;
|
|
335
|
-
}>;
|
|
336
|
-
/**
|
|
337
|
-
* Check if the processor is currently active.
|
|
338
|
-
*/
|
|
339
|
-
readonly active: boolean;
|
|
340
|
-
/**
|
|
341
|
-
* Check if processing is currently in progress.
|
|
342
|
-
*/
|
|
343
|
-
readonly running: boolean;
|
|
344
|
-
};
|
|
345
|
-
//# sourceMappingURL=manager.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/blobs/manager.ts"],"names":[],"mappings":"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"}
|