@byearlybird/starling 0.11.1 → 0.13.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 ADDED
@@ -0,0 +1,274 @@
1
+ # Starling
2
+
3
+ A mergable data store for building local-first apps that sync.
4
+
5
+ Starling lets you store data in memory with a fast, synchronous API. The merge system is fully built and ready—persistence and sync features are coming soon. When you need to sync with other devices or users, it automatically merges changes and resolves conflicts.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @byearlybird/starling
11
+ # or
12
+ bun add @byearlybird/starling
13
+ ```
14
+
15
+ Requires TypeScript 5 or higher.
16
+
17
+ ## Quick Example
18
+
19
+ ```typescript
20
+ import { createStore } from "@byearlybird/starling";
21
+ import { z } from "zod";
22
+
23
+ const userSchema = z.object({
24
+ id: z.string(),
25
+ name: z.string(),
26
+ });
27
+
28
+ const store = createStore({
29
+ collections: {
30
+ users: { schema: userSchema },
31
+ },
32
+ });
33
+
34
+ store.users.add({ id: "1", name: "Alice" });
35
+ const user = store.users.get("1"); // { id: "1", name: "Alice" }
36
+ ```
37
+
38
+ ## Features
39
+
40
+ - **In-memory and fast**: All operations are synchronous for maximum performance
41
+ - **Built for local-first**: API is ready for persistence and sync (coming soon)
42
+ - **Works with any standard schema library**: Zod, Valibot, ArkType, and more
43
+ - **Automatic conflict resolution**: When merging changes, conflicts are resolved automatically
44
+ - **Reactive updates**: Data changes trigger updates automatically
45
+ - **Type-safe**: Full TypeScript support with type inference
46
+ - **Merge snapshots**: Sync data between devices or users easily
47
+
48
+ ## Basic Usage
49
+
50
+ ### Creating a Store
51
+
52
+ A store holds one or more collections. Each collection has a schema that defines what data it can store.
53
+
54
+ ```typescript
55
+ import { createStore } from "@byearlybird/starling";
56
+ import { z } from "zod";
57
+
58
+ const store = createStore({
59
+ collections: {
60
+ users: {
61
+ schema: z.object({
62
+ id: z.string(),
63
+ name: z.string(),
64
+ email: z.string().optional(),
65
+ }),
66
+ },
67
+ notes: {
68
+ schema: z.object({
69
+ id: z.string(),
70
+ content: z.string(),
71
+ }),
72
+ },
73
+ },
74
+ });
75
+ ```
76
+
77
+ ### Adding Documents
78
+
79
+ Add new items to a collection with `add()`:
80
+
81
+ ```typescript
82
+ store.users.add({
83
+ id: "1",
84
+ name: "Alice",
85
+ email: "alice@example.com",
86
+ });
87
+ ```
88
+
89
+ ### Updating Documents
90
+
91
+ Update existing items with `update()`:
92
+
93
+ ```typescript
94
+ store.users.update("1", {
95
+ email: "newemail@example.com",
96
+ });
97
+ ```
98
+
99
+ ### Removing Documents
100
+
101
+ Remove items with `remove()`:
102
+
103
+ ```typescript
104
+ store.users.remove("1");
105
+ ```
106
+
107
+ ### Reading Data
108
+
109
+ Collections work like maps. You can read data in several ways:
110
+
111
+ ```typescript
112
+ // Get a single item
113
+ const user = store.users.get("1");
114
+
115
+ // Check if an item exists
116
+ if (store.users.has("1")) {
117
+ // ...
118
+ }
119
+
120
+ // Get all items
121
+ for (const [id, user] of store.users.entries()) {
122
+ console.log(id, user);
123
+ }
124
+
125
+ // Query data reactively (recommended)
126
+ const $userCount = store.query(["users"], (collections) => {
127
+ return collections.users.size;
128
+ });
129
+
130
+ // Get current value
131
+ console.log($userCount.get()); // 5
132
+
133
+ // Subscribe to updates
134
+ $userCount.subscribe((count) => {
135
+ console.log("User count:", count);
136
+ });
137
+ ```
138
+
139
+ ### Reactive Queries
140
+
141
+ For reactive updates, use the `query()` method. It lets you combine data from multiple collections and automatically updates when any of them change:
142
+
143
+ ```typescript
144
+ // Query multiple collections
145
+ const $stats = store.query(["users", "notes"], (collections) => {
146
+ return {
147
+ totalUsers: collections.users.size,
148
+ totalNotes: collections.notes.size,
149
+ firstUser: collections.users.get("1"),
150
+ };
151
+ });
152
+
153
+ // Subscribe to changes
154
+ $stats.subscribe((stats) => {
155
+ console.log("Stats updated:", stats);
156
+ });
157
+ ```
158
+
159
+ ## Merging Data
160
+
161
+ Starling's merge system is fully built and ready to use. When you add persistence and sync (coming soon), you'll use snapshots to sync data. A snapshot is a copy of all your data at a point in time.
162
+
163
+ ### Getting a Snapshot
164
+
165
+ Get the current state of your store:
166
+
167
+ ```typescript
168
+ const snapshot = store.$snapshot.get();
169
+ // { clock: { ms: ..., seq: ... }, collections: { ... } }
170
+ ```
171
+
172
+ ### Merging a Snapshot
173
+
174
+ Merge a snapshot from another device or user:
175
+
176
+ ```typescript
177
+ // Get snapshot from another device (when you add sync)
178
+ const otherSnapshot = getSnapshotFromServer();
179
+
180
+ // Merge it into your store
181
+ store.merge(otherSnapshot);
182
+ ```
183
+
184
+ Starling automatically resolves conflicts. If the same item was changed in both places, it keeps the change with the newer timestamp. The merge API is ready now—just add your persistence and sync layer on top.
185
+
186
+ ### Syncing Between Two Stores
187
+
188
+ Here's a simple example of syncing between two stores:
189
+
190
+ ```typescript
191
+ const store1 = createStore({ collections: { users: { schema: userSchema } } });
192
+ const store2 = createStore({ collections: { users: { schema: userSchema } } });
193
+
194
+ // Add data to store1
195
+ store1.users.add({ id: "1", name: "Alice" });
196
+
197
+ // Sync to store2
198
+ const snapshot = store1.$snapshot.get();
199
+ store2.merge(snapshot);
200
+
201
+ // Now store2 has the same data
202
+ console.log(store2.users.get("1")); // { id: "1", name: "Alice" }
203
+ ```
204
+
205
+ ## Schema Support
206
+
207
+ Starling works with any library that follows the [Standard Schema](https://github.com/standard-schema/spec) specification. This includes:
208
+
209
+ - **Zod** - Most popular schema library
210
+ - **Valibot** - Lightweight alternative
211
+ - **ArkType** - TypeScript-first schemas
212
+
213
+ You can use any of these to define your data shapes. Starling will validate your data and give you full TypeScript types.
214
+
215
+ ```typescript
216
+ import { z } from "zod";
217
+ // or
218
+ import * as v from "valibot";
219
+ // or
220
+ import { type } from "arktype";
221
+
222
+ // All of these work the same way
223
+ const schema = z.object({ id: z.string(), name: z.string() });
224
+ // or
225
+ const schema = v.object({ id: v.string(), name: v.string() });
226
+ // or
227
+ const schema = type({ id: "string", name: "string" });
228
+ ```
229
+
230
+ ## API Overview
231
+
232
+ ### Main Export
233
+
234
+ - `createStore(config)` - Creates a new store with collections
235
+
236
+ ### Collection Methods
237
+
238
+ Each collection in your store has these methods:
239
+
240
+ - `add(data)` - Add a new document
241
+ - `update(id, data)` - Update an existing document
242
+ - `remove(id)` - Remove a document
243
+ - `merge(snapshot)` - Merge a collection snapshot
244
+ - `get(id)` - Get a document by ID
245
+ - `has(id)` - Check if a document exists
246
+ - `keys()` - Get all document IDs
247
+ - `values()` - Get all documents
248
+ - `entries()` - Get all [id, document] pairs
249
+ - `forEach(callback)` - Iterate over documents
250
+ - `size` - Number of documents
251
+
252
+ ### Store Methods
253
+
254
+ - `$snapshot` - Reactive atom containing the full store snapshot
255
+ - `merge(snapshot)` - Merge a store snapshot
256
+ - `query(collections, callback)` - Query multiple collections reactively (recommended for reactive code)
257
+
258
+ For full type definitions, see the TypeScript types exported from the package.
259
+
260
+ ## Development
261
+
262
+ ```bash
263
+ # Install dependencies
264
+ bun install
265
+
266
+ # Build the library
267
+ bun run build
268
+
269
+ # Run tests
270
+ bun test
271
+
272
+ # Watch mode for development
273
+ bun run dev
274
+ ```
package/dist/index.d.ts CHANGED
@@ -1,3 +1,73 @@
1
- import { a as AnyObject, s as JsonDocument } from "./index-D7bXWDg6.js";
2
- import { _ as StandardSchemaV1, a as createDatabase, c as QueryCollectionHandle, d as Collection, f as CollectionInternals, g as SchemasMap, h as AnyObjectSchema, i as DbConfig, l as QueryContext, m as IdNotFoundError, n as Database, o as TransactionCollectionHandle, p as DuplicateIdError, r as DatabasePlugin, s as TransactionContext, t as CollectionConfig, u as QueryHandle } from "./db-DJ_6dO-K.js";
3
- export { type AnyObject, AnyObjectSchema, type Collection, type CollectionConfig, CollectionInternals, type Database, type DatabasePlugin, type DbConfig, DuplicateIdError, IdNotFoundError, type JsonDocument, type QueryCollectionHandle, type QueryContext, type QueryHandle, SchemasMap, type StandardSchemaV1, type TransactionCollectionHandle, type TransactionContext, createDatabase };
1
+ import { ReadableAtom, atom, map } from "nanostores";
2
+ import { StandardSchemaV1 } from "@standard-schema/spec";
3
+
4
+ //#region lib/core/clock.d.ts
5
+ type Clock = {
6
+ ms: number;
7
+ seq: number;
8
+ };
9
+ //#endregion
10
+ //#region lib/core/document.d.ts
11
+ type Field<T = unknown> = {
12
+ "~value": T;
13
+ "~stamp": string;
14
+ };
15
+ type Document = Record<string, Field>;
16
+ //#endregion
17
+ //#region lib/core/tombstone.d.ts
18
+ type Tombstones = Record<string, string>;
19
+ //#endregion
20
+ //#region lib/core/collection.d.ts
21
+ type DocumentId = string;
22
+ type Collection = {
23
+ documents: Record<DocumentId, Document>;
24
+ tombstones: Tombstones;
25
+ };
26
+ //#endregion
27
+ //#region lib/store/schema.d.ts
28
+ /**
29
+ * Base type constraint for any standard schema object
30
+ */
31
+ type AnyObject = StandardSchemaV1<Record<string, any>>;
32
+ type SchemaWithId<T extends AnyObject> = StandardSchemaV1.InferOutput<T> extends {
33
+ id: any;
34
+ } ? T : never;
35
+ type Output<T extends AnyObject> = StandardSchemaV1.InferOutput<T>;
36
+ type Input<T extends AnyObject> = StandardSchemaV1.InferInput<T>;
37
+ //#endregion
38
+ //#region lib/store/collection.d.ts
39
+ type CollectionConfig<T extends AnyObject> = {
40
+ schema: T;
41
+ getId: (data: Output<T>) => DocumentId;
42
+ } | {
43
+ schema: SchemaWithId<T>;
44
+ };
45
+ type CollectionApi<T extends AnyObject> = {
46
+ $data: ReadableAtom<ReadonlyMap<DocumentId, Output<T>>>;
47
+ $snapshot: ReadableAtom<Collection>;
48
+ add(data: Input<T>): void;
49
+ remove(id: DocumentId): void;
50
+ update(id: DocumentId, document: Partial<Input<T>>): void;
51
+ merge(snapshot: Collection): void;
52
+ } & Pick<ReadonlyMap<DocumentId, Output<T>>, "get" | "has" | "keys" | "values" | "entries" | "forEach" | "size">;
53
+ //#endregion
54
+ //#region lib/store/store.d.ts
55
+ type StoreSnapshot = {
56
+ clock: Clock;
57
+ collections: Record<string, Collection>;
58
+ };
59
+ type StoreCollections<T extends Record<string, CollectionConfig<any>>> = { [K in keyof T]: T[K] extends CollectionConfig<infer S> ? CollectionApi<S> : never };
60
+ type QueryCollections<TCollections extends StoreCollections<any>, TKeys extends readonly (keyof TCollections)[]> = { [K in TKeys[number]]: TCollections[K] extends {
61
+ $data: ReadableAtom<infer D>;
62
+ } ? D : never };
63
+ type StoreAPI<T extends Record<string, CollectionConfig<any>>> = StoreCollections<T> & {
64
+ $snapshot: ReadableAtom<StoreSnapshot>;
65
+ query<TKeys extends readonly (keyof StoreCollections<T>)[], TResult>(collections: TKeys, callback: (collections: QueryCollections<StoreCollections<T>, TKeys>) => TResult): ReadableAtom<TResult>;
66
+ merge(snapshot: StoreSnapshot): void;
67
+ };
68
+ declare function createStore<T extends Record<string, CollectionConfig<any>>>(config: {
69
+ collections: T;
70
+ }): StoreAPI<T>;
71
+ //#endregion
72
+ export { type AnyObject, QueryCollections, StoreAPI, StoreCollections, StoreSnapshot, createStore };
73
+ //# sourceMappingURL=index.d.ts.map