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 +21 -0
- package/README.md +375 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +853 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/index.d.ts +404 -0
- package/dist/core/index.js +1780 -0
- package/dist/core/index.js.map +1 -0
- package/dist/react/index.d.ts +90 -0
- package/dist/react/index.js +230 -0
- package/dist/react/index.js.map +1 -0
- package/dist/svelte/index.d.ts +60 -0
- package/dist/svelte/index.js +151 -0
- package/dist/svelte/index.js.map +1 -0
- package/dist/vue/index.d.ts +97 -0
- package/dist/vue/index.js +133 -0
- package/dist/vue/index.js.map +1 -0
- package/package.json +120 -0
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
|