@byearlybird/starling 0.17.3 → 0.19.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/README.md +64 -96
- package/dist/index.d.ts +63 -178
- package/dist/index.js +337 -519
- package/dist/index.js.map +1 -1
- package/package.json +20 -17
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ Starling is a CRDT (conflict-free replicated data type) library that provides au
|
|
|
10
10
|
npm install @byearlybird/starling
|
|
11
11
|
# or
|
|
12
12
|
pnpm add @byearlybird/starling
|
|
13
|
+
# or
|
|
14
|
+
bun add @byearlybird/starling
|
|
13
15
|
```
|
|
14
16
|
|
|
15
17
|
Requires TypeScript 5 or higher.
|
|
@@ -17,20 +19,14 @@ Requires TypeScript 5 or higher.
|
|
|
17
19
|
## Quick Example
|
|
18
20
|
|
|
19
21
|
```typescript
|
|
20
|
-
import { createStore
|
|
21
|
-
import { z } from "zod";
|
|
22
|
-
|
|
23
|
-
const userSchema = z.object({
|
|
24
|
-
id: z.string(),
|
|
25
|
-
name: z.string(),
|
|
26
|
-
});
|
|
22
|
+
import { createStore } from "@byearlybird/starling";
|
|
27
23
|
|
|
28
24
|
const store = createStore({
|
|
29
|
-
users:
|
|
25
|
+
users: (data: { id: string; name: string }) => data.id,
|
|
30
26
|
});
|
|
31
27
|
|
|
32
|
-
store.put(
|
|
33
|
-
const user = store.get("
|
|
28
|
+
store.users.put({ id: "1", name: "Alice" });
|
|
29
|
+
const user = store.users.get("1"); // { id: "1", name: "Alice" }
|
|
34
30
|
```
|
|
35
31
|
|
|
36
32
|
## Features
|
|
@@ -39,7 +35,6 @@ const user = store.get("users", "1"); // { id: "1", name: "Alice" }
|
|
|
39
35
|
- **Fast and synchronous**: All operations are in-memory and synchronous
|
|
40
36
|
- **Framework agnostic**: Works with React, Vue, Svelte, or vanilla JS
|
|
41
37
|
- **Type-safe**: Full TypeScript support with type inference
|
|
42
|
-
- **Schema validation**: Works with Zod, Valibot, ArkType, and more
|
|
43
38
|
- **Merge snapshots**: Sync data between devices or users easily
|
|
44
39
|
- **Change events**: Listen to data changes and integrate with your reactive system
|
|
45
40
|
|
|
@@ -47,28 +42,17 @@ const user = store.get("users", "1"); // { id: "1", name: "Alice" }
|
|
|
47
42
|
|
|
48
43
|
### Creating a Store
|
|
49
44
|
|
|
50
|
-
A store holds one or more collections. Each collection
|
|
45
|
+
A store holds one or more collections. Each collection is defined by a function that extracts the document ID.
|
|
51
46
|
|
|
52
47
|
```typescript
|
|
53
|
-
import { createStore
|
|
54
|
-
|
|
48
|
+
import { createStore } from "@byearlybird/starling";
|
|
49
|
+
|
|
50
|
+
type User = { id: string; name: string; email?: string };
|
|
51
|
+
type Note = { id: string; content: string };
|
|
55
52
|
|
|
56
53
|
const store = createStore({
|
|
57
|
-
users:
|
|
58
|
-
|
|
59
|
-
id: z.string(),
|
|
60
|
-
name: z.string(),
|
|
61
|
-
email: z.string().optional(),
|
|
62
|
-
}),
|
|
63
|
-
(data) => data.id,
|
|
64
|
-
),
|
|
65
|
-
notes: collection(
|
|
66
|
-
z.object({
|
|
67
|
-
id: z.string(),
|
|
68
|
-
content: z.string(),
|
|
69
|
-
}),
|
|
70
|
-
(data) => data.id,
|
|
71
|
-
),
|
|
54
|
+
users: (data: User) => data.id,
|
|
55
|
+
notes: (data: Note) => data.id,
|
|
72
56
|
});
|
|
73
57
|
```
|
|
74
58
|
|
|
@@ -77,7 +61,7 @@ const store = createStore({
|
|
|
77
61
|
Add new items to a collection using `put()`:
|
|
78
62
|
|
|
79
63
|
```typescript
|
|
80
|
-
store.put(
|
|
64
|
+
store.users.put({
|
|
81
65
|
id: "1",
|
|
82
66
|
name: "Alice",
|
|
83
67
|
email: "alice@example.com",
|
|
@@ -88,8 +72,8 @@ Or use `transact()` for atomic multi-collection operations:
|
|
|
88
72
|
|
|
89
73
|
```typescript
|
|
90
74
|
store.transact((tx) => {
|
|
91
|
-
tx.put(
|
|
92
|
-
tx.put(
|
|
75
|
+
tx.users.put({ id: "1", name: "Alice", email: "alice@example.com" });
|
|
76
|
+
tx.notes.put({ id: "n1", content: "Hello" });
|
|
93
77
|
});
|
|
94
78
|
```
|
|
95
79
|
|
|
@@ -98,7 +82,7 @@ store.transact((tx) => {
|
|
|
98
82
|
Update existing items using `patch()`:
|
|
99
83
|
|
|
100
84
|
```typescript
|
|
101
|
-
store.patch("
|
|
85
|
+
store.users.patch("1", {
|
|
102
86
|
email: "newemail@example.com",
|
|
103
87
|
});
|
|
104
88
|
```
|
|
@@ -108,7 +92,7 @@ store.patch("users", "1", {
|
|
|
108
92
|
Remove items using `remove()`:
|
|
109
93
|
|
|
110
94
|
```typescript
|
|
111
|
-
store.remove("
|
|
95
|
+
store.users.remove("1");
|
|
112
96
|
```
|
|
113
97
|
|
|
114
98
|
### Reading Data
|
|
@@ -117,10 +101,13 @@ Read data directly from the store:
|
|
|
117
101
|
|
|
118
102
|
```typescript
|
|
119
103
|
// Get a single item
|
|
120
|
-
const user = store.get("
|
|
104
|
+
const user = store.users.get("1");
|
|
121
105
|
|
|
122
106
|
// Get all items as an array
|
|
123
|
-
const allUsers = store.list(
|
|
107
|
+
const allUsers = store.users.list();
|
|
108
|
+
|
|
109
|
+
// Filter with a where clause
|
|
110
|
+
const admins = store.users.list({ where: (u) => u.role === "admin" });
|
|
124
111
|
|
|
125
112
|
// You can easily derive other operations:
|
|
126
113
|
const userIds = allUsers.map((u) => u.id);
|
|
@@ -132,15 +119,9 @@ const hasUser = allUsers.some((u) => u.id === "1");
|
|
|
132
119
|
Subscribe to changes with `subscribe()`:
|
|
133
120
|
|
|
134
121
|
```typescript
|
|
135
|
-
// Subscribe to
|
|
136
|
-
const unsubscribe = store.subscribe(
|
|
137
|
-
|
|
138
|
-
const allUsers = store.list("users");
|
|
139
|
-
// Update UI, invalidate queries, etc.
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// Or subscribe to all changes
|
|
143
|
-
store.subscribe((event) => {
|
|
122
|
+
// Subscribe to all changes
|
|
123
|
+
const unsubscribe = store.subscribe((event) => {
|
|
124
|
+
// event is e.g. { users: true } or { users: true, notes: true }
|
|
144
125
|
console.log("Store changed:", event);
|
|
145
126
|
});
|
|
146
127
|
|
|
@@ -181,14 +162,16 @@ function useUsers() {
|
|
|
181
162
|
const queryClient = useQueryClient();
|
|
182
163
|
|
|
183
164
|
useEffect(() => {
|
|
184
|
-
return store.subscribe(
|
|
185
|
-
|
|
165
|
+
return store.subscribe((event) => {
|
|
166
|
+
if ("users" in event) {
|
|
167
|
+
queryClient.invalidateQueries({ queryKey: ["users"] });
|
|
168
|
+
}
|
|
186
169
|
});
|
|
187
170
|
}, []);
|
|
188
171
|
|
|
189
172
|
return useQuery({
|
|
190
173
|
queryKey: ["users"],
|
|
191
|
-
queryFn: () => store.list(
|
|
174
|
+
queryFn: () => store.users.list(),
|
|
192
175
|
});
|
|
193
176
|
}
|
|
194
177
|
```
|
|
@@ -200,8 +183,11 @@ import { useSyncExternalStore } from "react";
|
|
|
200
183
|
|
|
201
184
|
function useUsers() {
|
|
202
185
|
return useSyncExternalStore(
|
|
203
|
-
(callback) =>
|
|
204
|
-
|
|
186
|
+
(callback) =>
|
|
187
|
+
store.subscribe((event) => {
|
|
188
|
+
if ("users" in event) callback();
|
|
189
|
+
}),
|
|
190
|
+
() => store.users.list(),
|
|
205
191
|
);
|
|
206
192
|
}
|
|
207
193
|
```
|
|
@@ -211,9 +197,11 @@ function useUsers() {
|
|
|
211
197
|
```typescript
|
|
212
198
|
import { writable } from "svelte/store";
|
|
213
199
|
|
|
214
|
-
const users = writable(store.list(
|
|
215
|
-
store.subscribe(
|
|
216
|
-
|
|
200
|
+
const users = writable(store.users.list());
|
|
201
|
+
store.subscribe((event) => {
|
|
202
|
+
if ("users" in event) {
|
|
203
|
+
users.set(store.users.list());
|
|
204
|
+
}
|
|
217
205
|
});
|
|
218
206
|
```
|
|
219
207
|
|
|
@@ -222,55 +210,36 @@ store.subscribe(["users"], () => {
|
|
|
222
210
|
```typescript
|
|
223
211
|
import { ref } from "vue";
|
|
224
212
|
|
|
225
|
-
const users = ref(store.list(
|
|
226
|
-
store.subscribe(
|
|
227
|
-
|
|
213
|
+
const users = ref(store.users.list());
|
|
214
|
+
store.subscribe((event) => {
|
|
215
|
+
if ("users" in event) {
|
|
216
|
+
users.value = store.users.list();
|
|
217
|
+
}
|
|
228
218
|
});
|
|
229
219
|
```
|
|
230
220
|
|
|
231
|
-
## Schema Support
|
|
232
|
-
|
|
233
|
-
Starling works with any library that follows the [Standard Schema](https://github.com/standard-schema/spec) specification. This includes:
|
|
234
|
-
|
|
235
|
-
- **Zod** - Most popular schema library
|
|
236
|
-
- **Valibot** - Lightweight alternative
|
|
237
|
-
- **ArkType** - TypeScript-first schemas
|
|
238
|
-
|
|
239
|
-
You can use any of these to define your data shapes. Starling will validate your data and give you full TypeScript types.
|
|
240
|
-
|
|
241
|
-
```typescript
|
|
242
|
-
import { z } from "zod";
|
|
243
|
-
// or
|
|
244
|
-
import * as v from "valibot";
|
|
245
|
-
// or
|
|
246
|
-
import { type } from "arktype";
|
|
247
|
-
|
|
248
|
-
// All of these work the same way
|
|
249
|
-
const schema = z.object({ id: z.string(), name: z.string() });
|
|
250
|
-
// or
|
|
251
|
-
const schema = v.object({ id: v.string(), name: v.string() });
|
|
252
|
-
// or
|
|
253
|
-
const schema = type({ id: "string", name: "string" });
|
|
254
|
-
```
|
|
255
|
-
|
|
256
221
|
## API Overview
|
|
257
222
|
|
|
258
223
|
### Main Export
|
|
259
224
|
|
|
260
225
|
- `createStore(config)` - Creates a new store with collections
|
|
261
226
|
|
|
227
|
+
### Collection Methods
|
|
228
|
+
|
|
229
|
+
Each collection is accessed as a property on the store (e.g., `store.users`):
|
|
230
|
+
|
|
231
|
+
- `store.<collection>.get(id)` - Get a document by ID
|
|
232
|
+
- `store.<collection>.list(options?)` - Get all documents as an array, with optional `where` filter
|
|
233
|
+
- `store.<collection>.put(data)` - Insert or replace a document (upsert, revives tombstoned IDs)
|
|
234
|
+
- `store.<collection>.patch(id, data)` - Partially update an existing document (throws if ID missing)
|
|
235
|
+
- `store.<collection>.remove(id)` - Remove a document
|
|
236
|
+
|
|
262
237
|
### Store Methods
|
|
263
238
|
|
|
264
|
-
- `
|
|
265
|
-
- `
|
|
266
|
-
- `
|
|
267
|
-
- `
|
|
268
|
-
- `remove(collection, id)` - Remove a document
|
|
269
|
-
- `transact(callback)` - Execute operations atomically. Collections are cloned lazily on first access.
|
|
270
|
-
- `subscribe(callback)` - Subscribe to all collection changes
|
|
271
|
-
- `subscribe(collections, callback)` - Subscribe to changes in specific collections
|
|
272
|
-
- `getState()` - Get current store state as a snapshot
|
|
273
|
-
- `merge(snapshot)` - Merge a snapshot into the store
|
|
239
|
+
- `store.transact(callback)` - Execute operations atomically across collections. The `tx` object has the same collection properties (e.g., `tx.users.put(...)`)
|
|
240
|
+
- `store.subscribe(callback)` - Subscribe to all collection changes
|
|
241
|
+
- `store.getState()` - Get current store state as a snapshot
|
|
242
|
+
- `store.merge(snapshot)` - Merge a snapshot into the store
|
|
274
243
|
|
|
275
244
|
For full type definitions, see the TypeScript types exported from the package.
|
|
276
245
|
|
|
@@ -278,20 +247,19 @@ For full type definitions, see the TypeScript types exported from the package.
|
|
|
278
247
|
|
|
279
248
|
- **`lib/core/`** – CRDT primitives: hybrid logical clock, per-field atoms (LWW), tombstones, and document/collection merging.
|
|
280
249
|
- **`lib/store/`** – Store API with collections, batching, queries, and change subscriptions.
|
|
281
|
-
- **`lib/middleware/`** – Optional middleware (e.g. persistence).
|
|
282
250
|
|
|
283
251
|
## Development
|
|
284
252
|
|
|
285
253
|
```bash
|
|
286
254
|
# Install dependencies
|
|
287
|
-
|
|
255
|
+
bun install
|
|
288
256
|
|
|
289
257
|
# Build the library
|
|
290
|
-
|
|
258
|
+
bun run build
|
|
291
259
|
|
|
292
260
|
# Run tests
|
|
293
|
-
|
|
261
|
+
bun test
|
|
294
262
|
|
|
295
263
|
# Watch mode for development
|
|
296
|
-
|
|
264
|
+
bun run dev
|
|
297
265
|
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,209 +1,94 @@
|
|
|
1
1
|
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
2
|
|
|
3
|
-
//#region lib/
|
|
3
|
+
//#region lib/clock.d.ts
|
|
4
4
|
type Clock = {
|
|
5
5
|
ms: number;
|
|
6
6
|
seq: number;
|
|
7
7
|
};
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* HLC timestamp.
|
|
10
|
+
* Lexicographically sortable string: `${ms}@${seq}@${deviceId}`.
|
|
11
|
+
* ms and seq are zero-padded hex; deviceId is appended as-is.
|
|
11
12
|
*/
|
|
12
|
-
type Stamp = string
|
|
13
|
-
readonly __brand: "Stamp";
|
|
14
|
-
};
|
|
13
|
+
type Stamp = string;
|
|
15
14
|
declare function advanceClock(current: Clock, next: Clock): Clock;
|
|
16
|
-
declare function makeStamp(ms: number, seq: number): Stamp;
|
|
17
|
-
/**
|
|
18
|
-
* Parse and validate a string as a Stamp. Use when deserializing stored state.
|
|
19
|
-
* Throws if the value is not a valid 24-character hex string.
|
|
20
|
-
*/
|
|
21
|
-
declare function asStamp(value: string): Stamp;
|
|
15
|
+
declare function makeStamp(ms: number, seq: number, deviceId: string): Stamp;
|
|
22
16
|
//#endregion
|
|
23
|
-
//#region lib/
|
|
24
|
-
type Tombstones = Record<string, Stamp>;
|
|
25
|
-
declare function isDeleted(id: string, tombstones: Tombstones): boolean;
|
|
26
|
-
declare function mergeTombstones(target: Tombstones, source: Tombstones): Tombstones;
|
|
27
|
-
//#endregion
|
|
28
|
-
//#region lib/core/types.d.ts
|
|
29
|
-
declare const KEYS: {
|
|
30
|
-
readonly VAL: "~val";
|
|
31
|
-
readonly TS: "~ts";
|
|
32
|
-
};
|
|
33
|
-
/**
|
|
34
|
-
* Base constraint for plain document shapes - any object with string keys.
|
|
35
|
-
*/
|
|
17
|
+
//#region lib/crdt.d.ts
|
|
36
18
|
type Document = Record<string, unknown>;
|
|
37
19
|
type Atom<T> = {
|
|
38
|
-
|
|
39
|
-
|
|
20
|
+
"~v": T;
|
|
21
|
+
"~ts": string;
|
|
40
22
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Collection of atomized documents with shape T. Map from document ID to AtomizedDocument<T>.
|
|
47
|
-
*/
|
|
48
|
-
type Collection<T extends Document> = Record<string, AtomizedDocument<T>>;
|
|
49
|
-
/**
|
|
50
|
-
* Collection state containing documents and tombstones.
|
|
51
|
-
*/
|
|
52
|
-
type CollectionState<T extends Document> = {
|
|
53
|
-
documents: Collection<T>;
|
|
23
|
+
type AtomizedDocument = Record<string, Atom<unknown>>;
|
|
24
|
+
type Tombstones = Record<string, string>;
|
|
25
|
+
type CollectionState = {
|
|
26
|
+
documents: Record<string, AtomizedDocument>;
|
|
54
27
|
tombstones: Tombstones;
|
|
55
28
|
};
|
|
56
29
|
type StoreState = {
|
|
57
30
|
clock: Clock;
|
|
58
|
-
collections: Record<string, CollectionState
|
|
31
|
+
collections: Record<string, CollectionState>;
|
|
32
|
+
};
|
|
33
|
+
type CollectionChangeEvent = {
|
|
34
|
+
put: string[];
|
|
35
|
+
patch: string[];
|
|
36
|
+
remove: string[];
|
|
59
37
|
};
|
|
60
|
-
/**
|
|
61
|
-
* Type guard to check if a value is an AtomizedDocument.
|
|
62
|
-
* An AtomizedDocument is an object where all values are Atoms.
|
|
63
|
-
*/
|
|
64
|
-
declare function isAtomizedDocument<T extends Document>(value: unknown): value is AtomizedDocument<T>;
|
|
65
|
-
/**
|
|
66
|
-
* Type guard to check if a value is a Collection.
|
|
67
|
-
* A Collection is a Record where all values are AtomizedDocuments.
|
|
68
|
-
*/
|
|
69
|
-
declare function isCollection<T extends Document>(value: unknown): value is Collection<T>;
|
|
70
|
-
/**
|
|
71
|
-
* Type guard to check if a value is a CollectionState.
|
|
72
|
-
* A CollectionState has documents (Collection) and tombstones (Record<string, string>).
|
|
73
|
-
*/
|
|
74
|
-
declare function isCollectionState<T extends Document>(value: unknown): value is CollectionState<T>;
|
|
75
|
-
//#endregion
|
|
76
|
-
//#region lib/core/atomizer.d.ts
|
|
77
|
-
declare function pack<T>(value: T, timestamp: Stamp): Atom<T>;
|
|
78
|
-
declare function unpack<T>(node: unknown): T | undefined;
|
|
79
|
-
declare function isAtom(node: unknown): node is Atom<unknown>;
|
|
80
|
-
declare function atomize<T extends Document>(data: T, timestamp: Stamp): AtomizedDocument<T>;
|
|
81
|
-
//#endregion
|
|
82
|
-
//#region lib/core/lens.d.ts
|
|
83
|
-
declare function createReadLens<T extends Document>(doc: AtomizedDocument<T>): T;
|
|
84
|
-
//#endregion
|
|
85
|
-
//#region lib/core/merge.d.ts
|
|
86
|
-
/** Merges incoming doc fields into local. Adds new keys from incoming; LWW on conflicts. */
|
|
87
|
-
declare function mergeDocs<T extends Document>(local: AtomizedDocument<T>, incoming: Partial<AtomizedDocument<T>>): AtomizedDocument<T>;
|
|
88
|
-
/**
|
|
89
|
-
* Merges two collection states, respecting tombstones.
|
|
90
|
-
* Documents that are tombstoned are excluded from the result.
|
|
91
|
-
* For documents that exist in both collections, fields are merged using LWW semantics.
|
|
92
|
-
*/
|
|
93
|
-
declare function mergeCollections<T extends Document>(local: CollectionState<T>, incoming: CollectionState<T>): CollectionState<T>;
|
|
94
38
|
//#endregion
|
|
95
|
-
//#region lib/
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
* The schema's runtime validation will ensure type safety.
|
|
100
|
-
*/
|
|
101
|
-
declare function validate<T extends StandardSchemaV1>(schema: T, input: unknown): StandardSchemaV1.InferOutput<T>;
|
|
102
|
-
/**
|
|
103
|
-
* Base type constraint for any standard schema object
|
|
104
|
-
*/
|
|
105
|
-
type AnyObject = StandardSchemaV1<Record<string, any>>;
|
|
106
|
-
type Output<T extends AnyObject> = StandardSchemaV1.InferOutput<T>;
|
|
107
|
-
type Input<T extends AnyObject> = StandardSchemaV1.InferInput<T>;
|
|
108
|
-
type CollectionDef<T extends Document = Document, Id extends string = string, S$1 extends AnyObject = AnyObject> = {
|
|
109
|
-
readonly "~docType": T;
|
|
110
|
-
readonly "~idType": Id;
|
|
111
|
-
readonly "~schemaType": S$1;
|
|
112
|
-
schema: AnyObject;
|
|
113
|
-
getId: (data: T) => Id;
|
|
39
|
+
//#region lib/collection.d.ts
|
|
40
|
+
type CollectionDef<Input = unknown, Output extends Document = Document, KeyPath extends keyof Output & string = string> = {
|
|
41
|
+
keyPath: KeyPath;
|
|
42
|
+
schema?: StandardSchemaV1<Input, Output>;
|
|
114
43
|
};
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
* @example
|
|
120
|
-
* ```ts
|
|
121
|
-
* const store = createStore({
|
|
122
|
-
* users: define(userSchema, (data) => data.id),
|
|
123
|
-
* });
|
|
124
|
-
* ```
|
|
125
|
-
*/
|
|
126
|
-
declare function define<S$1 extends AnyObject, Id extends string = string>(schema: S$1, getId: (data: Output<S$1>) => Id): CollectionDef<Output<S$1>, Id, S$1>;
|
|
127
|
-
/**
|
|
128
|
-
* Extract the document type from a CollectionDef
|
|
129
|
-
*/
|
|
130
|
-
type DocType<C extends CollectionDef> = C["~docType"];
|
|
131
|
-
/**
|
|
132
|
-
* Extract the ID type from a CollectionDef
|
|
133
|
-
*/
|
|
134
|
-
type IdType<C extends CollectionDef> = C["~idType"];
|
|
135
|
-
/**
|
|
136
|
-
* Extract the input type from a CollectionDef's schema
|
|
137
|
-
* This allows put() to accept partial data when schemas have defaults
|
|
138
|
-
*/
|
|
139
|
-
type InputType<C extends CollectionDef> = C extends CollectionDef<any, any, infer S> ? Input<S> : never;
|
|
140
|
-
/**
|
|
141
|
-
* Configuration for all collections in a store.
|
|
142
|
-
* All collection definitions must be created using the `collection()` helper.
|
|
143
|
-
*
|
|
144
|
-
* Uses `any` for type parameters to allow any CollectionDef to be stored,
|
|
145
|
-
* while still preserving specific types through inference in createStore.
|
|
146
|
-
*/
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region lib/store.d.ts
|
|
46
|
+
type DocType<C> = C extends CollectionDef<any, infer T, any> ? T : never;
|
|
47
|
+
type InputType<C> = C extends CollectionDef<infer I, any, any> ? I : never;
|
|
147
48
|
type StoreConfig = Record<string, CollectionDef<any, any, any>>;
|
|
148
|
-
/**
|
|
149
|
-
* Valid collection name from a store config
|
|
150
|
-
*/
|
|
151
49
|
type CollectionName<T extends StoreConfig> = keyof T & string;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
put<N extends CollectionName<T>>(collection: N, data: InputType<T[N]>): DocType<T[N]>;
|
|
161
|
-
patch<N extends CollectionName<T>>(collection: N, id: IdType<T[N]>, data: Partial<DocType<T[N]>>): DocType<T[N]>;
|
|
162
|
-
remove<N extends CollectionName<T>>(collection: N, id: IdType<T[N]>): void;
|
|
50
|
+
type CollectionAPI<C extends CollectionDef> = {
|
|
51
|
+
get(id: string): DocType<C> | undefined;
|
|
52
|
+
list(options?: {
|
|
53
|
+
where?: (doc: DocType<C>) => boolean;
|
|
54
|
+
}): DocType<C>[];
|
|
55
|
+
put(data: InputType<C>): DocType<C>;
|
|
56
|
+
patch(id: string, data: Partial<DocType<C>>): DocType<C>;
|
|
57
|
+
remove(id: string): void;
|
|
163
58
|
};
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
get<N extends CollectionName<T>>(collection: N, id: IdType<T[N]>): DocType<T[N]> | undefined;
|
|
168
|
-
list<N extends CollectionName<T>>(collection: N): DocType<T[N]>[];
|
|
169
|
-
put<N extends CollectionName<T>>(collection: N, data: InputType<T[N]>): DocType<T[N]>;
|
|
170
|
-
patch<N extends CollectionName<T>>(collection: N, id: IdType<T[N]>, data: Partial<DocType<T[N]>>): DocType<T[N]>;
|
|
171
|
-
remove<N extends CollectionName<T>>(collection: N, id: IdType<T[N]>): void;
|
|
59
|
+
type StoreChangeEvent<T extends StoreConfig> = { [K in keyof T]?: CollectionChangeEvent };
|
|
60
|
+
type TransactionAPI<T extends StoreConfig> = { [K in CollectionName<T>]: CollectionAPI<T[K]> };
|
|
61
|
+
type Store<T extends StoreConfig> = { [K in CollectionName<T>]: CollectionAPI<T[K]> } & {
|
|
172
62
|
subscribe(callback: (event: StoreChangeEvent<T>) => void): () => void;
|
|
173
63
|
getState(): StoreState;
|
|
174
64
|
merge(snapshot: StoreState): StoreChangeEvent<T>;
|
|
175
65
|
transact<R>(callback: (tx: TransactionAPI<T>) => R): R;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
66
|
+
};
|
|
67
|
+
type StringKeyOf<T> = { [K in keyof T & string]: T[K] extends string ? K : never }[keyof T & string];
|
|
68
|
+
declare function createStore<T extends StoreConfig>(config: {
|
|
69
|
+
deviceId: string;
|
|
70
|
+
collections: T & { [K in keyof T & string]: T[K] extends {
|
|
71
|
+
schema: StandardSchemaV1<any, infer O>;
|
|
72
|
+
} ? {
|
|
73
|
+
keyPath: StringKeyOf<O>;
|
|
74
|
+
} : T[K] };
|
|
75
|
+
}): Store<T>;
|
|
76
|
+
declare function createStore<T extends Record<string, Document & {
|
|
77
|
+
keyPath?: never;
|
|
78
|
+
}>>(config: {
|
|
79
|
+
deviceId: string;
|
|
80
|
+
collections: { [K in keyof T & string]: {
|
|
81
|
+
keyPath: StringKeyOf<T[K]>;
|
|
82
|
+
} };
|
|
83
|
+
}): Store<{ [K in keyof T & string]: CollectionDef<T[K], T[K], string> }>;
|
|
186
84
|
//#endregion
|
|
187
|
-
//#region lib/
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
type PersistenceOptions = {
|
|
193
|
-
key: string;
|
|
194
|
-
storeName?: string;
|
|
195
|
-
debounceMs?: number;
|
|
196
|
-
channelName?: string;
|
|
197
|
-
serialize?: (state: StoreState) => string;
|
|
198
|
-
deserialize?: (serialized: string) => StoreState;
|
|
85
|
+
//#region lib/query.d.ts
|
|
86
|
+
type Query<R> = {
|
|
87
|
+
readonly result: R;
|
|
88
|
+
subscribe(cb: (result: R) => void): () => void;
|
|
89
|
+
dispose(): void;
|
|
199
90
|
};
|
|
200
|
-
declare
|
|
201
|
-
#private;
|
|
202
|
-
constructor(store: Store<T>, options: PersistenceOptions);
|
|
203
|
-
init(): Promise<void>;
|
|
204
|
-
dispose(): Promise<void>;
|
|
205
|
-
subscribe(listener: (serialized: string) => void): () => void;
|
|
206
|
-
}
|
|
91
|
+
declare function createQuery<T extends StoreConfig, R>(store: Store<T>, predicate: (store: Store<T>) => R, dependencies: CollectionName<T>[]): Query<R>;
|
|
207
92
|
//#endregion
|
|
208
|
-
export {
|
|
93
|
+
export { type Clock, type CollectionAPI, type CollectionChangeEvent, type CollectionDef, type CollectionName, type CollectionState, type DocType, type Document, type InputType, type Query, type Store, type StoreChangeEvent, type StoreConfig, type StoreState, type Tombstones, type TransactionAPI, advanceClock, createQuery, createStore, makeStamp };
|
|
209
94
|
//# sourceMappingURL=index.d.ts.map
|