@syncular/client-plugin-blob 0.0.0 → 0.0.6-201

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.
@@ -0,0 +1,15 @@
1
+ import type { SyncClientPlugin } from '@syncular/client';
2
+ import type { BlobClient, ClientBlobStorage } from './types';
3
+ export * from './migrate';
4
+ export * from './types';
5
+ export declare const BLOB_PLUGIN_KIND = "blob";
6
+ export interface BlobPluginOptions {
7
+ storage: ClientBlobStorage;
8
+ }
9
+ declare module '@syncular/client' {
10
+ interface SyncClientFeatureRegistry {
11
+ blobs: BlobClient;
12
+ }
13
+ }
14
+ export declare function createBlobPlugin(options: BlobPluginOptions): SyncClientPlugin;
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAMvE,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE7D,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AAExB,eAAO,MAAM,gBAAgB,SAAS,CAAC;AAEvC,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,iBAAiB,CAAC;CAC5B;AAED,OAAO,QAAQ,kBAAkB,CAAC,CAAC;IACjC,UAAU,yBAAyB;QACjC,KAAK,EAAE,UAAU,CAAC;KACnB;CACF;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CA4B7E"}
package/dist/index.js ADDED
@@ -0,0 +1,376 @@
1
+ import { ensureHttpTransportBlobs } from '@syncular/transport-http/blob';
2
+ import { sql } from 'kysely';
3
+ import { ensureClientBlobSchema } from './migrate.js';
4
+ export * from './migrate.js';
5
+ export * from './types.js';
6
+ export const BLOB_PLUGIN_KIND = 'blob';
7
+ export function createBlobPlugin(options) {
8
+ return {
9
+ kind: BLOB_PLUGIN_KIND,
10
+ name: BLOB_PLUGIN_KIND,
11
+ setup(ctx) {
12
+ const blobs = ctx.transport.blobs ?? ensureHttpTransportBlobs(ctx.transport);
13
+ if (!blobs) {
14
+ throw new Error('Blob plugin requires a transport with blob support enabled');
15
+ }
16
+ ctx.transport.blobs = blobs;
17
+ ctx.defineFeature('blobs', createBlobClient({
18
+ db: ctx.db,
19
+ transport: ctx.transport,
20
+ storage: options.storage,
21
+ emit: ctx.emit,
22
+ }));
23
+ },
24
+ async migrate(ctx) {
25
+ await ensureClientBlobSchema(ctx.db);
26
+ },
27
+ };
28
+ }
29
+ function createBlobClient(args) {
30
+ const { db, storage, transport, emit } = args;
31
+ const blobs = transport.blobs;
32
+ const staleUploadingTimeoutMs = 30_000;
33
+ const maxUploadRetries = 3;
34
+ return {
35
+ async store(data, options) {
36
+ const bytes = await toUint8Array(data);
37
+ const mimeType = data instanceof Blob
38
+ ? data.type
39
+ : (options?.mimeType ?? 'application/octet-stream');
40
+ const hashHex = await computeSha256Hex(bytes);
41
+ const hash = `sha256:${hashHex}`;
42
+ await storage.write(hash, bytes);
43
+ const now = Date.now();
44
+ await sql `
45
+ insert into ${sql.table('sync_blob_cache')} (
46
+ ${sql.join([
47
+ sql.ref('hash'),
48
+ sql.ref('size'),
49
+ sql.ref('mime_type'),
50
+ sql.ref('cached_at'),
51
+ sql.ref('last_accessed_at'),
52
+ sql.ref('encrypted'),
53
+ sql.ref('key_id'),
54
+ sql.ref('body'),
55
+ ])}
56
+ ) values (
57
+ ${sql.join([
58
+ sql.val(hash),
59
+ sql.val(bytes.length),
60
+ sql.val(mimeType),
61
+ sql.val(now),
62
+ sql.val(now),
63
+ sql.val(0),
64
+ sql.val(null),
65
+ sql.val(bytes),
66
+ ])}
67
+ )
68
+ on conflict (${sql.ref('hash')}) do nothing
69
+ `.execute(db);
70
+ if (options?.immediate) {
71
+ const initResult = await blobs.initiateUpload({
72
+ hash,
73
+ size: bytes.length,
74
+ mimeType,
75
+ });
76
+ if (!initResult.exists && initResult.uploadUrl) {
77
+ const uploadResponse = await fetch(initResult.uploadUrl, {
78
+ method: initResult.uploadMethod ?? 'PUT',
79
+ body: bytes.buffer,
80
+ headers: initResult.uploadHeaders,
81
+ });
82
+ if (!uploadResponse.ok) {
83
+ throw new Error(`Upload failed: ${uploadResponse.statusText}`);
84
+ }
85
+ await blobs.completeUpload(hash);
86
+ }
87
+ }
88
+ else {
89
+ await sql `
90
+ insert into ${sql.table('sync_blob_outbox')} (
91
+ ${sql.join([
92
+ sql.ref('hash'),
93
+ sql.ref('size'),
94
+ sql.ref('mime_type'),
95
+ sql.ref('status'),
96
+ sql.ref('created_at'),
97
+ sql.ref('updated_at'),
98
+ sql.ref('attempt_count'),
99
+ sql.ref('error'),
100
+ sql.ref('encrypted'),
101
+ sql.ref('key_id'),
102
+ sql.ref('body'),
103
+ ])}
104
+ ) values (
105
+ ${sql.join([
106
+ sql.val(hash),
107
+ sql.val(bytes.length),
108
+ sql.val(mimeType),
109
+ sql.val('pending'),
110
+ sql.val(now),
111
+ sql.val(now),
112
+ sql.val(0),
113
+ sql.val(null),
114
+ sql.val(0),
115
+ sql.val(null),
116
+ sql.val(bytes),
117
+ ])}
118
+ )
119
+ on conflict (${sql.ref('hash')}) do nothing
120
+ `.execute(db);
121
+ }
122
+ return {
123
+ hash,
124
+ size: bytes.length,
125
+ mimeType,
126
+ };
127
+ },
128
+ async retrieve(ref) {
129
+ const local = await storage.read(ref.hash);
130
+ if (local) {
131
+ await sql `
132
+ update ${sql.table('sync_blob_cache')}
133
+ set ${sql.ref('last_accessed_at')} = ${sql.val(Date.now())}
134
+ where ${sql.ref('hash')} = ${sql.val(ref.hash)}
135
+ `.execute(db);
136
+ return local;
137
+ }
138
+ const { url } = await blobs.getDownloadUrl(ref.hash);
139
+ const response = await fetch(url);
140
+ if (!response.ok) {
141
+ throw new Error(`Download failed: ${response.statusText}`);
142
+ }
143
+ const bytes = new Uint8Array(await response.arrayBuffer());
144
+ await storage.write(ref.hash, bytes);
145
+ const now = Date.now();
146
+ await sql `
147
+ insert into ${sql.table('sync_blob_cache')} (
148
+ ${sql.join([
149
+ sql.ref('hash'),
150
+ sql.ref('size'),
151
+ sql.ref('mime_type'),
152
+ sql.ref('cached_at'),
153
+ sql.ref('last_accessed_at'),
154
+ sql.ref('encrypted'),
155
+ sql.ref('key_id'),
156
+ sql.ref('body'),
157
+ ])}
158
+ ) values (
159
+ ${sql.join([
160
+ sql.val(ref.hash),
161
+ sql.val(bytes.length),
162
+ sql.val(ref.mimeType),
163
+ sql.val(now),
164
+ sql.val(now),
165
+ sql.val(0),
166
+ sql.val(null),
167
+ sql.val(bytes),
168
+ ])}
169
+ )
170
+ on conflict (${sql.ref('hash')}) do nothing
171
+ `.execute(db);
172
+ return bytes;
173
+ },
174
+ async isLocal(hash) {
175
+ return storage.exists(hash);
176
+ },
177
+ async preload(refs) {
178
+ await Promise.all(refs.map((ref) => this.retrieve(ref)));
179
+ },
180
+ async processUploadQueue() {
181
+ let uploaded = 0;
182
+ let failed = 0;
183
+ const now = Date.now();
184
+ const staleThreshold = now - staleUploadingTimeoutMs;
185
+ await sql `
186
+ update ${sql.table('sync_blob_outbox')}
187
+ set
188
+ ${sql.ref('status')} = ${sql.val('failed')},
189
+ ${sql.ref('attempt_count')} = ${sql.ref('attempt_count')} + ${sql.val(1)},
190
+ ${sql.ref('error')} = ${sql.val('Upload timed out while in uploading state')},
191
+ ${sql.ref('updated_at')} = ${sql.val(now)}
192
+ where ${sql.ref('status')} = ${sql.val('uploading')}
193
+ and ${sql.ref('updated_at')} < ${sql.val(staleThreshold)}
194
+ and ${sql.ref('attempt_count')} + ${sql.val(1)} >= ${sql.val(maxUploadRetries)}
195
+ `.execute(db);
196
+ await sql `
197
+ update ${sql.table('sync_blob_outbox')}
198
+ set
199
+ ${sql.ref('status')} = ${sql.val('pending')},
200
+ ${sql.ref('attempt_count')} = ${sql.ref('attempt_count')} + ${sql.val(1)},
201
+ ${sql.ref('error')} = ${sql.val('Upload timed out while in uploading state; retrying')},
202
+ ${sql.ref('updated_at')} = ${sql.val(now)}
203
+ where ${sql.ref('status')} = ${sql.val('uploading')}
204
+ and ${sql.ref('updated_at')} < ${sql.val(staleThreshold)}
205
+ and ${sql.ref('attempt_count')} + ${sql.val(1)} < ${sql.val(maxUploadRetries)}
206
+ `.execute(db);
207
+ const pendingResult = await sql `
208
+ select
209
+ ${sql.ref('hash')},
210
+ ${sql.ref('size')},
211
+ ${sql.ref('mime_type')},
212
+ ${sql.ref('body')},
213
+ ${sql.ref('attempt_count')}
214
+ from ${sql.table('sync_blob_outbox')}
215
+ where ${sql.ref('status')} = ${sql.val('pending')}
216
+ and ${sql.ref('attempt_count')} < ${sql.val(maxUploadRetries)}
217
+ limit ${sql.val(10)}
218
+ `.execute(db);
219
+ const pending = pendingResult.rows;
220
+ for (const item of pending) {
221
+ const nextAttemptCount = item.attempt_count + 1;
222
+ try {
223
+ await sql `
224
+ update ${sql.table('sync_blob_outbox')}
225
+ set
226
+ ${sql.ref('status')} = ${sql.val('uploading')},
227
+ ${sql.ref('attempt_count')} = ${sql.val(nextAttemptCount)},
228
+ ${sql.ref('error')} = ${sql.val(null)},
229
+ ${sql.ref('updated_at')} = ${sql.val(Date.now())}
230
+ where ${sql.ref('hash')} = ${sql.val(item.hash)}
231
+ and ${sql.ref('status')} = ${sql.val('pending')}
232
+ `.execute(db);
233
+ const initResult = await blobs.initiateUpload({
234
+ hash: item.hash,
235
+ size: item.size,
236
+ mimeType: item.mime_type,
237
+ });
238
+ if (!initResult.exists && initResult.uploadUrl && item.body) {
239
+ const uploadBody = new ArrayBuffer(item.body.byteLength);
240
+ new Uint8Array(uploadBody).set(item.body);
241
+ const uploadResponse = await fetch(initResult.uploadUrl, {
242
+ method: initResult.uploadMethod ?? 'PUT',
243
+ body: uploadBody,
244
+ headers: initResult.uploadHeaders,
245
+ });
246
+ if (!uploadResponse.ok) {
247
+ throw new Error(`Upload failed: ${uploadResponse.statusText}`);
248
+ }
249
+ const completeResult = await blobs.completeUpload(item.hash);
250
+ if (!completeResult.ok) {
251
+ throw new Error(completeResult.error ?? 'Failed to complete blob upload');
252
+ }
253
+ }
254
+ await sql `
255
+ delete from ${sql.table('sync_blob_outbox')}
256
+ where ${sql.ref('hash')} = ${sql.val(item.hash)}
257
+ `.execute(db);
258
+ emit('blob:upload:complete', {
259
+ hash: item.hash,
260
+ size: item.size,
261
+ mimeType: item.mime_type,
262
+ });
263
+ uploaded++;
264
+ }
265
+ catch (err) {
266
+ const nextStatus = nextAttemptCount >= maxUploadRetries ? 'failed' : 'pending';
267
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
268
+ await sql `
269
+ update ${sql.table('sync_blob_outbox')}
270
+ set
271
+ ${sql.ref('status')} = ${sql.val(nextStatus)},
272
+ ${sql.ref('error')} = ${sql.val(errorMessage)},
273
+ ${sql.ref('updated_at')} = ${sql.val(Date.now())}
274
+ where ${sql.ref('hash')} = ${sql.val(item.hash)}
275
+ `.execute(db);
276
+ emit('blob:upload:error', {
277
+ hash: item.hash,
278
+ error: errorMessage,
279
+ });
280
+ if (nextStatus === 'failed') {
281
+ failed++;
282
+ }
283
+ }
284
+ }
285
+ return { uploaded, failed };
286
+ },
287
+ async getUploadQueueStats() {
288
+ const rowsResult = await sql `
289
+ select
290
+ ${sql.ref('status')} as status,
291
+ count(${sql.ref('hash')}) as count
292
+ from ${sql.table('sync_blob_outbox')}
293
+ group by ${sql.ref('status')}
294
+ `.execute(db);
295
+ const stats = { pending: 0, uploading: 0, failed: 0 };
296
+ for (const row of rowsResult.rows) {
297
+ if (row.status === 'pending')
298
+ stats.pending = Number(row.count);
299
+ if (row.status === 'uploading')
300
+ stats.uploading = Number(row.count);
301
+ if (row.status === 'failed')
302
+ stats.failed = Number(row.count);
303
+ }
304
+ return stats;
305
+ },
306
+ async getCacheStats() {
307
+ const result = await sql `
308
+ select
309
+ count(${sql.ref('hash')}) as count,
310
+ sum(${sql.ref('size')}) as totalBytes
311
+ from ${sql.table('sync_blob_cache')}
312
+ `.execute(db);
313
+ const row = result.rows[0];
314
+ return {
315
+ count: Number(row?.count ?? 0),
316
+ totalBytes: Number(row?.totalBytes ?? 0),
317
+ };
318
+ },
319
+ async pruneCache(maxBytes) {
320
+ if (!maxBytes)
321
+ return 0;
322
+ const stats = await this.getCacheStats();
323
+ if (stats.totalBytes <= maxBytes)
324
+ return 0;
325
+ const toFree = stats.totalBytes - maxBytes;
326
+ let freed = 0;
327
+ const oldEntriesResult = await sql `
328
+ select ${sql.ref('hash')}, ${sql.ref('size')}
329
+ from ${sql.table('sync_blob_cache')}
330
+ order by ${sql.ref('last_accessed_at')} asc
331
+ `.execute(db);
332
+ const oldEntries = oldEntriesResult.rows;
333
+ for (const entry of oldEntries) {
334
+ if (freed >= toFree)
335
+ break;
336
+ await storage.delete(entry.hash);
337
+ await sql `
338
+ delete from ${sql.table('sync_blob_cache')}
339
+ where ${sql.ref('hash')} = ${sql.val(entry.hash)}
340
+ `.execute(db);
341
+ freed += entry.size;
342
+ }
343
+ return freed;
344
+ },
345
+ async clearCache() {
346
+ if (storage.clear) {
347
+ await storage.clear();
348
+ }
349
+ else {
350
+ const entriesResult = await sql `
351
+ select ${sql.ref('hash')}
352
+ from ${sql.table('sync_blob_cache')}
353
+ `.execute(db);
354
+ for (const entry of entriesResult.rows) {
355
+ await storage.delete(entry.hash);
356
+ }
357
+ }
358
+ await sql `delete from ${sql.table('sync_blob_cache')}`.execute(db);
359
+ },
360
+ };
361
+ }
362
+ async function toUint8Array(data) {
363
+ if (data instanceof Uint8Array) {
364
+ return data;
365
+ }
366
+ const buffer = await data.arrayBuffer();
367
+ return new Uint8Array(buffer);
368
+ }
369
+ async function computeSha256Hex(data) {
370
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data.buffer);
371
+ const hashArray = new Uint8Array(hashBuffer);
372
+ return Array.from(hashArray)
373
+ .map((b) => b.toString(16).padStart(2, '0'))
374
+ .join('');
375
+ }
376
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { Kysely } from 'kysely';
2
+ export declare function ensureClientBlobSchema<DB>(db: Kysely<DB>): Promise<void>;
3
+ export declare function dropClientBlobSchema<DB>(db: Kysely<DB>): Promise<void>;
4
+ //# sourceMappingURL=migrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAErC,wBAAsB,sBAAsB,CAAC,EAAE,EAC7C,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,GACb,OAAO,CAAC,IAAI,CAAC,CA4Cf;AAED,wBAAsB,oBAAoB,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAG5E"}
@@ -0,0 +1,47 @@
1
+ export async function ensureClientBlobSchema(db) {
2
+ await db.schema
3
+ .createTable('sync_blob_cache')
4
+ .ifNotExists()
5
+ .addColumn('hash', 'text', (col) => col.primaryKey())
6
+ .addColumn('size', 'integer', (col) => col.notNull())
7
+ .addColumn('mime_type', 'text', (col) => col.notNull())
8
+ .addColumn('body', 'blob', (col) => col.notNull())
9
+ .addColumn('encrypted', 'integer', (col) => col.notNull().defaultTo(0))
10
+ .addColumn('key_id', 'text')
11
+ .addColumn('cached_at', 'bigint', (col) => col.notNull())
12
+ .addColumn('last_accessed_at', 'bigint', (col) => col.notNull())
13
+ .execute();
14
+ await db.schema
15
+ .createIndex('idx_sync_blob_cache_last_accessed')
16
+ .ifNotExists()
17
+ .on('sync_blob_cache')
18
+ .columns(['last_accessed_at'])
19
+ .execute();
20
+ await db.schema
21
+ .createTable('sync_blob_outbox')
22
+ .ifNotExists()
23
+ .addColumn('id', 'integer', (col) => col.primaryKey().autoIncrement())
24
+ .addColumn('hash', 'text', (col) => col.notNull().unique())
25
+ .addColumn('size', 'integer', (col) => col.notNull())
26
+ .addColumn('mime_type', 'text', (col) => col.notNull())
27
+ .addColumn('body', 'blob', (col) => col.notNull())
28
+ .addColumn('encrypted', 'integer', (col) => col.notNull().defaultTo(0))
29
+ .addColumn('key_id', 'text')
30
+ .addColumn('status', 'text', (col) => col.notNull())
31
+ .addColumn('attempt_count', 'integer', (col) => col.notNull().defaultTo(0))
32
+ .addColumn('error', 'text')
33
+ .addColumn('created_at', 'bigint', (col) => col.notNull())
34
+ .addColumn('updated_at', 'bigint', (col) => col.notNull())
35
+ .execute();
36
+ await db.schema
37
+ .createIndex('idx_sync_blob_outbox_status')
38
+ .ifNotExists()
39
+ .on('sync_blob_outbox')
40
+ .columns(['status', 'created_at'])
41
+ .execute();
42
+ }
43
+ export async function dropClientBlobSchema(db) {
44
+ await db.schema.dropTable('sync_blob_outbox').ifExists().execute();
45
+ await db.schema.dropTable('sync_blob_cache').ifExists().execute();
46
+ }
47
+ //# sourceMappingURL=migrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ import type { Generated } from 'kysely';
2
+ export interface ClientBlobStorage {
3
+ write(hash: string, data: Uint8Array | ReadableStream<Uint8Array>): Promise<void>;
4
+ read(hash: string): Promise<Uint8Array | null>;
5
+ readStream?(hash: string): Promise<ReadableStream<Uint8Array> | null>;
6
+ delete(hash: string): Promise<void>;
7
+ exists(hash: string): Promise<boolean>;
8
+ getUsage?(): Promise<number>;
9
+ clear?(): Promise<void>;
10
+ }
11
+ interface BlobStoreOptions {
12
+ mimeType?: string;
13
+ immediate?: boolean;
14
+ }
15
+ export interface BlobClient {
16
+ store(data: Blob | File | Uint8Array, options?: BlobStoreOptions): Promise<import('@syncular/core').BlobRef>;
17
+ retrieve(ref: import('@syncular/core').BlobRef): Promise<Uint8Array>;
18
+ isLocal(hash: string): Promise<boolean>;
19
+ preload(refs: import('@syncular/core').BlobRef[]): Promise<void>;
20
+ processUploadQueue(): Promise<{
21
+ uploaded: number;
22
+ failed: number;
23
+ }>;
24
+ getUploadQueueStats(): Promise<{
25
+ pending: number;
26
+ uploading: number;
27
+ failed: number;
28
+ }>;
29
+ getCacheStats(): Promise<{
30
+ count: number;
31
+ totalBytes: number;
32
+ }>;
33
+ pruneCache(maxBytes?: number): Promise<number>;
34
+ clearCache(): Promise<void>;
35
+ }
36
+ export interface SyncBlobCacheTable {
37
+ hash: string;
38
+ size: number;
39
+ mime_type: string;
40
+ body: Uint8Array;
41
+ encrypted: number;
42
+ key_id: string | null;
43
+ cached_at: number;
44
+ last_accessed_at: number;
45
+ }
46
+ export type BlobUploadStatus = 'pending' | 'uploading' | 'uploaded' | 'confirming' | 'complete' | 'failed';
47
+ export interface SyncBlobOutboxTable {
48
+ id: Generated<number>;
49
+ hash: string;
50
+ size: number;
51
+ mime_type: string;
52
+ body: Uint8Array;
53
+ encrypted: number;
54
+ key_id: string | null;
55
+ status: BlobUploadStatus;
56
+ attempt_count: number;
57
+ error: string | null;
58
+ created_at: number;
59
+ updated_at: number;
60
+ }
61
+ export interface SyncBlobClientDb {
62
+ sync_blob_cache: SyncBlobCacheTable;
63
+ sync_blob_outbox: SyncBlobOutboxTable;
64
+ }
65
+ export {};
66
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncular/client-plugin-blob",
3
- "version": "0.0.0",
3
+ "version": "0.0.6-201",
4
4
  "description": "Blob storage plugin for the Syncular client",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Benjamin Kniffler",
@@ -44,13 +44,13 @@
44
44
  "release": "bunx syncular-publish"
45
45
  },
46
46
  "dependencies": {
47
- "@syncular/client": "0.0.0",
48
- "@syncular/core": "0.0.0"
47
+ "@syncular/client": "0.0.6-201",
48
+ "@syncular/core": "0.0.6-201",
49
+ "@syncular/transport-http": "0.0.6-201"
49
50
  },
50
51
  "devDependencies": {
51
52
  "@syncular/config": "0.0.0",
52
- "kysely": "*",
53
- "kysely-bun-sqlite": "^0.4.0"
53
+ "kysely": "*"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "kysely": "*"
package/src/index.test.ts CHANGED
@@ -1,18 +1,18 @@
1
1
  import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
2
- import {
3
- createDatabase,
4
- type SyncTransport,
5
- } from '@syncular/core';
2
+ import { createDatabase, type SyncTransport } from '@syncular/core';
6
3
  import type { Kysely } from 'kysely';
7
4
  import { sql } from 'kysely';
8
- import { createBunSqliteDialect } from '../../../../packages/dialect-bun-sqlite/src';
9
- import {
10
- Client,
11
- type SyncClientDb,
12
- } from '../../../../packages/client/src';
5
+ import { Client, type SyncClientDb } from '../../../../packages/client/src';
13
6
  import type { ClientHandlerCollection } from '../../../../packages/client/src/handlers/collection';
14
7
  import { ensureClientSyncSchema } from '../../../../packages/client/src/migrate';
15
- import { createBlobPlugin, ensureClientBlobSchema, type ClientBlobStorage } from './index';
8
+ import { createBunSqliteDialect } from '../../../../packages/dialect-bun-sqlite/src';
9
+ import { createHttpTransport } from '../../../../packages/transport-http/src';
10
+ import { createWebSocketTransport } from '../../../../packages/transport-ws/src';
11
+ import {
12
+ type ClientBlobStorage,
13
+ createBlobPlugin,
14
+ ensureClientBlobSchema,
15
+ } from './index';
16
16
 
17
17
  interface TasksTable {
18
18
  id: string;
@@ -165,6 +165,59 @@ describe('blob client plugin', () => {
165
165
  expect(client.blobs).toBeDefined();
166
166
  });
167
167
 
168
+ it('attaches blob transport support for the standard HTTP transport', async () => {
169
+ const transport = createHttpTransport({
170
+ baseUrl: 'https://example.invalid',
171
+ fetch: async () =>
172
+ new Response(JSON.stringify({ ok: true }), {
173
+ headers: { 'content-type': 'application/json' },
174
+ status: 200,
175
+ }),
176
+ });
177
+
178
+ const httpClient = new Client<TestDb>({
179
+ db,
180
+ transport,
181
+ tableHandlers: [],
182
+ clientId: 'client-http',
183
+ actorId: 'u1',
184
+ subscriptions: [],
185
+ plugins: [createBlobPlugin({ storage: createMemoryBlobStorage() })],
186
+ });
187
+
188
+ expect(httpClient.blobs).toBeDefined();
189
+ expect(transport.blobs).toBeDefined();
190
+
191
+ httpClient.destroy();
192
+ });
193
+
194
+ it('attaches blob transport support for the standard WebSocket transport', async () => {
195
+ const transport = createWebSocketTransport({
196
+ baseUrl: 'https://example.invalid',
197
+ fetch: async () =>
198
+ new Response(JSON.stringify({ ok: true }), {
199
+ headers: { 'content-type': 'application/json' },
200
+ status: 200,
201
+ }),
202
+ WebSocketImpl: WebSocket,
203
+ });
204
+
205
+ const wsClient = new Client<TestDb>({
206
+ db,
207
+ transport,
208
+ tableHandlers: [],
209
+ clientId: 'client-ws',
210
+ actorId: 'u1',
211
+ subscriptions: [],
212
+ plugins: [createBlobPlugin({ storage: createMemoryBlobStorage() })],
213
+ });
214
+
215
+ expect(wsClient.blobs).toBeDefined();
216
+ expect(transport.blobs).toBeDefined();
217
+
218
+ wsClient.destroy();
219
+ });
220
+
168
221
  it('requeues stale uploading rows and uploads them on the next queue run', async () => {
169
222
  await insertBlobOutboxRow({
170
223
  hash: 'sha256:stale-upload',
package/src/index.ts CHANGED
@@ -1,8 +1,7 @@
1
+ import type { SyncClientDb, SyncClientPlugin } from '@syncular/client';
1
2
  import type { SyncTransport } from '@syncular/core';
2
- import type {
3
- SyncClientDb,
4
- SyncClientPlugin,
5
- } from '@syncular/client';
3
+ import { ensureHttpTransportBlobs } from '@syncular/transport-http/blob';
4
+ import type { Kysely } from 'kysely';
6
5
  import { sql } from 'kysely';
7
6
  import { ensureClientBlobSchema } from './migrate';
8
7
  import type { BlobClient, ClientBlobStorage } from './types';
@@ -22,22 +21,19 @@ declare module '@syncular/client' {
22
21
  }
23
22
  }
24
23
 
25
- declare module 'syncular/client' {
26
- interface SyncClientFeatureRegistry {
27
- blobs: BlobClient;
28
- }
29
- }
30
-
31
24
  export function createBlobPlugin(options: BlobPluginOptions): SyncClientPlugin {
32
25
  return {
33
26
  kind: BLOB_PLUGIN_KIND,
34
27
  name: BLOB_PLUGIN_KIND,
35
28
  setup(ctx) {
36
- if (!ctx.transport.blobs) {
29
+ const blobs =
30
+ ctx.transport.blobs ?? ensureHttpTransportBlobs(ctx.transport);
31
+ if (!blobs) {
37
32
  throw new Error(
38
33
  'Blob plugin requires a transport with blob support enabled'
39
34
  );
40
35
  }
36
+ ctx.transport.blobs = blobs;
41
37
 
42
38
  ctx.defineFeature(
43
39
  'blobs',
@@ -55,8 +51,8 @@ export function createBlobPlugin(options: BlobPluginOptions): SyncClientPlugin {
55
51
  };
56
52
  }
57
53
 
58
- function createBlobClient(args: {
59
- db: import('kysely').Kysely<SyncClientDb>;
54
+ function createBlobClient<DB extends SyncClientDb>(args: {
55
+ db: Kysely<DB>;
60
56
  transport: SyncTransport;
61
57
  storage: ClientBlobStorage;
62
58
  emit: (event: string, payload: object) => void;