@tanstack/db 0.4.9 → 0.4.11
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/cjs/collection/events.cjs +9 -51
- package/dist/cjs/collection/events.cjs.map +1 -1
- package/dist/cjs/collection/events.d.cts +18 -7
- package/dist/cjs/collection/index.cjs +9 -12
- package/dist/cjs/collection/index.cjs.map +1 -1
- package/dist/cjs/collection/index.d.cts +13 -14
- package/dist/cjs/collection/subscription.cjs +62 -6
- package/dist/cjs/collection/subscription.cjs.map +1 -1
- package/dist/cjs/collection/subscription.d.cts +16 -3
- package/dist/cjs/collection/sync.cjs +58 -6
- package/dist/cjs/collection/sync.cjs.map +1 -1
- package/dist/cjs/collection/sync.d.cts +18 -4
- package/dist/cjs/errors.cjs +8 -0
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/errors.d.cts +6 -0
- package/dist/cjs/event-emitter.cjs +94 -0
- package/dist/cjs/event-emitter.cjs.map +1 -0
- package/dist/cjs/event-emitter.d.cts +45 -0
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/local-only.cjs.map +1 -1
- package/dist/cjs/local-only.d.cts +2 -5
- package/dist/cjs/query/compiler/index.cjs +6 -2
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.d.cts +3 -2
- package/dist/cjs/query/compiler/joins.cjs +6 -3
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/joins.d.cts +2 -2
- package/dist/cjs/query/compiler/order-by.cjs +18 -4
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +2 -1
- package/dist/cjs/query/compiler/types.d.cts +4 -0
- package/dist/cjs/query/index.d.cts +1 -0
- package/dist/cjs/query/live/collection-config-builder.cjs +43 -6
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.d.cts +27 -1
- package/dist/cjs/query/live/collection-subscriber.cjs +29 -0
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/query/live/collection-subscriber.d.cts +1 -0
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.d.cts +2 -2
- package/dist/cjs/types.d.cts +82 -11
- package/dist/esm/collection/events.d.ts +18 -7
- package/dist/esm/collection/events.js +9 -51
- package/dist/esm/collection/events.js.map +1 -1
- package/dist/esm/collection/index.d.ts +13 -14
- package/dist/esm/collection/index.js +9 -12
- package/dist/esm/collection/index.js.map +1 -1
- package/dist/esm/collection/subscription.d.ts +16 -3
- package/dist/esm/collection/subscription.js +62 -6
- package/dist/esm/collection/subscription.js.map +1 -1
- package/dist/esm/collection/sync.d.ts +18 -4
- package/dist/esm/collection/sync.js +59 -7
- package/dist/esm/collection/sync.js.map +1 -1
- package/dist/esm/errors.d.ts +6 -0
- package/dist/esm/errors.js +8 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/event-emitter.d.ts +45 -0
- package/dist/esm/event-emitter.js +94 -0
- package/dist/esm/event-emitter.js.map +1 -0
- package/dist/esm/index.js +2 -1
- package/dist/esm/local-only.d.ts +2 -5
- package/dist/esm/local-only.js.map +1 -1
- package/dist/esm/query/compiler/index.d.ts +3 -2
- package/dist/esm/query/compiler/index.js +6 -2
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/joins.d.ts +2 -2
- package/dist/esm/query/compiler/joins.js +6 -3
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +2 -1
- package/dist/esm/query/compiler/order-by.js +18 -4
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/compiler/types.d.ts +4 -0
- package/dist/esm/query/index.d.ts +1 -0
- package/dist/esm/query/live/collection-config-builder.d.ts +27 -1
- package/dist/esm/query/live/collection-config-builder.js +44 -7
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-subscriber.d.ts +1 -0
- package/dist/esm/query/live/collection-subscriber.js +29 -0
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/query/live-query-collection.d.ts +2 -2
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/types.d.ts +82 -11
- package/package.json +2 -2
- package/src/collection/events.ts +25 -74
- package/src/collection/index.ts +15 -19
- package/src/collection/subscription.ts +88 -6
- package/src/collection/sync.ts +81 -9
- package/src/errors.ts +12 -0
- package/src/event-emitter.ts +118 -0
- package/src/local-only.ts +5 -12
- package/src/query/compiler/index.ts +9 -1
- package/src/query/compiler/joins.ts +7 -1
- package/src/query/compiler/order-by.ts +23 -2
- package/src/query/compiler/types.ts +5 -0
- package/src/query/index.ts +1 -0
- package/src/query/live/collection-config-builder.ts +76 -7
- package/src/query/live/collection-subscriber.ts +50 -0
- package/src/query/live-query-collection.ts +8 -4
- package/src/types.ts +93 -11
package/dist/esm/types.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Collection } from './collection/index.js';
|
|
|
3
3
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
4
4
|
import { Transaction } from './transactions.js';
|
|
5
5
|
import { BasicExpression, OrderBy } from './query/ir.js';
|
|
6
|
+
import { EventEmitter } from './event-emitter.js';
|
|
6
7
|
/**
|
|
7
8
|
* Helper type to extract the output type from a standard schema
|
|
8
9
|
*
|
|
@@ -92,15 +93,74 @@ type Value<TExtensions = never> = string | number | boolean | bigint | null | TE
|
|
|
92
93
|
};
|
|
93
94
|
export type Row<TExtensions = never> = Record<string, Value<TExtensions>>;
|
|
94
95
|
export type OperationType = `insert` | `update` | `delete`;
|
|
95
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Subscription status values
|
|
98
|
+
*/
|
|
99
|
+
export type SubscriptionStatus = `ready` | `loadingSubset`;
|
|
100
|
+
/**
|
|
101
|
+
* Event emitted when subscription status changes
|
|
102
|
+
*/
|
|
103
|
+
export interface SubscriptionStatusChangeEvent {
|
|
104
|
+
type: `status:change`;
|
|
105
|
+
subscription: Subscription;
|
|
106
|
+
previousStatus: SubscriptionStatus;
|
|
107
|
+
status: SubscriptionStatus;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Event emitted when subscription status changes to a specific status
|
|
111
|
+
*/
|
|
112
|
+
export interface SubscriptionStatusEvent<T extends SubscriptionStatus> {
|
|
113
|
+
type: `status:${T}`;
|
|
114
|
+
subscription: Subscription;
|
|
115
|
+
previousStatus: SubscriptionStatus;
|
|
116
|
+
status: T;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Event emitted when subscription is unsubscribed
|
|
120
|
+
*/
|
|
121
|
+
export interface SubscriptionUnsubscribedEvent {
|
|
122
|
+
type: `unsubscribed`;
|
|
123
|
+
subscription: Subscription;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* All subscription events
|
|
127
|
+
*/
|
|
128
|
+
export type SubscriptionEvents = {
|
|
129
|
+
"status:change": SubscriptionStatusChangeEvent;
|
|
130
|
+
"status:ready": SubscriptionStatusEvent<`ready`>;
|
|
131
|
+
"status:loadingSubset": SubscriptionStatusEvent<`loadingSubset`>;
|
|
132
|
+
unsubscribed: SubscriptionUnsubscribedEvent;
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Public interface for a collection subscription
|
|
136
|
+
* Used by sync implementations to track subscription lifecycle
|
|
137
|
+
*/
|
|
138
|
+
export interface Subscription extends EventEmitter<SubscriptionEvents> {
|
|
139
|
+
/** Current status of the subscription */
|
|
140
|
+
readonly status: SubscriptionStatus;
|
|
141
|
+
}
|
|
142
|
+
export type LoadSubsetOptions = {
|
|
143
|
+
/** The where expression to filter the data */
|
|
96
144
|
where?: BasicExpression<boolean>;
|
|
145
|
+
/** The order by clause to sort the data */
|
|
97
146
|
orderBy?: OrderBy;
|
|
147
|
+
/** The limit of the data to load */
|
|
98
148
|
limit?: number;
|
|
149
|
+
/**
|
|
150
|
+
* The subscription that triggered the load.
|
|
151
|
+
* Advanced sync implementations can use this for:
|
|
152
|
+
* - LRU caching keyed by subscription
|
|
153
|
+
* - Reference counting to track active subscriptions
|
|
154
|
+
* - Subscribing to subscription events (e.g., finalization/unsubscribe)
|
|
155
|
+
* @optional Available when called from CollectionSubscription, may be undefined for direct calls
|
|
156
|
+
*/
|
|
157
|
+
subscription?: Subscription;
|
|
99
158
|
};
|
|
159
|
+
export type LoadSubsetFn = (options: LoadSubsetOptions) => true | Promise<void>;
|
|
100
160
|
export type CleanupFn = () => void;
|
|
101
161
|
export type SyncConfigRes = {
|
|
102
162
|
cleanup?: CleanupFn;
|
|
103
|
-
|
|
163
|
+
loadSubset?: LoadSubsetFn;
|
|
104
164
|
};
|
|
105
165
|
export interface SyncConfig<T extends object = Record<string, unknown>, TKey extends string | number = string | number> {
|
|
106
166
|
sync: (params: {
|
|
@@ -161,21 +221,21 @@ export interface InsertConfig {
|
|
|
161
221
|
/** Whether to apply optimistic updates immediately. Defaults to true. */
|
|
162
222
|
optimistic?: boolean;
|
|
163
223
|
}
|
|
164
|
-
export type UpdateMutationFnParams<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord =
|
|
224
|
+
export type UpdateMutationFnParams<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord = UtilsRecord> = {
|
|
165
225
|
transaction: TransactionWithMutations<T, `update`>;
|
|
166
226
|
collection: Collection<T, TKey, TUtils>;
|
|
167
227
|
};
|
|
168
|
-
export type InsertMutationFnParams<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord =
|
|
228
|
+
export type InsertMutationFnParams<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord = UtilsRecord> = {
|
|
169
229
|
transaction: TransactionWithMutations<T, `insert`>;
|
|
170
230
|
collection: Collection<T, TKey, TUtils>;
|
|
171
231
|
};
|
|
172
|
-
export type DeleteMutationFnParams<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord =
|
|
232
|
+
export type DeleteMutationFnParams<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord = UtilsRecord> = {
|
|
173
233
|
transaction: TransactionWithMutations<T, `delete`>;
|
|
174
234
|
collection: Collection<T, TKey, TUtils>;
|
|
175
235
|
};
|
|
176
|
-
export type InsertMutationFn<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord =
|
|
177
|
-
export type UpdateMutationFn<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord =
|
|
178
|
-
export type DeleteMutationFn<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord =
|
|
236
|
+
export type InsertMutationFn<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord = UtilsRecord, TReturn = any> = (params: InsertMutationFnParams<T, TKey, TUtils>) => Promise<TReturn>;
|
|
237
|
+
export type UpdateMutationFn<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord = UtilsRecord, TReturn = any> = (params: UpdateMutationFnParams<T, TKey, TUtils>) => Promise<TReturn>;
|
|
238
|
+
export type DeleteMutationFn<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord = UtilsRecord, TReturn = any> = (params: DeleteMutationFnParams<T, TKey, TUtils>) => Promise<TReturn>;
|
|
179
239
|
/**
|
|
180
240
|
* Collection status values for lifecycle management
|
|
181
241
|
* @example
|
|
@@ -202,7 +262,8 @@ export type CollectionStatus =
|
|
|
202
262
|
| `error`
|
|
203
263
|
/** Collection has been cleaned up and resources freed */
|
|
204
264
|
| `cleaned-up`;
|
|
205
|
-
export
|
|
265
|
+
export type SyncMode = `eager` | `on-demand`;
|
|
266
|
+
export interface BaseCollectionConfig<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TSchema extends StandardSchemaV1 = never, TUtils extends UtilsRecord = UtilsRecord, TReturn = any> {
|
|
206
267
|
id?: string;
|
|
207
268
|
schema?: TSchema;
|
|
208
269
|
/**
|
|
@@ -251,6 +312,15 @@ export interface BaseCollectionConfig<T extends object = Record<string, unknown>
|
|
|
251
312
|
* compare: (x, y) => x.createdAt.getTime() - y.createdAt.getTime()
|
|
252
313
|
*/
|
|
253
314
|
compare?: (x: T, y: T) => number;
|
|
315
|
+
/**
|
|
316
|
+
* The mode of sync to use for the collection.
|
|
317
|
+
* @default `eager`
|
|
318
|
+
* @description
|
|
319
|
+
* - `eager`: syncs all data immediately on preload
|
|
320
|
+
* - `on-demand`: syncs data in incremental snapshots when the collection is queried
|
|
321
|
+
* The exact implementation of the sync mode is up to the sync implementation.
|
|
322
|
+
*/
|
|
323
|
+
syncMode?: SyncMode;
|
|
254
324
|
/**
|
|
255
325
|
* Optional asynchronous handler function called before an insert operation
|
|
256
326
|
* @param params Object containing transaction and collection information
|
|
@@ -379,8 +449,9 @@ export interface BaseCollectionConfig<T extends object = Record<string, unknown>
|
|
|
379
449
|
* }
|
|
380
450
|
*/
|
|
381
451
|
onDelete?: DeleteMutationFn<T, TKey, TUtils, TReturn>;
|
|
452
|
+
utils?: TUtils;
|
|
382
453
|
}
|
|
383
|
-
export interface CollectionConfig<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TSchema extends StandardSchemaV1 = never> extends BaseCollectionConfig<T, TKey, TSchema> {
|
|
454
|
+
export interface CollectionConfig<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TSchema extends StandardSchemaV1 = never, TUtils extends UtilsRecord = UtilsRecord> extends BaseCollectionConfig<T, TKey, TSchema, TUtils> {
|
|
384
455
|
sync: SyncConfig<T, TKey>;
|
|
385
456
|
}
|
|
386
457
|
export type SingleResult = {
|
|
@@ -395,7 +466,7 @@ export type MaybeSingleResult = {
|
|
|
395
466
|
*/
|
|
396
467
|
singleResult?: true;
|
|
397
468
|
};
|
|
398
|
-
export type CollectionConfigSingleRowOption<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TSchema extends StandardSchemaV1 = never> = CollectionConfig<T, TKey, TSchema> & MaybeSingleResult;
|
|
469
|
+
export type CollectionConfigSingleRowOption<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TSchema extends StandardSchemaV1 = never, TUtils extends UtilsRecord = {}> = CollectionConfig<T, TKey, TSchema, TUtils> & MaybeSingleResult;
|
|
399
470
|
export type ChangesPayload<T extends object = Record<string, unknown>> = Array<ChangeMessage<T>>;
|
|
400
471
|
/**
|
|
401
472
|
* An input row from a collection
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/db",
|
|
3
3
|
"description": "A reactive client store for building super fast apps on sync",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.11",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@standard-schema/spec": "^1.0.0",
|
|
7
|
-
"@tanstack/db-ivm": "0.1.
|
|
7
|
+
"@tanstack/db-ivm": "0.1.11"
|
|
8
8
|
},
|
|
9
9
|
"devDependencies": {
|
|
10
10
|
"@vitest/coverage-istanbul": "^3.2.4",
|
package/src/collection/events.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EventEmitter } from "../event-emitter.js"
|
|
1
2
|
import type { Collection } from "./index.js"
|
|
2
3
|
import type { CollectionStatus } from "../types.js"
|
|
3
4
|
|
|
@@ -31,9 +32,21 @@ export interface CollectionSubscribersChangeEvent {
|
|
|
31
32
|
subscriberCount: number
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Event emitted when the collection's loading more state changes
|
|
37
|
+
*/
|
|
38
|
+
export interface CollectionLoadingSubsetChangeEvent {
|
|
39
|
+
type: `loadingSubset:change`
|
|
40
|
+
collection: Collection<any, any, any, any, any>
|
|
41
|
+
isLoadingSubset: boolean
|
|
42
|
+
previousIsLoadingSubset: boolean
|
|
43
|
+
loadingSubsetTransition: `start` | `end`
|
|
44
|
+
}
|
|
45
|
+
|
|
34
46
|
export type AllCollectionEvents = {
|
|
35
47
|
"status:change": CollectionStatusChangeEvent
|
|
36
48
|
"subscribers:change": CollectionSubscribersChangeEvent
|
|
49
|
+
"loadingSubset:change": CollectionLoadingSubsetChangeEvent
|
|
37
50
|
} & {
|
|
38
51
|
[K in CollectionStatus as `status:${K}`]: CollectionStatusEvent<K>
|
|
39
52
|
}
|
|
@@ -42,94 +55,32 @@ export type CollectionEvent =
|
|
|
42
55
|
| AllCollectionEvents[keyof AllCollectionEvents]
|
|
43
56
|
| CollectionStatusChangeEvent
|
|
44
57
|
| CollectionSubscribersChangeEvent
|
|
58
|
+
| CollectionLoadingSubsetChangeEvent
|
|
45
59
|
|
|
46
60
|
export type CollectionEventHandler<T extends keyof AllCollectionEvents> = (
|
|
47
61
|
event: AllCollectionEvents[T]
|
|
48
62
|
) => void
|
|
49
63
|
|
|
50
|
-
export class CollectionEventsManager {
|
|
64
|
+
export class CollectionEventsManager extends EventEmitter<AllCollectionEvents> {
|
|
51
65
|
private collection!: Collection<any, any, any, any, any>
|
|
52
|
-
private listeners = new Map<
|
|
53
|
-
keyof AllCollectionEvents,
|
|
54
|
-
Set<CollectionEventHandler<any>>
|
|
55
|
-
>()
|
|
56
66
|
|
|
57
|
-
constructor() {
|
|
67
|
+
constructor() {
|
|
68
|
+
super()
|
|
69
|
+
}
|
|
58
70
|
|
|
59
71
|
setDeps(deps: { collection: Collection<any, any, any, any, any> }) {
|
|
60
72
|
this.collection = deps.collection
|
|
61
73
|
}
|
|
62
74
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (!this.listeners.has(event)) {
|
|
68
|
-
this.listeners.set(event, new Set())
|
|
69
|
-
}
|
|
70
|
-
this.listeners.get(event)!.add(callback)
|
|
71
|
-
|
|
72
|
-
return () => {
|
|
73
|
-
this.listeners.get(event)?.delete(callback)
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
once<T extends keyof AllCollectionEvents>(
|
|
78
|
-
event: T,
|
|
79
|
-
callback: CollectionEventHandler<T>
|
|
80
|
-
) {
|
|
81
|
-
const unsubscribe = this.on(event, (eventPayload) => {
|
|
82
|
-
callback(eventPayload)
|
|
83
|
-
unsubscribe()
|
|
84
|
-
})
|
|
85
|
-
return unsubscribe
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
off<T extends keyof AllCollectionEvents>(
|
|
89
|
-
event: T,
|
|
90
|
-
callback: CollectionEventHandler<T>
|
|
91
|
-
) {
|
|
92
|
-
this.listeners.get(event)?.delete(callback)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
waitFor<T extends keyof AllCollectionEvents>(
|
|
96
|
-
event: T,
|
|
97
|
-
timeout?: number
|
|
98
|
-
): Promise<AllCollectionEvents[T]> {
|
|
99
|
-
return new Promise((resolve, reject) => {
|
|
100
|
-
let timeoutId: NodeJS.Timeout | undefined
|
|
101
|
-
const unsubscribe = this.on(event, (eventPayload) => {
|
|
102
|
-
if (timeoutId) {
|
|
103
|
-
clearTimeout(timeoutId)
|
|
104
|
-
timeoutId = undefined
|
|
105
|
-
}
|
|
106
|
-
resolve(eventPayload)
|
|
107
|
-
unsubscribe()
|
|
108
|
-
})
|
|
109
|
-
if (timeout) {
|
|
110
|
-
timeoutId = setTimeout(() => {
|
|
111
|
-
timeoutId = undefined
|
|
112
|
-
unsubscribe()
|
|
113
|
-
reject(new Error(`Timeout waiting for event ${event}`))
|
|
114
|
-
}, timeout)
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
|
-
}
|
|
118
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Emit an event to all listeners
|
|
77
|
+
* Public API for emitting collection events
|
|
78
|
+
*/
|
|
119
79
|
emit<T extends keyof AllCollectionEvents>(
|
|
120
80
|
event: T,
|
|
121
81
|
eventPayload: AllCollectionEvents[T]
|
|
122
|
-
) {
|
|
123
|
-
this.
|
|
124
|
-
try {
|
|
125
|
-
listener(eventPayload)
|
|
126
|
-
} catch (error) {
|
|
127
|
-
// Re-throw in a microtask to surface the error
|
|
128
|
-
queueMicrotask(() => {
|
|
129
|
-
throw error
|
|
130
|
-
})
|
|
131
|
-
}
|
|
132
|
-
})
|
|
82
|
+
): void {
|
|
83
|
+
this.emitInner(event, eventPayload)
|
|
133
84
|
}
|
|
134
85
|
|
|
135
86
|
emitStatusChange<T extends CollectionStatus>(
|
|
@@ -166,6 +117,6 @@ export class CollectionEventsManager {
|
|
|
166
117
|
}
|
|
167
118
|
|
|
168
119
|
cleanup() {
|
|
169
|
-
this.
|
|
120
|
+
this.clearListeners()
|
|
170
121
|
}
|
|
171
122
|
}
|
package/src/collection/index.ts
CHANGED
|
@@ -25,7 +25,6 @@ import type {
|
|
|
25
25
|
InferSchemaOutput,
|
|
26
26
|
InsertConfig,
|
|
27
27
|
NonSingleResult,
|
|
28
|
-
OnLoadMoreOptions,
|
|
29
28
|
OperationConfig,
|
|
30
29
|
SingleResult,
|
|
31
30
|
SubscribeChangesOptions,
|
|
@@ -48,7 +47,7 @@ import type { IndexProxy } from "../indexes/lazy-index.js"
|
|
|
48
47
|
export interface Collection<
|
|
49
48
|
T extends object = Record<string, unknown>,
|
|
50
49
|
TKey extends string | number = string | number,
|
|
51
|
-
TUtils extends UtilsRecord =
|
|
50
|
+
TUtils extends UtilsRecord = UtilsRecord,
|
|
52
51
|
TSchema extends StandardSchemaV1 = StandardSchemaV1,
|
|
53
52
|
TInsertInput extends object = T,
|
|
54
53
|
> extends CollectionImpl<T, TKey, TUtils, TSchema, TInsertInput> {
|
|
@@ -131,7 +130,7 @@ export interface Collection<
|
|
|
131
130
|
export function createCollection<
|
|
132
131
|
T extends StandardSchemaV1,
|
|
133
132
|
TKey extends string | number = string | number,
|
|
134
|
-
TUtils extends UtilsRecord =
|
|
133
|
+
TUtils extends UtilsRecord = UtilsRecord,
|
|
135
134
|
>(
|
|
136
135
|
options: CollectionConfig<InferSchemaOutput<T>, TKey, T> & {
|
|
137
136
|
schema: T
|
|
@@ -144,7 +143,7 @@ export function createCollection<
|
|
|
144
143
|
export function createCollection<
|
|
145
144
|
T extends StandardSchemaV1,
|
|
146
145
|
TKey extends string | number = string | number,
|
|
147
|
-
TUtils extends UtilsRecord =
|
|
146
|
+
TUtils extends UtilsRecord = UtilsRecord,
|
|
148
147
|
>(
|
|
149
148
|
options: CollectionConfig<InferSchemaOutput<T>, TKey, T> & {
|
|
150
149
|
schema: T
|
|
@@ -158,7 +157,7 @@ export function createCollection<
|
|
|
158
157
|
export function createCollection<
|
|
159
158
|
T extends object,
|
|
160
159
|
TKey extends string | number = string | number,
|
|
161
|
-
TUtils extends UtilsRecord =
|
|
160
|
+
TUtils extends UtilsRecord = UtilsRecord,
|
|
162
161
|
>(
|
|
163
162
|
options: CollectionConfig<T, TKey, never> & {
|
|
164
163
|
schema?: never // prohibit schema if an explicit type is provided
|
|
@@ -171,7 +170,7 @@ export function createCollection<
|
|
|
171
170
|
export function createCollection<
|
|
172
171
|
T extends object,
|
|
173
172
|
TKey extends string | number = string | number,
|
|
174
|
-
TUtils extends UtilsRecord =
|
|
173
|
+
TUtils extends UtilsRecord = UtilsRecord,
|
|
175
174
|
>(
|
|
176
175
|
options: CollectionConfig<T, TKey, never> & {
|
|
177
176
|
schema?: never // prohibit schema if an explicit type is provided
|
|
@@ -218,7 +217,7 @@ export class CollectionImpl<
|
|
|
218
217
|
private _events: CollectionEventsManager
|
|
219
218
|
private _changes: CollectionChangesManager<TOutput, TKey, TSchema, TInput>
|
|
220
219
|
public _lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>
|
|
221
|
-
|
|
220
|
+
public _sync: CollectionSyncManager<TOutput, TKey, TSchema, TInput>
|
|
222
221
|
private _indexes: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>
|
|
223
222
|
private _mutations: CollectionMutationsManager<
|
|
224
223
|
TOutput,
|
|
@@ -303,6 +302,7 @@ export class CollectionImpl<
|
|
|
303
302
|
collection: this, // Required for passing to config.sync callback
|
|
304
303
|
state: this._state,
|
|
305
304
|
lifecycle: this._lifecycle,
|
|
305
|
+
events: this._events,
|
|
306
306
|
})
|
|
307
307
|
|
|
308
308
|
// Only start sync immediately if explicitly enabled
|
|
@@ -356,23 +356,19 @@ export class CollectionImpl<
|
|
|
356
356
|
}
|
|
357
357
|
|
|
358
358
|
/**
|
|
359
|
-
*
|
|
360
|
-
*
|
|
359
|
+
* Check if the collection is currently loading more data
|
|
360
|
+
* @returns true if the collection has pending load more operations, false otherwise
|
|
361
361
|
*/
|
|
362
|
-
public
|
|
363
|
-
this._sync.
|
|
362
|
+
public get isLoadingSubset(): boolean {
|
|
363
|
+
return this._sync.isLoadingSubset
|
|
364
364
|
}
|
|
365
365
|
|
|
366
366
|
/**
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
* @returns If data loading is asynchronous, this method returns a promise that resolves when the data is loaded.
|
|
370
|
-
* If data loading is synchronous, the data is loaded when the method returns.
|
|
367
|
+
* Start sync immediately - internal method for compiled queries
|
|
368
|
+
* This bypasses lazy loading for special cases like live query results
|
|
371
369
|
*/
|
|
372
|
-
public
|
|
373
|
-
|
|
374
|
-
return this._sync.syncOnLoadMoreFn(options)
|
|
375
|
-
}
|
|
370
|
+
public startSyncImmediate(): void {
|
|
371
|
+
this._sync.startSync()
|
|
376
372
|
}
|
|
377
373
|
|
|
378
374
|
/**
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { ensureIndexForExpression } from "../indexes/auto-index.js"
|
|
2
2
|
import { and, gt, lt } from "../query/builder/functions.js"
|
|
3
3
|
import { Value } from "../query/ir.js"
|
|
4
|
+
import { EventEmitter } from "../event-emitter.js"
|
|
4
5
|
import {
|
|
5
6
|
createFilterFunctionFromExpression,
|
|
6
7
|
createFilteredCallback,
|
|
7
8
|
} from "./change-events.js"
|
|
8
9
|
import type { BasicExpression, OrderBy } from "../query/ir.js"
|
|
9
10
|
import type { IndexInterface } from "../indexes/base-index.js"
|
|
10
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
ChangeMessage,
|
|
13
|
+
Subscription,
|
|
14
|
+
SubscriptionEvents,
|
|
15
|
+
SubscriptionStatus,
|
|
16
|
+
SubscriptionUnsubscribedEvent,
|
|
17
|
+
} from "../types.js"
|
|
11
18
|
import type { CollectionImpl } from "./index.js"
|
|
12
19
|
|
|
13
20
|
type RequestSnapshotOptions = {
|
|
@@ -22,13 +29,17 @@ type RequestLimitedSnapshotOptions = {
|
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
type CollectionSubscriptionOptions = {
|
|
32
|
+
includeInitialState?: boolean
|
|
25
33
|
/** Pre-compiled expression for filtering changes */
|
|
26
34
|
whereExpression?: BasicExpression<boolean>
|
|
27
35
|
/** Callback to call when the subscription is unsubscribed */
|
|
28
|
-
onUnsubscribe?: () => void
|
|
36
|
+
onUnsubscribe?: (event: SubscriptionUnsubscribedEvent) => void
|
|
29
37
|
}
|
|
30
38
|
|
|
31
|
-
export class CollectionSubscription
|
|
39
|
+
export class CollectionSubscription
|
|
40
|
+
extends EventEmitter<SubscriptionEvents>
|
|
41
|
+
implements Subscription
|
|
42
|
+
{
|
|
32
43
|
private loadedInitialState = false
|
|
33
44
|
|
|
34
45
|
// Flag to indicate that we have sent at least 1 snapshot.
|
|
@@ -42,11 +53,24 @@ export class CollectionSubscription {
|
|
|
42
53
|
|
|
43
54
|
private orderByIndex: IndexInterface<string | number> | undefined
|
|
44
55
|
|
|
56
|
+
// Status tracking
|
|
57
|
+
private _status: SubscriptionStatus = `ready`
|
|
58
|
+
private pendingLoadSubsetPromises: Set<Promise<void>> = new Set()
|
|
59
|
+
|
|
60
|
+
public get status(): SubscriptionStatus {
|
|
61
|
+
return this._status
|
|
62
|
+
}
|
|
63
|
+
|
|
45
64
|
constructor(
|
|
46
65
|
private collection: CollectionImpl<any, any, any, any, any>,
|
|
47
66
|
private callback: (changes: Array<ChangeMessage<any, any>>) => void,
|
|
48
67
|
private options: CollectionSubscriptionOptions
|
|
49
68
|
) {
|
|
69
|
+
super()
|
|
70
|
+
if (options.onUnsubscribe) {
|
|
71
|
+
this.on(`unsubscribed`, (event) => options.onUnsubscribe!(event))
|
|
72
|
+
}
|
|
73
|
+
|
|
50
74
|
// Auto-index for where expressions if enabled
|
|
51
75
|
if (options.whereExpression) {
|
|
52
76
|
ensureIndexForExpression(options.whereExpression, this.collection)
|
|
@@ -71,6 +95,53 @@ export class CollectionSubscription {
|
|
|
71
95
|
this.orderByIndex = index
|
|
72
96
|
}
|
|
73
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Set subscription status and emit events if changed
|
|
100
|
+
*/
|
|
101
|
+
private setStatus(newStatus: SubscriptionStatus) {
|
|
102
|
+
if (this._status === newStatus) {
|
|
103
|
+
return // No change
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const previousStatus = this._status
|
|
107
|
+
this._status = newStatus
|
|
108
|
+
|
|
109
|
+
// Emit status:change event
|
|
110
|
+
this.emitInner(`status:change`, {
|
|
111
|
+
type: `status:change`,
|
|
112
|
+
subscription: this,
|
|
113
|
+
previousStatus,
|
|
114
|
+
status: newStatus,
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
// Emit specific status event
|
|
118
|
+
const eventKey: `status:${SubscriptionStatus}` = `status:${newStatus}`
|
|
119
|
+
this.emitInner(eventKey, {
|
|
120
|
+
type: eventKey,
|
|
121
|
+
subscription: this,
|
|
122
|
+
previousStatus,
|
|
123
|
+
status: newStatus,
|
|
124
|
+
} as SubscriptionEvents[typeof eventKey])
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Track a loadSubset promise and manage loading status
|
|
129
|
+
*/
|
|
130
|
+
private trackLoadSubsetPromise(syncResult: Promise<void> | true) {
|
|
131
|
+
// Track the promise if it's actually a promise (async work)
|
|
132
|
+
if (syncResult instanceof Promise) {
|
|
133
|
+
this.pendingLoadSubsetPromises.add(syncResult)
|
|
134
|
+
this.setStatus(`loadingSubset`)
|
|
135
|
+
|
|
136
|
+
syncResult.finally(() => {
|
|
137
|
+
this.pendingLoadSubsetPromises.delete(syncResult)
|
|
138
|
+
if (this.pendingLoadSubsetPromises.size === 0) {
|
|
139
|
+
this.setStatus(`ready`)
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
74
145
|
hasLoadedInitialState() {
|
|
75
146
|
return this.loadedInitialState
|
|
76
147
|
}
|
|
@@ -121,10 +192,13 @@ export class CollectionSubscription {
|
|
|
121
192
|
|
|
122
193
|
// Request the sync layer to load more data
|
|
123
194
|
// don't await it, we will load the data into the collection when it comes in
|
|
124
|
-
this.collection.
|
|
195
|
+
const syncResult = this.collection._sync.loadSubset({
|
|
125
196
|
where: stateOpts.where,
|
|
197
|
+
subscription: this,
|
|
126
198
|
})
|
|
127
199
|
|
|
200
|
+
this.trackLoadSubsetPromise(syncResult)
|
|
201
|
+
|
|
128
202
|
// Also load data immediately from the collection
|
|
129
203
|
const snapshot = this.collection.currentStateAsChanges(stateOpts)
|
|
130
204
|
|
|
@@ -215,11 +289,14 @@ export class CollectionSubscription {
|
|
|
215
289
|
|
|
216
290
|
// Request the sync layer to load more data
|
|
217
291
|
// don't await it, we will load the data into the collection when it comes in
|
|
218
|
-
this.collection.
|
|
292
|
+
const syncResult = this.collection._sync.loadSubset({
|
|
219
293
|
where: whereWithValueFilter,
|
|
220
294
|
limit,
|
|
221
295
|
orderBy,
|
|
296
|
+
subscription: this,
|
|
222
297
|
})
|
|
298
|
+
|
|
299
|
+
this.trackLoadSubsetPromise(syncResult)
|
|
223
300
|
}
|
|
224
301
|
|
|
225
302
|
/**
|
|
@@ -264,6 +341,11 @@ export class CollectionSubscription {
|
|
|
264
341
|
}
|
|
265
342
|
|
|
266
343
|
unsubscribe() {
|
|
267
|
-
this.
|
|
344
|
+
this.emitInner(`unsubscribed`, {
|
|
345
|
+
type: `unsubscribed`,
|
|
346
|
+
subscription: this,
|
|
347
|
+
})
|
|
348
|
+
// Clear all event listeners to prevent memory leaks
|
|
349
|
+
this.clearListeners()
|
|
268
350
|
}
|
|
269
351
|
}
|