@@ -12,15 +12,17 @@ import type {
12
12
OperationConfig ,
13
13
OptimisticChangeMessage ,
14
14
PendingMutation ,
15
+ ResolveInsertInput ,
15
16
ResolveType ,
16
17
StandardSchema ,
17
18
Transaction as TransactionType ,
19
+ TransactionWithMutations ,
18
20
UtilsRecord ,
19
21
} from "./types"
20
22
import type { StandardSchemaV1 } from "@standard-schema/spec"
21
23
22
24
// Store collections in memory
23
- export const collectionsStore = new Map < string , CollectionImpl < any , any > > ( )
25
+ export const collectionsStore = new Map < string , CollectionImpl < any , any , any > > ( )
24
26
25
27
interface PendingSyncedTransaction < T extends object = Record < string , unknown > > {
26
28
committed : boolean
@@ -32,12 +34,15 @@ interface PendingSyncedTransaction<T extends object = Record<string, unknown>> {
32
34
* @template T - The type of items in the collection
33
35
* @template TKey - The type of the key for the collection
34
36
* @template TUtils - The utilities record type
37
+ * @template TInsertInput - The type for insert operations (can be different from T for schemas with defaults)
35
38
*/
36
39
export interface Collection <
37
40
T extends object = Record < string , unknown > ,
38
41
TKey extends string | number = string | number ,
39
42
TUtils extends UtilsRecord = { } ,
40
- > extends CollectionImpl < T , TKey > {
43
+ TSchema extends StandardSchemaV1 = StandardSchemaV1 ,
44
+ TInsertInput extends object = T ,
45
+ > extends CollectionImpl < T , TKey , TUtils , TSchema , TInsertInput > {
41
46
readonly utils : TUtils
42
47
}
43
48
@@ -124,12 +129,22 @@ export function createCollection<
124
129
options : CollectionConfig <
125
130
ResolveType < TExplicit , TSchema , TFallback > ,
126
131
TKey ,
127
- TSchema
132
+ TSchema ,
133
+ ResolveInsertInput < TExplicit , TSchema , TFallback >
128
134
> & { utils ?: TUtils }
129
- ) : Collection < ResolveType < TExplicit , TSchema , TFallback > , TKey , TUtils > {
135
+ ) : Collection <
136
+ ResolveType < TExplicit , TSchema , TFallback > ,
137
+ TKey ,
138
+ TUtils ,
139
+ TSchema ,
140
+ ResolveInsertInput < TExplicit , TSchema , TFallback >
141
+ > {
130
142
const collection = new CollectionImpl <
131
143
ResolveType < TExplicit , TSchema , TFallback > ,
132
- TKey
144
+ TKey ,
145
+ TUtils ,
146
+ TSchema ,
147
+ ResolveInsertInput < TExplicit , TSchema , TFallback >
133
148
> ( options )
134
149
135
150
// Copy utils to both top level and .utils namespace
@@ -142,7 +157,9 @@ export function createCollection<
142
157
return collection as Collection <
143
158
ResolveType < TExplicit , TSchema , TFallback > ,
144
159
TKey ,
145
- TUtils
160
+ TUtils ,
161
+ TSchema ,
162
+ ResolveInsertInput < TExplicit , TSchema , TFallback >
146
163
>
147
164
}
148
165
@@ -179,8 +196,10 @@ export class CollectionImpl<
179
196
T extends object = Record < string , unknown > ,
180
197
TKey extends string | number = string | number ,
181
198
TUtils extends UtilsRecord = { } ,
199
+ TSchema extends StandardSchemaV1 = StandardSchemaV1 ,
200
+ TInsertInput extends object = T ,
182
201
> {
183
- public config : CollectionConfig < T , TKey , any >
202
+ public config : CollectionConfig < T , TKey , TSchema , TInsertInput >
184
203
185
204
// Core state - make public for testing
186
205
public transactions : SortedMap < string , Transaction < any > >
@@ -312,7 +331,7 @@ export class CollectionImpl<
312
331
* @param config - Configuration object for the collection
313
332
* @throws Error if sync config is missing
314
333
*/
315
- constructor ( config : CollectionConfig < T , TKey , any > ) {
334
+ constructor ( config : CollectionConfig < T , TKey , TSchema , TInsertInput > ) {
316
335
// eslint-disable-next-line
317
336
if ( ! config ) {
318
337
throw new Error ( `Collection requires a config` )
@@ -1322,9 +1341,11 @@ export class CollectionImpl<
1322
1341
* console.log('Insert failed:', error)
1323
1342
* }
1324
1343
*/
1325
- insert = ( data : T | Array < T > , config ?: InsertConfig ) => {
1344
+ insert = (
1345
+ data : TInsertInput | Array < TInsertInput > ,
1346
+ config ?: InsertConfig
1347
+ ) => {
1326
1348
this . validateCollectionUsable ( `insert` )
1327
-
1328
1349
const ambientTransaction = getActiveTransaction ( )
1329
1350
1330
1351
// If no ambient transaction exists, check for an onInsert handler early
@@ -1335,15 +1356,15 @@ export class CollectionImpl<
1335
1356
}
1336
1357
1337
1358
const items = Array . isArray ( data ) ? data : [ data ]
1338
- const mutations : Array < PendingMutation < T , `insert` > > = [ ]
1359
+ const mutations : Array < PendingMutation < T > > = [ ]
1339
1360
1340
1361
// Create mutations for each item
1341
1362
items . forEach ( ( item ) => {
1342
1363
// Validate the data against the schema if one exists
1343
1364
const validatedData = this . validateData ( item , `insert` )
1344
1365
1345
1366
// Check if an item with this ID already exists in the collection
1346
- const key = this . getKeyFromItem ( item )
1367
+ const key = this . getKeyFromItem ( validatedData )
1347
1368
if ( this . has ( key ) ) {
1348
1369
throw `Cannot insert document with ID "${ key } " because it already exists in the collection`
1349
1370
}
@@ -1353,7 +1374,15 @@ export class CollectionImpl<
1353
1374
mutationId : crypto . randomUUID ( ) ,
1354
1375
original : { } ,
1355
1376
modified : validatedData ,
1356
- changes : validatedData ,
1377
+ // Pick the values from validatedData based on what's passed in - this is for cases
1378
+ // where a schema has default values. The validated data has the extra default
1379
+ // values but for changes, we just want to show the data that was actually passed in.
1380
+ changes : Object . fromEntries (
1381
+ Object . keys ( item ) . map ( ( k ) => [
1382
+ k ,
1383
+ validatedData [ k as keyof typeof validatedData ] ,
1384
+ ] )
1385
+ ) as TInsertInput ,
1357
1386
globalKey,
1358
1387
key,
1359
1388
metadata : config ?. metadata as unknown ,
@@ -1381,8 +1410,12 @@ export class CollectionImpl<
1381
1410
const directOpTransaction = createTransaction < T > ( {
1382
1411
mutationFn : async ( params ) => {
1383
1412
// Call the onInsert handler with the transaction and collection
1384
- return this . config . onInsert ! ( {
1385
- ...params ,
1413
+ return await this . config . onInsert ! ( {
1414
+ transaction :
1415
+ params . transaction as unknown as TransactionWithMutations <
1416
+ TInsertInput ,
1417
+ `insert`
1418
+ > ,
1386
1419
collection : this as unknown as Collection < T , TKey , TUtils > ,
1387
1420
} )
1388
1421
} ,
@@ -1526,7 +1559,7 @@ export class CollectionImpl<
1526
1559
}
1527
1560
1528
1561
// Create mutations for each object that has changes
1529
- const mutations : Array < PendingMutation < T , `update`> > = keysArray
1562
+ const mutations : Array < PendingMutation < T , `update`, this > > = keysArray
1530
1563
. map ( ( key , index ) => {
1531
1564
const itemChanges = changesArray [ index ] // User-provided changes for this specific item
1532
1565
@@ -1581,7 +1614,7 @@ export class CollectionImpl<
1581
1614
collection : this ,
1582
1615
}
1583
1616
} )
1584
- . filter ( Boolean ) as Array < PendingMutation < T , `update`> >
1617
+ . filter ( Boolean ) as Array < PendingMutation < T , `update`, this > >
1585
1618
1586
1619
// If no changes were made, return an empty transaction early
1587
1620
if ( mutations . length === 0 ) {
@@ -1609,7 +1642,11 @@ export class CollectionImpl<
1609
1642
mutationFn : async ( params ) => {
1610
1643
// Call the onUpdate handler with the transaction and collection
1611
1644
return this . config . onUpdate ! ( {
1612
- ...params ,
1645
+ transaction :
1646
+ params . transaction as unknown as TransactionWithMutations <
1647
+ T ,
1648
+ `update`
1649
+ > ,
1613
1650
collection : this as unknown as Collection < T , TKey , TUtils > ,
1614
1651
} )
1615
1652
} ,
@@ -1677,7 +1714,7 @@ export class CollectionImpl<
1677
1714
}
1678
1715
1679
1716
const keysArray = Array . isArray ( keys ) ? keys : [ keys ]
1680
- const mutations : Array < PendingMutation < T , `delete`> > = [ ]
1717
+ const mutations : Array < PendingMutation < T , `delete`, this > > = [ ]
1681
1718
1682
1719
for ( const key of keysArray ) {
1683
1720
if ( ! this . has ( key ) ) {
@@ -1686,7 +1723,7 @@ export class CollectionImpl<
1686
1723
)
1687
1724
}
1688
1725
const globalKey = this . generateGlobalKey ( key , this . get ( key ) ! )
1689
- const mutation : PendingMutation < T , `delete`> = {
1726
+ const mutation : PendingMutation < T , `delete`, this > = {
1690
1727
mutationId : crypto . randomUUID ( ) ,
1691
1728
original : this . get ( key ) ! ,
1692
1729
modified : this . get ( key ) ! ,
@@ -1724,7 +1761,11 @@ export class CollectionImpl<
1724
1761
mutationFn : async ( params ) => {
1725
1762
// Call the onDelete handler with the transaction and collection
1726
1763
return this . config . onDelete ! ( {
1727
- ...params ,
1764
+ transaction :
1765
+ params . transaction as unknown as TransactionWithMutations <
1766
+ T ,
1767
+ `delete`
1768
+ > ,
1728
1769
collection : this as unknown as Collection < T , TKey , TUtils > ,
1729
1770
} )
1730
1771
} ,
0 commit comments