mpb-localkit 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 LocalKit Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,375 @@
1
+ # MPB LocalKit
2
+
3
+ > Type-driven, offline-first SDK for TypeScript developers. Define your data schema once — get local storage, sync, auth, and error tracking out of the box.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ npm install mpb-localkit
9
+ ```
10
+
11
+ ```ts
12
+ import { createApp, collection, z } from 'mpb-localkit'
13
+
14
+ const app = createApp({
15
+ collections: {
16
+ brews: collection(z.object({
17
+ brewDate: z.date(),
18
+ style: z.string(),
19
+ og: z.number(),
20
+ fg: z.number().optional(),
21
+ notes: z.string().optional(),
22
+ })),
23
+ },
24
+ })
25
+
26
+ // Fully typed CRUD — works offline immediately
27
+ const brew = await app.brews.create({ brewDate: new Date(), style: 'IPA', og: 1.065 })
28
+ const allBrews = await app.brews.findMany()
29
+ await app.brews.update(brew._id, { fg: 1.012 })
30
+ await app.brews.delete(brew._id)
31
+ ```
32
+
33
+ ## API Reference
34
+
35
+ ### `createApp(config)`
36
+
37
+ Creates a typed app instance with CRUD methods for each collection.
38
+
39
+ ```ts
40
+ const app = createApp({
41
+ collections: { ... }, // Required: collection definitions
42
+ sync: { // Optional: sync configuration
43
+ endpoint: 'https://...',
44
+ interval: 30000,
45
+ enabled: true,
46
+ },
47
+ errorTracking: { // Optional: error tracking config
48
+ enabled: true,
49
+ snapshot: true,
50
+ maxLocalErrors: 100,
51
+ },
52
+ })
53
+ ```
54
+
55
+ ### `collection(schema)`
56
+
57
+ Wraps a Zod schema into a typed collection descriptor. TypeScript infers the document type automatically.
58
+
59
+ ```ts
60
+ const todos = collection(z.object({
61
+ text: z.string(),
62
+ done: z.boolean().default(false),
63
+ }))
64
+ // todos._inferredType is { text: string; done: boolean }
65
+ ```
66
+
67
+ ### `z`
68
+
69
+ Re-exported from [Zod](https://zod.dev). Use it to define your schemas.
70
+
71
+ ## Collection CRUD
72
+
73
+ Every collection in `app.collections` gets these methods:
74
+
75
+ ```ts
76
+ // Create — returns the document with metadata fields
77
+ const doc = await app.todos.create({ text: 'Buy hops', done: false })
78
+ // doc._id, doc._collection, doc._updatedAt, doc._deleted are added automatically
79
+
80
+ // Read
81
+ const all = await app.todos.findMany()
82
+ const one = await app.todos.findOne(doc._id)
83
+
84
+ // Update — merges partial data
85
+ await app.todos.update(doc._id, { done: true })
86
+
87
+ // Delete — soft delete (sets _deleted: true for sync propagation)
88
+ await app.todos.delete(doc._id)
89
+ ```
90
+
91
+ ## Auth
92
+
93
+ ```ts
94
+ // Sign up
95
+ await app.auth.signUp({ email: 'user@example.com', password: 'secret' })
96
+
97
+ // Sign in
98
+ await app.auth.signIn({ email: 'user@example.com', password: 'secret' })
99
+
100
+ // Sign out
101
+ await app.auth.signOut()
102
+
103
+ // Current user
104
+ const user = app.auth.currentUser() // { id, email } | null
105
+ ```
106
+
107
+ Passwords are hashed client-side (PBKDF2) before transmission. Sessions are JWTs cached locally for offline access.
108
+
109
+ ## Sync
110
+
111
+ Sync happens automatically when configured with an `endpoint`. MPB LocalKit uses a Last-Write-Wins (LWW) protocol — the document with the highest `_updatedAt` timestamp wins conflicts.
112
+
113
+ ```ts
114
+ // Manual sync
115
+ await app.sync()
116
+ ```
117
+
118
+ Sync triggers automatically on:
119
+ - Tab focus (returning to the app)
120
+ - Network reconnect (coming back online)
121
+ - Periodic interval (configurable, default 30s)
122
+
123
+ All reads and writes hit local storage immediately — sync never blocks the UI.
124
+
125
+ ## React Hooks
126
+
127
+ ```tsx
128
+ import { useCollection, useAuth, useSync } from 'mpb-localkit/react'
129
+ import { app } from './schema'
130
+
131
+ function BrewList() {
132
+ const { data: brews, isLoading } = useCollection(app.brews)
133
+ const { user, signIn, signOut } = useAuth(app)
134
+ const { status, lastSyncAt } = useSync(app)
135
+
136
+ if (isLoading) return <div>Loading...</div>
137
+
138
+ return (
139
+ <ul>
140
+ {brews.map(brew => <li key={brew._id}>{brew.style}</li>)}
141
+ </ul>
142
+ )
143
+ }
144
+ ```
145
+
146
+ ## CLI
147
+
148
+ ```bash
149
+ # Start local dev (SDK works without a Worker)
150
+ npx mpb-localkit dev
151
+
152
+ # Generate Cloudflare Worker from your schema
153
+ npx mpb-localkit build --name my-app
154
+
155
+ # Build + deploy to Cloudflare
156
+ npx mpb-localkit deploy
157
+ ```
158
+
159
+ The generated Worker handles auth, sync, and error storage via Cloudflare R2 + KV.
160
+
161
+ ## Architecture
162
+
163
+ ```
164
+ Your App (React/Vue/Svelte)
165
+
166
+ Framework Bindings
167
+ (useCollection, useAuth)
168
+
169
+ Core SDK
170
+ ┌──────────────────────────────┐
171
+ │ Schema │ Sync │ Auth │
172
+ │ Engine │ Engine │ Module │
173
+ ├──────────────────────────────┤
174
+ │ Local Storage │
175
+ │ (IndexedDB, via idb) │
176
+ └──────────────────────────────┘
177
+ ↓ (when online)
178
+ Cloudflare Worker (generated)
179
+ ┌──────────────────────────────┐
180
+ │ Auth │ Sync │ Errors │
181
+ │ Routes │ Routes │ Routes │
182
+ ├──────────────────────────────┤
183
+ │ Cloudflare R2 + KV │
184
+ └──────────────────────────────┘
185
+ ```
186
+
187
+ **All writes hit IndexedDB first** — your app works fully offline. The sync engine pushes/pulls changes to the Cloudflare Worker when connectivity is available.
188
+
189
+ ## Document Shape
190
+
191
+ Every document has these metadata fields added automatically:
192
+
193
+ ```ts
194
+ {
195
+ _id: string // UUIDv7 — sortable by creation time
196
+ _collection: string // Collection name
197
+ _updatedAt: number // Unix ms — the sync cursor
198
+ _deleted: boolean // Soft delete for sync propagation
199
+ // ...your fields
200
+ }
201
+ ```
202
+
203
+ ## Create a New App
204
+
205
+ The fastest way to start is with the scaffolder:
206
+
207
+ ```bash
208
+ npm create mpb-localkit@latest
209
+ # or
210
+ pnpm create mpb-localkit@latest
211
+ ```
212
+
213
+ It prompts for project name, framework, auth method, sync transport, and deploy target — then scaffolds a ready-to-run project and installs dependencies.
214
+
215
+ ```
216
+ ? Project name: my-app
217
+ ? Framework: React
218
+ ? Auth method: Email + Password
219
+ ? Sync transport: Auto (WebSocket with HTTP fallback)
220
+ ? Deploy target: Cloudflare Workers
221
+
222
+ Done! Your project is ready.
223
+ cd my-app
224
+ pnpm run dev
225
+ ```
226
+
227
+ ## Transport Configuration
228
+
229
+ MPB LocalKit supports three sync transports, configurable at `createApp` time:
230
+
231
+ ### HTTP Transport (default)
232
+
233
+ Polling-based sync. Works everywhere, no persistent connection required.
234
+
235
+ ```ts
236
+ const app = createApp({
237
+ collections: { ... },
238
+ sync: {
239
+ transport: 'http',
240
+ endpoint: 'https://your-worker.workers.dev',
241
+ interval: 30000,
242
+ },
243
+ })
244
+ ```
245
+
246
+ ### WebSocket Transport
247
+
248
+ Persistent connection for real-time sync. The server pushes changes as they happen.
249
+
250
+ ```ts
251
+ const app = createApp({
252
+ collections: { ... },
253
+ sync: {
254
+ transport: 'websocket',
255
+ endpoint: 'wss://your-worker.workers.dev',
256
+ },
257
+ })
258
+ ```
259
+
260
+ ### Auto Transport (recommended)
261
+
262
+ Starts a WebSocket connection and falls back to HTTP polling if WebSocket is unavailable. Best of both worlds.
263
+
264
+ ```ts
265
+ const app = createApp({
266
+ collections: { ... },
267
+ sync: {
268
+ transport: 'auto',
269
+ endpoint: 'https://your-worker.workers.dev',
270
+ },
271
+ })
272
+ ```
273
+
274
+ With `auto`, MPB LocalKit upgrades to WebSocket when the server supports it and degrades gracefully on restricted networks.
275
+
276
+ ## WebSocket Sync
277
+
278
+ When using `transport: 'websocket'` or `transport: 'auto'`, MPB LocalKit maintains a persistent WebSocket connection to your sync Worker. The Worker sends change events as they occur — no polling delay.
279
+
280
+ ```ts
281
+ import { createApp, collection, z } from 'mpb-localkit'
282
+
283
+ const app = createApp({
284
+ collections: {
285
+ messages: collection(z.object({
286
+ text: z.string(),
287
+ author: z.string(),
288
+ })),
289
+ },
290
+ sync: {
291
+ transport: 'auto',
292
+ endpoint: 'https://chat.your-subdomain.workers.dev',
293
+ },
294
+ })
295
+
296
+ // Writes go to IndexedDB immediately (no await on network)
297
+ await app.messages.create({ text: 'Hello!', author: 'alice' })
298
+
299
+ // The Worker broadcasts the change to all connected clients via WebSocket
300
+ // Recipients see the new message arrive without polling
301
+ ```
302
+
303
+ The WebSocket connection is managed automatically:
304
+ - Reconnects with exponential backoff on disconnect
305
+ - Replays missed changes on reconnect using the LWW cursor
306
+ - Falls back to HTTP polling when WebSocket is unavailable (Auto mode)
307
+
308
+ ## Better Auth Integration
309
+
310
+ MPB LocalKit integrates with [Better Auth](https://better-auth.com) for full-featured server-side authentication.
311
+
312
+ ### Setup
313
+
314
+ ```ts
315
+ import { createApp, collection, z } from 'mpb-localkit'
316
+
317
+ const app = createApp({
318
+ collections: {
319
+ notes: collection(z.object({ text: z.string() })),
320
+ },
321
+ auth: {
322
+ type: 'better-auth',
323
+ baseURL: 'https://auth.your-domain.com',
324
+ },
325
+ sync: {
326
+ transport: 'auto',
327
+ endpoint: 'https://sync.your-domain.com',
328
+ },
329
+ })
330
+ ```
331
+
332
+ ### Session handling
333
+
334
+ Better Auth sessions are cached locally so the app stays functional offline. The session is re-validated against the server on reconnect.
335
+
336
+ ```ts
337
+ // Sign in — stores session locally for offline access
338
+ await app.auth.signIn({ email: 'user@example.com', password: 'secret' })
339
+
340
+ // Current user is available immediately (from local cache)
341
+ const user = app.auth.currentUser() // { id, email, name } | null
342
+
343
+ // Sync automatically includes the session token in requests
344
+ await app.sync()
345
+
346
+ // Sign out clears the local session
347
+ await app.auth.signOut()
348
+ ```
349
+
350
+ ### React hooks with Better Auth
351
+
352
+ ```tsx
353
+ import { useAuth, useCollection } from 'mpb-localkit/react'
354
+ import { app } from './schema'
355
+
356
+ function App() {
357
+ const { user, signIn, signOut, isLoading } = useAuth(app)
358
+ const { data: notes } = useCollection(app.notes)
359
+
360
+ if (isLoading) return <div>Loading...</div>
361
+ if (!user) return <SignInForm onSignIn={signIn} />
362
+
363
+ return (
364
+ <div>
365
+ <p>Signed in as {user.email}</p>
366
+ <ul>{notes.map(n => <li key={n._id}>{n.text}</li>)}</ul>
367
+ <button onClick={signOut}>Sign out</button>
368
+ </div>
369
+ )
370
+ }
371
+ ```
372
+
373
+ ## License
374
+
375
+ MIT
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node