@zveltio/sdk 1.2.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 +191 -0
- package/dist/client/Auth.d.ts +19 -0
- package/dist/client/Auth.d.ts.map +1 -0
- package/dist/client/Auth.js +45 -0
- package/dist/client/QueryBuilder.d.ts +17 -0
- package/dist/client/QueryBuilder.d.ts.map +1 -0
- package/dist/client/QueryBuilder.js +76 -0
- package/dist/client/RealtimeClient.d.ts +22 -0
- package/dist/client/RealtimeClient.d.ts.map +1 -0
- package/dist/client/RealtimeClient.js +96 -0
- package/dist/client/ZveltioClient.d.ts +20 -0
- package/dist/client/ZveltioClient.d.ts.map +1 -0
- package/dist/client/ZveltioClient.js +78 -0
- package/dist/client.d.ts +82 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +122 -0
- package/dist/core.d.ts +61 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +65 -0
- package/dist/crdt.d.ts +41 -0
- package/dist/crdt.d.ts.map +1 -0
- package/dist/crdt.js +87 -0
- package/dist/extension/index.d.ts +46 -0
- package/dist/extension/index.d.ts.map +1 -0
- package/dist/extension/index.js +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/local-store.d.ts +68 -0
- package/dist/local-store.d.ts.map +1 -0
- package/dist/local-store.js +263 -0
- package/dist/realtime.d.ts +14 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +82 -0
- package/dist/schema-watcher.d.ts +41 -0
- package/dist/schema-watcher.d.ts.map +1 -0
- package/dist/schema-watcher.js +182 -0
- package/dist/svelte.d.ts +38 -0
- package/dist/svelte.d.ts.map +1 -0
- package/dist/svelte.js +48 -0
- package/dist/sync-manager.d.ts +73 -0
- package/dist/sync-manager.d.ts.map +1 -0
- package/dist/sync-manager.js +293 -0
- package/dist/tests/local-store.test.d.ts +2 -0
- package/dist/tests/local-store.test.d.ts.map +1 -0
- package/dist/tests/local-store.test.js +76 -0
- package/dist/tests/setup.d.ts +2 -0
- package/dist/tests/setup.d.ts.map +1 -0
- package/dist/tests/setup.js +3 -0
- package/dist/types/index.d.ts +39 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# @zveltio/sdk
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for Zveltio — HTTP client, Realtime WebSocket, and Local-First offline support.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @zveltio/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Basic Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createZveltioClient } from '@zveltio/sdk';
|
|
15
|
+
|
|
16
|
+
const client = createZveltioClient({ baseUrl: 'https://api.myapp.com' });
|
|
17
|
+
|
|
18
|
+
// CRUD
|
|
19
|
+
const todos = await client.collection('todos').list();
|
|
20
|
+
await client.collection('todos').create({ title: 'Buy milk', done: false });
|
|
21
|
+
await client.collection('todos').update('id-123', { done: true });
|
|
22
|
+
await client.collection('todos').delete('id-123');
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Live TypeScript Types
|
|
26
|
+
|
|
27
|
+
Get full IntelliSense on every `.collection()` call by pointing the client at your generated schema.
|
|
28
|
+
|
|
29
|
+
**Step 1 — Generate types** (one-shot or watch mode):
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# One-shot
|
|
33
|
+
bunx zveltio generate-types --url http://localhost:3000 --out ./src/zveltio-types.d.ts
|
|
34
|
+
|
|
35
|
+
# Watch mode (re-generates on every schema change while developing)
|
|
36
|
+
bunx zveltio dev --watch
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Step 2 — Import the generated alias:**
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import type { ZveltioSchema } from './zveltio-types';
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Step 3 — Pass it to `createZveltioClient`:**
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { createZveltioClient } from '@zveltio/sdk';
|
|
49
|
+
import type { ZveltioSchema } from './zveltio-types';
|
|
50
|
+
|
|
51
|
+
const client = createZveltioClient<ZveltioSchema>({
|
|
52
|
+
baseUrl: 'https://api.myapp.com',
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Step 4 — Every `.collection()` call is now fully typed:**
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// TypeScript knows the shape of each record automatically
|
|
60
|
+
const { data: products } = await client.collection('products').list();
|
|
61
|
+
// ^-- typed as Products[] (from your generated CollectionTypeMap)
|
|
62
|
+
|
|
63
|
+
const order = await client.collection('orders').getOne('ord-123');
|
|
64
|
+
// ^-- typed as Orders
|
|
65
|
+
|
|
66
|
+
await client.collection('products').create({ name: 'Widget', price: 9.99 });
|
|
67
|
+
// TypeScript will error if you pass unknown fields or the wrong types
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The generated file (`zveltio-types.d.ts`) exports individual interfaces per collection, a `CollectionTypeMap` interface, and the `ZveltioSchema` alias — all kept in sync automatically when you run in watch mode.
|
|
71
|
+
|
|
72
|
+
## Realtime
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { ZveltioRealtime } from '@zveltio/sdk';
|
|
76
|
+
|
|
77
|
+
const rt = new ZveltioRealtime('https://api.myapp.com');
|
|
78
|
+
rt.connect();
|
|
79
|
+
|
|
80
|
+
const unsub = rt.subscribe('todos', (event) => {
|
|
81
|
+
console.log('Event:', event.event, event.record_id);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Cleanup
|
|
85
|
+
unsub();
|
|
86
|
+
rt.disconnect();
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Local-First Usage
|
|
90
|
+
|
|
91
|
+
Zveltio SDK supports offline-first data access with automatic background sync:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { createZveltioClient, SyncManager } from '@zveltio/sdk';
|
|
95
|
+
|
|
96
|
+
const client = createZveltioClient({ baseUrl: 'https://api.myapp.com' });
|
|
97
|
+
const sync = new SyncManager(client, {
|
|
98
|
+
syncInterval: 5000,
|
|
99
|
+
onConflict: (local, server) => {
|
|
100
|
+
// Custom merge: keep local 'notes' field, take rest from server
|
|
101
|
+
return { ...server, notes: local?.notes };
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Start: opens IndexedDB, connects WebSocket, starts periodic sync
|
|
106
|
+
await sync.start('https://api.myapp.com');
|
|
107
|
+
|
|
108
|
+
// All operations are instant (local-first)
|
|
109
|
+
const todos = sync.collection('todos');
|
|
110
|
+
|
|
111
|
+
// Create — writes locally, syncs in background
|
|
112
|
+
await todos.create({ title: 'Buy milk', done: false });
|
|
113
|
+
|
|
114
|
+
// List — reads from local IndexedDB (instant, works offline)
|
|
115
|
+
const all = await todos.list();
|
|
116
|
+
// each record has _syncStatus: 'synced' | 'pending' | 'conflict'
|
|
117
|
+
|
|
118
|
+
// Update — writes locally, syncs in background
|
|
119
|
+
await todos.update('id-123', { done: true });
|
|
120
|
+
|
|
121
|
+
// Delete — soft-deletes locally, syncs in background
|
|
122
|
+
await todos.delete('id-123');
|
|
123
|
+
|
|
124
|
+
// Subscribe — reactive updates (local writes + server push via WebSocket)
|
|
125
|
+
const unsub = todos.subscribe((records) => {
|
|
126
|
+
console.log('Todos updated:', records);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Check sync status
|
|
130
|
+
const status = await sync.getStatus();
|
|
131
|
+
// { pending: 2, conflicts: 0, isOnline: true }
|
|
132
|
+
|
|
133
|
+
// Handle conflicts manually
|
|
134
|
+
const conflicts = await todos.getConflicts();
|
|
135
|
+
for (const conflict of conflicts) {
|
|
136
|
+
await todos.resolveConflict(conflict.id, { ...conflict.data, resolved: true });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Cleanup
|
|
140
|
+
unsub();
|
|
141
|
+
await sync.stop();
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Svelte 5 Integration
|
|
145
|
+
|
|
146
|
+
```svelte
|
|
147
|
+
<script lang="ts">
|
|
148
|
+
import { onDestroy } from 'svelte';
|
|
149
|
+
import { useSyncCollection, useSyncStatus } from '@zveltio/sdk';
|
|
150
|
+
|
|
151
|
+
let todos = $state<any[]>([]);
|
|
152
|
+
let syncStatus = $state({ pending: 0, conflicts: 0, isOnline: true });
|
|
153
|
+
|
|
154
|
+
const unsubTodos = useSyncCollection(sync, 'todos', (records) => {
|
|
155
|
+
todos = records;
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const unsubStatus = useSyncStatus(sync, (s) => {
|
|
159
|
+
syncStatus = s;
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
onDestroy(() => {
|
|
163
|
+
unsubTodos();
|
|
164
|
+
unsubStatus();
|
|
165
|
+
});
|
|
166
|
+
</script>
|
|
167
|
+
|
|
168
|
+
{#if !syncStatus.isOnline}
|
|
169
|
+
<div class="alert alert-warning">Offline — changes will sync when reconnected</div>
|
|
170
|
+
{/if}
|
|
171
|
+
|
|
172
|
+
{#if syncStatus.pending > 0}
|
|
173
|
+
<div class="badge badge-warning">{syncStatus.pending} pending</div>
|
|
174
|
+
{/if}
|
|
175
|
+
|
|
176
|
+
{#each todos as todo}
|
|
177
|
+
<div class:opacity-60={todo._syncStatus === 'pending'}>
|
|
178
|
+
{todo.title}
|
|
179
|
+
{#if todo._syncStatus === 'conflict'}<span class="badge badge-error">conflict</span>{/if}
|
|
180
|
+
</div>
|
|
181
|
+
{/each}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Engine Sync Endpoints
|
|
185
|
+
|
|
186
|
+
The engine exposes two endpoints for batch sync:
|
|
187
|
+
|
|
188
|
+
- `POST /api/sync/push` — send offline operations to server
|
|
189
|
+
- `POST /api/sync/pull` — pull changes since a timestamp
|
|
190
|
+
|
|
191
|
+
These are used automatically by `SyncManager`. You can also call them directly for custom sync logic.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ZveltioConfig } from '../types/index.js';
|
|
2
|
+
export declare class Auth {
|
|
3
|
+
private config;
|
|
4
|
+
constructor(config: ZveltioConfig);
|
|
5
|
+
signIn(email: string, password: string): Promise<{
|
|
6
|
+
user: any;
|
|
7
|
+
session: any;
|
|
8
|
+
}>;
|
|
9
|
+
signUp(name: string, email: string, password: string): Promise<{
|
|
10
|
+
user: any;
|
|
11
|
+
}>;
|
|
12
|
+
signOut(): Promise<void>;
|
|
13
|
+
getSession(): Promise<{
|
|
14
|
+
user: any;
|
|
15
|
+
session: any;
|
|
16
|
+
} | null>;
|
|
17
|
+
signInWithOAuth(provider: 'google' | 'github' | 'microsoft'): void;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=Auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Auth.d.ts","sourceRoot":"","sources":["../../src/client/Auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,qBAAa,IAAI;IACf,OAAO,CAAC,MAAM,CAAgB;gBAElB,MAAM,EAAE,aAAa;IAI3B,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,EAAE,GAAG,CAAA;KAAE,CAAC;IAW7E,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAA;KAAE,CAAC;IAW7E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAOxB,UAAU,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,EAAE,GAAG,CAAA;KAAE,GAAG,IAAI,CAAC;IAQ/D,eAAe,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,IAAI;CAGnE"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export class Auth {
|
|
2
|
+
config;
|
|
3
|
+
constructor(config) {
|
|
4
|
+
this.config = config;
|
|
5
|
+
}
|
|
6
|
+
async signIn(email, password) {
|
|
7
|
+
const res = await fetch(`${this.config.baseUrl}/api/auth/sign-in/email`, {
|
|
8
|
+
method: 'POST',
|
|
9
|
+
credentials: 'include',
|
|
10
|
+
headers: { 'Content-Type': 'application/json' },
|
|
11
|
+
body: JSON.stringify({ email, password }),
|
|
12
|
+
});
|
|
13
|
+
if (!res.ok)
|
|
14
|
+
throw new Error((await res.json()).message || 'Sign in failed');
|
|
15
|
+
return res.json();
|
|
16
|
+
}
|
|
17
|
+
async signUp(name, email, password) {
|
|
18
|
+
const res = await fetch(`${this.config.baseUrl}/api/auth/sign-up/email`, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
credentials: 'include',
|
|
21
|
+
headers: { 'Content-Type': 'application/json' },
|
|
22
|
+
body: JSON.stringify({ name, email, password }),
|
|
23
|
+
});
|
|
24
|
+
if (!res.ok)
|
|
25
|
+
throw new Error((await res.json()).message || 'Sign up failed');
|
|
26
|
+
return res.json();
|
|
27
|
+
}
|
|
28
|
+
async signOut() {
|
|
29
|
+
await fetch(`${this.config.baseUrl}/api/auth/sign-out`, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
credentials: 'include',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async getSession() {
|
|
35
|
+
const res = await fetch(`${this.config.baseUrl}/api/me`, { credentials: 'include' });
|
|
36
|
+
if (!res.ok)
|
|
37
|
+
return null;
|
|
38
|
+
const data = await res.json();
|
|
39
|
+
return { user: data.user, session: {} };
|
|
40
|
+
}
|
|
41
|
+
// OAuth redirect
|
|
42
|
+
signInWithOAuth(provider) {
|
|
43
|
+
window.location.href = `${this.config.baseUrl}/api/auth/sign-in/${provider}`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ZveltioConfig, QueryOptions, QueryResponse } from '../types/index.js';
|
|
2
|
+
export declare class QueryBuilder {
|
|
3
|
+
private _collection;
|
|
4
|
+
private _config;
|
|
5
|
+
private _filters;
|
|
6
|
+
private _options;
|
|
7
|
+
constructor(collection: string, config: ZveltioConfig);
|
|
8
|
+
where(field: string, value: any): this;
|
|
9
|
+
where(field: string, op: string, value: any): this;
|
|
10
|
+
page(n: number): this;
|
|
11
|
+
limit(n: number): this;
|
|
12
|
+
sortBy(field: string, order?: 'asc' | 'desc'): this;
|
|
13
|
+
search(q: string): this;
|
|
14
|
+
query<T = any>(overrideOptions?: QueryOptions): Promise<QueryResponse<T>>;
|
|
15
|
+
[Symbol.asyncIterator]<T = any>(): AsyncGenerator<T[]>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=QueryBuilder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QueryBuilder.d.ts","sourceRoot":"","sources":["../../src/client/QueryBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEpF,qBAAa,YAAY;IACvB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,QAAQ,CAA2B;IAC3C,OAAO,CAAC,QAAQ,CAAoB;gBAExB,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa;IAMrD,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI;IACtC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI;IAWlD,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IACrB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAGtB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,KAAK,GAAG,MAAc,GAAG,IAAI;IAO1D,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAGjB,KAAK,CAAC,CAAC,GAAG,GAAG,EAAE,eAAe,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAiCxE,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,cAAc,CAAC,CAAC,EAAE,CAAC;CAY9D"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export class QueryBuilder {
|
|
2
|
+
_collection;
|
|
3
|
+
_config;
|
|
4
|
+
_filters = {};
|
|
5
|
+
_options = {};
|
|
6
|
+
constructor(collection, config) {
|
|
7
|
+
this._collection = collection;
|
|
8
|
+
this._config = config;
|
|
9
|
+
}
|
|
10
|
+
where(field, opOrValue, value) {
|
|
11
|
+
if (value === undefined) {
|
|
12
|
+
this._filters[field] = opOrValue;
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
this._filters[field] = { [opOrValue]: value };
|
|
16
|
+
}
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
// Pagination
|
|
20
|
+
page(n) { this._options.page = n; return this; }
|
|
21
|
+
limit(n) { this._options.limit = n; return this; }
|
|
22
|
+
// Sorting
|
|
23
|
+
sortBy(field, order = 'asc') {
|
|
24
|
+
this._options.sort = field;
|
|
25
|
+
this._options.order = order;
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
// Search
|
|
29
|
+
search(q) { this._options.search = q; return this; }
|
|
30
|
+
// Execute
|
|
31
|
+
async query(overrideOptions) {
|
|
32
|
+
const opts = { ...this._options, ...overrideOptions };
|
|
33
|
+
const params = new URLSearchParams();
|
|
34
|
+
if (opts.page)
|
|
35
|
+
params.set('page', String(opts.page));
|
|
36
|
+
if (opts.limit)
|
|
37
|
+
params.set('limit', String(opts.limit));
|
|
38
|
+
if (opts.sort)
|
|
39
|
+
params.set('sort', opts.sort);
|
|
40
|
+
if (opts.order)
|
|
41
|
+
params.set('order', opts.order);
|
|
42
|
+
if (opts.search)
|
|
43
|
+
params.set('search', opts.search);
|
|
44
|
+
if (Object.keys(this._filters).length > 0) {
|
|
45
|
+
params.set('filter', JSON.stringify(this._filters));
|
|
46
|
+
}
|
|
47
|
+
const qs = params.toString();
|
|
48
|
+
const url = `/api/data/${this._collection}${qs ? '?' + qs : ''}`;
|
|
49
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
50
|
+
if (this._config.apiKey)
|
|
51
|
+
headers['X-API-Key'] = this._config.apiKey;
|
|
52
|
+
const res = await fetch(`${this._config.baseUrl}${url}`, {
|
|
53
|
+
credentials: 'include',
|
|
54
|
+
headers,
|
|
55
|
+
});
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
58
|
+
throw new Error(err.error || `Query failed: ${res.status}`);
|
|
59
|
+
}
|
|
60
|
+
return res.json();
|
|
61
|
+
}
|
|
62
|
+
// Async iterable — allows `for await (const page of query)`
|
|
63
|
+
async *[Symbol.asyncIterator]() {
|
|
64
|
+
let currentPage = this._options.page || 1;
|
|
65
|
+
const pageSize = this._options.limit || 20;
|
|
66
|
+
while (true) {
|
|
67
|
+
const result = await this.page(currentPage).limit(pageSize).query();
|
|
68
|
+
if (result.records.length === 0)
|
|
69
|
+
break;
|
|
70
|
+
yield result.records;
|
|
71
|
+
if (currentPage >= result.pagination.pages)
|
|
72
|
+
break;
|
|
73
|
+
currentPage++;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { RealtimeMessage } from '../types/index.js';
|
|
2
|
+
type EventCallback = (message: RealtimeMessage) => void;
|
|
3
|
+
type StatusCallback = (status: 'connecting' | 'connected' | 'disconnected') => void;
|
|
4
|
+
export declare class RealtimeClient {
|
|
5
|
+
private ws;
|
|
6
|
+
private baseUrl;
|
|
7
|
+
private subscriptions;
|
|
8
|
+
private statusCallbacks;
|
|
9
|
+
private reconnectDelay;
|
|
10
|
+
private maxReconnectDelay;
|
|
11
|
+
private shouldReconnect;
|
|
12
|
+
constructor(baseUrl: string);
|
|
13
|
+
connect(): void;
|
|
14
|
+
disconnect(): void;
|
|
15
|
+
subscribe(collection: string, event: string | '*', callback: EventCallback): () => void;
|
|
16
|
+
onStatusChange(callback: StatusCallback): () => void;
|
|
17
|
+
private sendSubscribe;
|
|
18
|
+
private sendUnsubscribe;
|
|
19
|
+
private notifyStatus;
|
|
20
|
+
}
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=RealtimeClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RealtimeClient.d.ts","sourceRoot":"","sources":["../../src/client/RealtimeClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,KAAK,aAAa,GAAG,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;AACxD,KAAK,cAAc,GAAG,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW,GAAG,cAAc,KAAK,IAAI,CAAC;AAEpF,qBAAa,cAAc;IACzB,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,aAAa,CAAyC;IAC9D,OAAO,CAAC,eAAe,CAA6B;IACpD,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,eAAe,CAAQ;gBAEnB,OAAO,EAAE,MAAM;IAI3B,OAAO,IAAI,IAAI;IA4Cf,UAAU,IAAI,IAAI;IAQlB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE,QAAQ,EAAE,aAAa,GAAG,MAAM,IAAI;IAwBvF,cAAc,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,IAAI;IAKpD,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,YAAY;CAGrB"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export class RealtimeClient {
|
|
2
|
+
ws = null;
|
|
3
|
+
baseUrl;
|
|
4
|
+
subscriptions = new Map();
|
|
5
|
+
statusCallbacks = new Set();
|
|
6
|
+
reconnectDelay = 1000;
|
|
7
|
+
maxReconnectDelay = 30000;
|
|
8
|
+
shouldReconnect = true;
|
|
9
|
+
constructor(baseUrl) {
|
|
10
|
+
this.baseUrl = baseUrl;
|
|
11
|
+
}
|
|
12
|
+
connect() {
|
|
13
|
+
if (this.ws?.readyState === WebSocket.OPEN)
|
|
14
|
+
return;
|
|
15
|
+
const wsUrl = this.baseUrl.replace(/^http/, 'ws') + '/api/ws';
|
|
16
|
+
this.ws = new WebSocket(wsUrl);
|
|
17
|
+
this.notifyStatus('connecting');
|
|
18
|
+
this.ws.onopen = () => {
|
|
19
|
+
this.reconnectDelay = 1000;
|
|
20
|
+
this.notifyStatus('connected');
|
|
21
|
+
// Re-subscribe all active subscriptions
|
|
22
|
+
for (const channel of this.subscriptions.keys()) {
|
|
23
|
+
this.sendSubscribe(channel);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
this.ws.onmessage = (event) => {
|
|
27
|
+
try {
|
|
28
|
+
const message = JSON.parse(event.data);
|
|
29
|
+
const key = `${message.collection}:${message.event}`;
|
|
30
|
+
const wildcard = `${message.collection}:*`;
|
|
31
|
+
const allWildcard = `*:*`;
|
|
32
|
+
for (const [sub, callbacks] of this.subscriptions) {
|
|
33
|
+
if (sub === key || sub === wildcard || sub === allWildcard) {
|
|
34
|
+
callbacks.forEach((cb) => cb(message));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch { /* ignore invalid messages */ }
|
|
39
|
+
};
|
|
40
|
+
this.ws.onclose = () => {
|
|
41
|
+
this.notifyStatus('disconnected');
|
|
42
|
+
if (this.shouldReconnect) {
|
|
43
|
+
setTimeout(() => this.connect(), this.reconnectDelay);
|
|
44
|
+
this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
this.ws.onerror = () => {
|
|
48
|
+
this.ws?.close();
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
disconnect() {
|
|
52
|
+
this.shouldReconnect = false;
|
|
53
|
+
this.ws?.close();
|
|
54
|
+
this.ws = null;
|
|
55
|
+
}
|
|
56
|
+
// Subscribe to events on a collection
|
|
57
|
+
// pattern examples: 'products:insert', 'products:*', '*:*'
|
|
58
|
+
subscribe(collection, event, callback) {
|
|
59
|
+
const key = `${collection}:${event}`;
|
|
60
|
+
if (!this.subscriptions.has(key)) {
|
|
61
|
+
this.subscriptions.set(key, new Set());
|
|
62
|
+
}
|
|
63
|
+
this.subscriptions.get(key).add(callback);
|
|
64
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
65
|
+
this.sendSubscribe(key);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
this.connect();
|
|
69
|
+
}
|
|
70
|
+
// Return unsubscribe function
|
|
71
|
+
return () => {
|
|
72
|
+
this.subscriptions.get(key)?.delete(callback);
|
|
73
|
+
if (this.subscriptions.get(key)?.size === 0) {
|
|
74
|
+
this.subscriptions.delete(key);
|
|
75
|
+
this.sendUnsubscribe(key);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
onStatusChange(callback) {
|
|
80
|
+
this.statusCallbacks.add(callback);
|
|
81
|
+
return () => this.statusCallbacks.delete(callback);
|
|
82
|
+
}
|
|
83
|
+
sendSubscribe(channel) {
|
|
84
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
85
|
+
this.ws.send(JSON.stringify({ type: 'subscribe', channel }));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
sendUnsubscribe(channel) {
|
|
89
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
90
|
+
this.ws.send(JSON.stringify({ type: 'unsubscribe', channel }));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
notifyStatus(status) {
|
|
94
|
+
this.statusCallbacks.forEach((cb) => cb(status));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ZveltioConfig, QueryOptions, QueryResponse, DeleteResponse } from '../types/index.js';
|
|
2
|
+
import { QueryBuilder } from './QueryBuilder.js';
|
|
3
|
+
import { Auth } from './Auth.js';
|
|
4
|
+
import { RealtimeClient } from './RealtimeClient.js';
|
|
5
|
+
export declare class ZveltioClient {
|
|
6
|
+
readonly auth: Auth;
|
|
7
|
+
readonly realtime: RealtimeClient;
|
|
8
|
+
private config;
|
|
9
|
+
constructor(config: ZveltioConfig);
|
|
10
|
+
from(collection: string): QueryBuilder;
|
|
11
|
+
list<T = any>(collection: string, options?: QueryOptions): Promise<QueryResponse<T>>;
|
|
12
|
+
get<T = any>(collection: string, id: string): Promise<T>;
|
|
13
|
+
create<T = any>(collection: string, data: Partial<T>): Promise<T>;
|
|
14
|
+
update<T = any>(collection: string, id: string, data: Partial<T>): Promise<T>;
|
|
15
|
+
replace<T = any>(collection: string, id: string, data: T): Promise<T>;
|
|
16
|
+
delete(collection: string, id: string): Promise<DeleteResponse>;
|
|
17
|
+
request<T>(path: string, init?: RequestInit): Promise<T>;
|
|
18
|
+
}
|
|
19
|
+
export declare function createClient(config: ZveltioConfig): ZveltioClient;
|
|
20
|
+
//# sourceMappingURL=ZveltioClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ZveltioClient.d.ts","sourceRoot":"","sources":["../../src/client/ZveltioClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAkC,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACpI,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,qBAAa,aAAa;IACxB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,OAAO,CAAC,MAAM,CAAgB;gBAElB,MAAM,EAAE,aAAa;IAOjC,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY;IAKhC,IAAI,CAAC,CAAC,GAAG,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAIpF,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAKxD,MAAM,CAAC,CAAC,GAAG,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAQjE,MAAM,CAAC,CAAC,GAAG,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAQ7E,OAAO,CAAC,CAAC,GAAG,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAQrE,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAO/D,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;CA4B/D;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CAEjE"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { QueryBuilder } from './QueryBuilder.js';
|
|
2
|
+
import { Auth } from './Auth.js';
|
|
3
|
+
import { RealtimeClient } from './RealtimeClient.js';
|
|
4
|
+
export class ZveltioClient {
|
|
5
|
+
auth;
|
|
6
|
+
realtime;
|
|
7
|
+
config;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this.config = config;
|
|
10
|
+
this.auth = new Auth(config);
|
|
11
|
+
this.realtime = new RealtimeClient(config.baseUrl);
|
|
12
|
+
}
|
|
13
|
+
// Fluent query builder
|
|
14
|
+
from(collection) {
|
|
15
|
+
return new QueryBuilder(collection, this.config);
|
|
16
|
+
}
|
|
17
|
+
// Direct CRUD methods
|
|
18
|
+
async list(collection, options) {
|
|
19
|
+
return this.from(collection).query(options);
|
|
20
|
+
}
|
|
21
|
+
async get(collection, id) {
|
|
22
|
+
const res = await this.request(`/api/data/${collection}/${id}`);
|
|
23
|
+
return res.record;
|
|
24
|
+
}
|
|
25
|
+
async create(collection, data) {
|
|
26
|
+
const res = await this.request(`/api/data/${collection}`, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
body: JSON.stringify(data),
|
|
29
|
+
});
|
|
30
|
+
return res.record;
|
|
31
|
+
}
|
|
32
|
+
async update(collection, id, data) {
|
|
33
|
+
const res = await this.request(`/api/data/${collection}/${id}`, {
|
|
34
|
+
method: 'PATCH',
|
|
35
|
+
body: JSON.stringify(data),
|
|
36
|
+
});
|
|
37
|
+
return res.record;
|
|
38
|
+
}
|
|
39
|
+
async replace(collection, id, data) {
|
|
40
|
+
const res = await this.request(`/api/data/${collection}/${id}`, {
|
|
41
|
+
method: 'PUT',
|
|
42
|
+
body: JSON.stringify(data),
|
|
43
|
+
});
|
|
44
|
+
return res.record;
|
|
45
|
+
}
|
|
46
|
+
async delete(collection, id) {
|
|
47
|
+
return this.request(`/api/data/${collection}/${id}`, {
|
|
48
|
+
method: 'DELETE',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
// Raw request helper
|
|
52
|
+
async request(path, init) {
|
|
53
|
+
const headers = {
|
|
54
|
+
'Content-Type': 'application/json',
|
|
55
|
+
...init?.headers,
|
|
56
|
+
};
|
|
57
|
+
if (this.config.apiKey) {
|
|
58
|
+
headers['X-API-Key'] = this.config.apiKey;
|
|
59
|
+
}
|
|
60
|
+
const res = await fetch(`${this.config.baseUrl}${path}`, {
|
|
61
|
+
...init,
|
|
62
|
+
credentials: 'include',
|
|
63
|
+
headers,
|
|
64
|
+
});
|
|
65
|
+
if (res.status === 401) {
|
|
66
|
+
this.config.onUnauthorized?.();
|
|
67
|
+
throw new Error('Unauthorized');
|
|
68
|
+
}
|
|
69
|
+
if (!res.ok) {
|
|
70
|
+
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
71
|
+
throw new Error(err.error || `Request failed: ${res.status}`);
|
|
72
|
+
}
|
|
73
|
+
return res.json();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function createClient(config) {
|
|
77
|
+
return new ZveltioClient(config);
|
|
78
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export interface ZveltioClientConfig {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
headers?: Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
export interface ListParams {
|
|
7
|
+
page?: number;
|
|
8
|
+
limit?: number;
|
|
9
|
+
sort?: string;
|
|
10
|
+
order?: 'asc' | 'desc';
|
|
11
|
+
search?: string;
|
|
12
|
+
filter?: Record<string, any>;
|
|
13
|
+
cursor?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ListResult<T> {
|
|
16
|
+
data: T[];
|
|
17
|
+
total: number;
|
|
18
|
+
page: number;
|
|
19
|
+
limit: number;
|
|
20
|
+
next_cursor?: string;
|
|
21
|
+
}
|
|
22
|
+
declare class CollectionRef<T extends Record<string, any>> {
|
|
23
|
+
private readonly name;
|
|
24
|
+
private readonly client;
|
|
25
|
+
constructor(name: string, client: ZveltioClient<any>);
|
|
26
|
+
list(params?: ListParams): Promise<ListResult<T>>;
|
|
27
|
+
getMany(params?: ListParams): Promise<ListResult<T>>;
|
|
28
|
+
get(id: string): Promise<T>;
|
|
29
|
+
getOne(id: string): Promise<T>;
|
|
30
|
+
create(data: Omit<T, 'id' | 'created_at' | 'updated_at'>): Promise<T>;
|
|
31
|
+
update(id: string, data: Partial<Omit<T, 'id' | 'created_at' | 'updated_at'>>): Promise<T>;
|
|
32
|
+
delete(id: string): Promise<{
|
|
33
|
+
success: boolean;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* ZveltioClient — generic over your schema type.
|
|
38
|
+
*
|
|
39
|
+
* Usage with generated types (run `zveltio generate-types` once):
|
|
40
|
+
*
|
|
41
|
+
* import type { ZveltioSchema } from './zveltio-types';
|
|
42
|
+
* const client = createZveltioClient<ZveltioSchema>({ baseUrl: '...' });
|
|
43
|
+
* const { data } = await client.collection('products').list();
|
|
44
|
+
* // ^-- typed as your Products interface
|
|
45
|
+
*
|
|
46
|
+
* Usage without types (untyped, same as before):
|
|
47
|
+
* const client = createZveltioClient({ baseUrl: '...' });
|
|
48
|
+
*/
|
|
49
|
+
export declare class ZveltioClient<Schema extends Record<string, any> = Record<string, any>> {
|
|
50
|
+
private baseUrl;
|
|
51
|
+
private headers;
|
|
52
|
+
constructor(config: ZveltioClientConfig);
|
|
53
|
+
private request;
|
|
54
|
+
get<T = any>(path: string): Promise<T>;
|
|
55
|
+
post<T = any>(path: string, body?: unknown): Promise<T>;
|
|
56
|
+
patch<T = any>(path: string, body?: unknown): Promise<T>;
|
|
57
|
+
delete<T = any>(path: string): Promise<T>;
|
|
58
|
+
upload<T = any>(path: string, formData: FormData): Promise<T>;
|
|
59
|
+
/**
|
|
60
|
+
* Returns a fully-typed collection reference.
|
|
61
|
+
* If Schema is provided (via createZveltioClient<MySchema>()),
|
|
62
|
+
* the return type reflects the collection's record shape.
|
|
63
|
+
*/
|
|
64
|
+
collection<K extends keyof Schema & string>(name: K): CollectionRef<Schema[K]>;
|
|
65
|
+
collection(name: string): CollectionRef<Record<string, any>>;
|
|
66
|
+
/** Auth helpers */
|
|
67
|
+
readonly auth: {
|
|
68
|
+
readonly login: (email: string, password: string) => Promise<any>;
|
|
69
|
+
readonly signup: (email: string, password: string, name: string) => Promise<any>;
|
|
70
|
+
readonly logout: () => Promise<any>;
|
|
71
|
+
readonly session: () => Promise<any>;
|
|
72
|
+
};
|
|
73
|
+
/** Storage helpers */
|
|
74
|
+
readonly storage: {
|
|
75
|
+
readonly upload: (file: File, folder?: string) => Promise<any>;
|
|
76
|
+
readonly list: (folder?: string) => Promise<any>;
|
|
77
|
+
readonly delete: (key: string) => Promise<any>;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export declare function createZveltioClient<Schema extends Record<string, any> = Record<string, any>>(config: ZveltioClientConfig): ZveltioClient<Schema>;
|
|
81
|
+
export {};
|
|
82
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|