@firtoz/drizzle-utils 0.2.0 → 0.3.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/CHANGELOG.md +12 -0
- package/README.md +59 -1
- package/package.json +5 -5
- package/src/collection-utils.ts +131 -14
- package/src/index.ts +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @firtoz/drizzle-utils
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`46059a2`](https://github.com/firtoz/fullstack-toolkit/commit/46059a28bd0135414b9ed022ffe162a2292adae3) Thanks [@firtoz](https://github.com/firtoz)! - Add external sync support and collection truncate utilities:
|
|
8
|
+
|
|
9
|
+
- **`ExternalSyncEvent`** / **`ExternalSyncHandler`** types for receiving sync events from external sources (e.g., proxy server)
|
|
10
|
+
- **`CollectionUtils`** interface with `truncate()` method for clearing all data from a store
|
|
11
|
+
- **`handleTruncate`** added to `SyncBackend` interface
|
|
12
|
+
- **`pushExternalSync`** exposed on `SyncFunctionResult` for pushing external sync events to collections
|
|
13
|
+
- `createSyncFunction` now returns `utils` with truncate functionality
|
|
14
|
+
|
|
3
15
|
## 0.2.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @firtoz/drizzle-utils
|
|
2
2
|
|
|
3
|
-
Shared utilities and types for Drizzle ORM-based packages. Provides type-safe table builders with automatic timestamp tracking, branded IDs,
|
|
3
|
+
Shared utilities and types for Drizzle ORM-based packages. Provides type-safe table builders with automatic timestamp tracking, branded IDs, common migration types, and collection sync utilities.
|
|
4
4
|
|
|
5
5
|
> **⚠️ Early WIP Notice:** This package is in very early development and is **not production-ready**. It is TypeScript-only and may have breaking changes. While I (the maintainer) have limited time, I'm open to PRs for features, bug fixes, or additional support (like JS builds). Please feel free to try it out and contribute! See [CONTRIBUTING.md](../../CONTRIBUTING.md) for details.
|
|
6
6
|
|
|
@@ -203,6 +203,64 @@ Comprehensive types for database migrations:
|
|
|
203
203
|
- `ViewDefinition` - Database view definition
|
|
204
204
|
- `EnumDefinition` - Enum type definition
|
|
205
205
|
|
|
206
|
+
### Collection Sync Utilities
|
|
207
|
+
|
|
208
|
+
Support for external sync and collection utilities:
|
|
209
|
+
|
|
210
|
+
#### External Sync Events
|
|
211
|
+
|
|
212
|
+
Push sync events from external sources (e.g., proxy servers) to collections:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import type { ExternalSyncEvent, ExternalSyncHandler } from "@firtoz/drizzle-utils";
|
|
216
|
+
|
|
217
|
+
// Receive sync events from a server
|
|
218
|
+
const handleSync: ExternalSyncHandler<Todo> = (event) => {
|
|
219
|
+
switch (event.type) {
|
|
220
|
+
case "insert":
|
|
221
|
+
// event.items contains new items
|
|
222
|
+
break;
|
|
223
|
+
case "update":
|
|
224
|
+
// event.items contains updated items
|
|
225
|
+
break;
|
|
226
|
+
case "delete":
|
|
227
|
+
// event.items contains deleted items
|
|
228
|
+
break;
|
|
229
|
+
case "truncate":
|
|
230
|
+
// All items should be cleared
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Push events to a collection's reactive store
|
|
236
|
+
syncResult.pushExternalSync({ type: "insert", items: [newTodo] });
|
|
237
|
+
syncResult.pushExternalSync({ type: "truncate" });
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
#### Collection Utils
|
|
241
|
+
|
|
242
|
+
The `SyncFunctionResult` includes utilities for common operations:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
// Truncate (clear all data)
|
|
246
|
+
await collection.utils.truncate();
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
#### SyncBackend Interface
|
|
250
|
+
|
|
251
|
+
For implementing custom backends, the `SyncBackend` interface includes:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
interface SyncBackend<TTable> {
|
|
255
|
+
initialLoad: (write) => Promise<void>;
|
|
256
|
+
loadSubset: (options, write) => Promise<void>;
|
|
257
|
+
handleInsert: (mutations) => Promise<T[]>;
|
|
258
|
+
handleUpdate: (mutations) => Promise<T[]>;
|
|
259
|
+
handleDelete: (mutations) => Promise<void>;
|
|
260
|
+
handleTruncate?: () => Promise<void>; // Optional truncate support
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
206
264
|
## Best Practices
|
|
207
265
|
|
|
208
266
|
### 1. Use syncableTable for Data Tables
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firtoz/drizzle-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Shared utilities and types for Drizzle-based packages",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"module": "./src/index.ts",
|
|
@@ -55,15 +55,15 @@
|
|
|
55
55
|
"access": "public"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@tanstack/db": "^0.5.
|
|
58
|
+
"@tanstack/db": "^0.5.10",
|
|
59
59
|
"drizzle-orm": "^0.44.7",
|
|
60
60
|
"drizzle-valibot": "^0.4.2",
|
|
61
|
-
"valibot": "^1.
|
|
61
|
+
"valibot": "^1.2.0"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
|
-
"@tanstack/db": "^0.5.
|
|
64
|
+
"@tanstack/db": "^0.5.10",
|
|
65
65
|
"drizzle-orm": "^0.44.7",
|
|
66
66
|
"drizzle-valibot": "^0.4.2",
|
|
67
|
-
"valibot": "^1.
|
|
67
|
+
"valibot": "^1.2.0"
|
|
68
68
|
}
|
|
69
69
|
}
|
package/src/collection-utils.ts
CHANGED
|
@@ -160,6 +160,41 @@ export interface SyncBackend<TTable extends Table> {
|
|
|
160
160
|
original: InferSchemaOutput<SelectSchema<TTable>>;
|
|
161
161
|
}>,
|
|
162
162
|
) => Promise<void>;
|
|
163
|
+
/**
|
|
164
|
+
* Handle truncate (clear all data from store)
|
|
165
|
+
* Optional - if not provided, truncate util won't be available
|
|
166
|
+
*/
|
|
167
|
+
handleTruncate?: () => Promise<void>;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* External sync event for pushing changes from outside (e.g., from a proxy server)
|
|
172
|
+
*/
|
|
173
|
+
export type ExternalSyncEvent<T> =
|
|
174
|
+
| { type: "insert"; items: T[] }
|
|
175
|
+
| { type: "update"; items: T[] }
|
|
176
|
+
| { type: "delete"; items: T[] }
|
|
177
|
+
| { type: "truncate" };
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Handler for external sync events
|
|
181
|
+
*/
|
|
182
|
+
export type ExternalSyncHandler<T> = (event: ExternalSyncEvent<T>) => void;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Collection utils that include truncate and external sync functionality
|
|
186
|
+
*/
|
|
187
|
+
export interface CollectionUtils<T = unknown> {
|
|
188
|
+
/**
|
|
189
|
+
* Clear all data from the store (truncate).
|
|
190
|
+
* This clears the backend store and updates the local reactive store.
|
|
191
|
+
*/
|
|
192
|
+
truncate: () => Promise<void>;
|
|
193
|
+
/**
|
|
194
|
+
* Push external sync events to the collection.
|
|
195
|
+
* Use this when receiving sync messages from a proxy server or other external source.
|
|
196
|
+
*/
|
|
197
|
+
pushExternalSync: ExternalSyncHandler<T>;
|
|
163
198
|
}
|
|
164
199
|
|
|
165
200
|
/**
|
|
@@ -185,6 +220,10 @@ export type SyncFunctionResult<TTable extends Table> = {
|
|
|
185
220
|
// biome-ignore lint/suspicious/noExplicitAny: Schema type parameter needs to be flexible
|
|
186
221
|
any
|
|
187
222
|
>["onDelete"];
|
|
223
|
+
/**
|
|
224
|
+
* Collection utilities including truncate and external sync
|
|
225
|
+
*/
|
|
226
|
+
utils: CollectionUtils<InferSchemaOutput<SelectSchema<TTable>>>;
|
|
188
227
|
};
|
|
189
228
|
|
|
190
229
|
/**
|
|
@@ -194,8 +233,9 @@ export function createSyncFunction<TTable extends Table>(
|
|
|
194
233
|
config: BaseSyncConfig<TTable>,
|
|
195
234
|
backend: SyncBackend<TTable>,
|
|
196
235
|
): SyncFunctionResult<TTable> {
|
|
236
|
+
type ItemType = InferSchemaOutput<SelectSchema<TTable>>;
|
|
197
237
|
type CollectionType = CollectionConfig<
|
|
198
|
-
|
|
238
|
+
ItemType,
|
|
199
239
|
string,
|
|
200
240
|
// biome-ignore lint/suspicious/noExplicitAny: Schema type parameter needs to be flexible
|
|
201
241
|
any
|
|
@@ -205,11 +245,25 @@ export function createSyncFunction<TTable extends Table>(
|
|
|
205
245
|
let updateListener: CollectionType["onUpdate"];
|
|
206
246
|
let deleteListener: CollectionType["onDelete"];
|
|
207
247
|
|
|
248
|
+
// Captured sync functions for external sync
|
|
249
|
+
let syncBegin: (() => void) | null = null;
|
|
250
|
+
let syncWrite:
|
|
251
|
+
| ((op: { type: "insert" | "update" | "delete"; value: ItemType }) => void)
|
|
252
|
+
| null = null;
|
|
253
|
+
let syncCommit: (() => void) | null = null;
|
|
254
|
+
let syncTruncate: (() => void) | null = null;
|
|
255
|
+
|
|
208
256
|
const syncFn: SyncConfig<
|
|
209
257
|
InferSchemaOutput<SelectSchema<TTable>>,
|
|
210
258
|
string
|
|
211
259
|
>["sync"] = (params) => {
|
|
212
|
-
const { begin, write, commit, markReady } = params;
|
|
260
|
+
const { begin, write, commit, markReady, truncate } = params;
|
|
261
|
+
|
|
262
|
+
// Capture sync functions for external use
|
|
263
|
+
syncBegin = begin;
|
|
264
|
+
syncWrite = write;
|
|
265
|
+
syncCommit = commit;
|
|
266
|
+
syncTruncate = truncate;
|
|
213
267
|
|
|
214
268
|
const initialSync = async () => {
|
|
215
269
|
await config.readyPromise;
|
|
@@ -315,6 +369,69 @@ export function createSyncFunction<TTable extends Table>(
|
|
|
315
369
|
} satisfies SyncConfigRes;
|
|
316
370
|
};
|
|
317
371
|
|
|
372
|
+
// External sync handler - allows pushing sync events from outside (e.g., proxy server)
|
|
373
|
+
const pushExternalSync: ExternalSyncHandler<ItemType> = (event) => {
|
|
374
|
+
if (!syncBegin || !syncWrite || !syncCommit || !syncTruncate) {
|
|
375
|
+
if (config.debug) {
|
|
376
|
+
console.warn(
|
|
377
|
+
"[pushExternalSync] Sync functions not initialized yet - event will be dropped",
|
|
378
|
+
event,
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
switch (event.type) {
|
|
385
|
+
case "insert":
|
|
386
|
+
syncBegin();
|
|
387
|
+
for (const item of event.items) {
|
|
388
|
+
syncWrite({ type: "insert", value: item });
|
|
389
|
+
}
|
|
390
|
+
syncCommit();
|
|
391
|
+
break;
|
|
392
|
+
case "update":
|
|
393
|
+
syncBegin();
|
|
394
|
+
for (const item of event.items) {
|
|
395
|
+
syncWrite({ type: "update", value: item });
|
|
396
|
+
}
|
|
397
|
+
syncCommit();
|
|
398
|
+
break;
|
|
399
|
+
case "delete":
|
|
400
|
+
syncBegin();
|
|
401
|
+
for (const item of event.items) {
|
|
402
|
+
syncWrite({ type: "delete", value: item });
|
|
403
|
+
}
|
|
404
|
+
syncCommit();
|
|
405
|
+
break;
|
|
406
|
+
case "truncate":
|
|
407
|
+
syncBegin();
|
|
408
|
+
syncTruncate();
|
|
409
|
+
syncCommit();
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// Create utils with truncate and external sync
|
|
415
|
+
const utils: CollectionUtils<ItemType> = {
|
|
416
|
+
truncate: async () => {
|
|
417
|
+
if (!backend.handleTruncate) {
|
|
418
|
+
throw new Error("Truncate not supported by this backend");
|
|
419
|
+
}
|
|
420
|
+
if (!syncBegin || !syncTruncate || !syncCommit) {
|
|
421
|
+
throw new Error(
|
|
422
|
+
"Sync functions not initialized - sync function may not have been called yet",
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
// Clear the backend store
|
|
426
|
+
await backend.handleTruncate();
|
|
427
|
+
// Update local reactive store (same pattern as insert/update/delete listeners)
|
|
428
|
+
syncBegin();
|
|
429
|
+
syncTruncate();
|
|
430
|
+
syncCommit();
|
|
431
|
+
},
|
|
432
|
+
pushExternalSync,
|
|
433
|
+
};
|
|
434
|
+
|
|
318
435
|
return {
|
|
319
436
|
sync: syncFn,
|
|
320
437
|
onInsert: async (params) => {
|
|
@@ -341,6 +458,7 @@ export function createSyncFunction<TTable extends Table>(
|
|
|
341
458
|
}
|
|
342
459
|
return deleteListener(params);
|
|
343
460
|
},
|
|
461
|
+
utils,
|
|
344
462
|
};
|
|
345
463
|
}
|
|
346
464
|
|
|
@@ -478,13 +596,17 @@ export function createCollectionConfig<
|
|
|
478
596
|
any
|
|
479
597
|
>["onDelete"];
|
|
480
598
|
syncMode?: SyncMode;
|
|
481
|
-
}):
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
599
|
+
}): Omit<
|
|
600
|
+
CollectionConfig<
|
|
601
|
+
InferSchemaOutput<SelectSchema<TTable>>,
|
|
602
|
+
string,
|
|
603
|
+
// biome-ignore lint/suspicious/noExplicitAny: Schema type parameter needs to be flexible
|
|
604
|
+
any
|
|
605
|
+
>,
|
|
606
|
+
"utils"
|
|
486
607
|
> & {
|
|
487
608
|
schema: TSchema;
|
|
609
|
+
utils: CollectionUtils<InferSchemaOutput<SelectSchema<TTable>>>;
|
|
488
610
|
} {
|
|
489
611
|
return {
|
|
490
612
|
schema: config.schema,
|
|
@@ -497,12 +619,7 @@ export function createCollectionConfig<
|
|
|
497
619
|
onUpdate: config.onUpdate ?? config.syncResult.onUpdate,
|
|
498
620
|
onDelete: config.onDelete ?? config.syncResult.onDelete,
|
|
499
621
|
syncMode: config.syncMode,
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
string,
|
|
503
|
-
// biome-ignore lint/suspicious/noExplicitAny: Schema type parameter needs to be flexible
|
|
504
|
-
any
|
|
505
|
-
> & {
|
|
506
|
-
schema: TSchema;
|
|
622
|
+
// Include utils with truncate and pushExternalSync
|
|
623
|
+
utils: config.syncResult.utils,
|
|
507
624
|
};
|
|
508
625
|
}
|