@zuzjs/flare 0.2.1

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 Zuz.js Team
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,58 @@
1
+ # 🔥 ZuzFlare Client
2
+
3
+ > Official JavaScript/TypeScript client for ZuzFlare Server
4
+
5
+ [![npm version](https://badge.fury.io/js/%40zuzjs%2Fflare.svg)](https://www.npmjs.com/package/@zuzjs/flare)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ Real-time database client with Firebase-like API for your self-hosted ZuzFlare server.
9
+
10
+ ## 📦 Installation
11
+
12
+ ```bash
13
+ npm install @zuzjs/flare
14
+ ```
15
+
16
+ ## 🚀 Quick Start
17
+
18
+ ```typescript
19
+ import { FlareClient } from '@zuzjs/flare';
20
+
21
+ const flare = new FlareClient({
22
+ endpoint: 'http://localhost:5050',
23
+ appId: 'my-app'
24
+ });
25
+
26
+ flare.connect();
27
+
28
+ // Write data
29
+ await flare.collection('users').doc('alice').set({
30
+ name: 'Alice',
31
+ email: 'alice@example.com'
32
+ });
33
+
34
+ // Real-time updates
35
+ flare.collection('users').onSnapshot((data) => {
36
+ console.log('Users:', data);
37
+ });
38
+ ```
39
+
40
+ ## 📖 Full Documentation
41
+
42
+ See [complete documentation](https://flare.zuz.com.pk) for:
43
+ - API Reference
44
+ - Usage Examples
45
+ - TypeScript Types
46
+ - React/Vue/Svelte Integration
47
+ - Authentication
48
+ - Offline Support
49
+
50
+ ## 🔗 Links
51
+
52
+ - [Server Package](@zuzjs/flare-server)
53
+ - [GitHub](https://github.com/zuzjs/flare-client)
54
+ - [Documentation](https://flare.zuz.com.pk)
55
+
56
+ ## 📄 License
57
+
58
+ MIT © Zuz.js Team
package/dist/index.cjs ADDED
@@ -0,0 +1,3 @@
1
+ 'use strict';Object.defineProperty(exports,'__esModule',{value:true});var core=require('@zuzjs/core'),auth=require('@zuzjs/auth');/* ZuzFlare Client */
2
+ var C=(c=>(c.SUBSCRIBE="subscribe",c.UNSUBSCRIBE="unsubscribe",c.WRITE="write",c.DELETE="delete",c.AUTH="auth",c.PING="ping",c.OFFLINE_SYNC="offline_sync",c.CALL="call",c))(C||{}),v=(c=>(c.SNAPSHOT="snapshot",c.CHANGE="change",c.ERROR="error",c.ACK="ack",c.PONG="pong",c.AUTH_OK="auth_ok",c.OFFLINE_ACK="offline_ack",c.CALL_RESPONSE="call_response",c))(v||{});function S(r){let e=[];for(let[t,i]of Object.entries(r))if(typeof i=="string"){let o=i.match(/^(>=|<=|!=|>|<|==)\s*(.+)$/);if(o){let[,n,s]=o;e.push({field:t,op:n,value:w(s.trim())});}else e.push({field:t,op:"==",value:i});}else Array.isArray(i)?e.push({field:t,op:"in",value:i}):e.push({field:t,op:"==",value:i});return e}function w(r){if(!isNaN(Number(r)))return Number(r);if(r==="true")return true;if(r==="false")return false;if(r==="null")return null;if(r!=="undefined")return r}var a=class{constructor(e,t,i){this.client=e;this.collection=t;this.legacyId=i;}whereCondition;updateData;setData;deleteOp=false;promise;where(e){return this.whereCondition=e,this}update(e){return this.updateData=e,this}set(e){return this.setData=e,this}delete(){return this.deleteOp=true,this}getDocId(){if(this.legacyId)return this.legacyId;if(this.whereCondition&&this.whereCondition.id){let e=this.whereCondition.id;if(typeof e=="string")return e}throw new Error('Document ID not specified. Use .where({ id: "..." }) or doc(collection, id)')}async execute(){return this._execute()}async _execute(){let e=this.getDocId();if(this.deleteOp){await this.client.send("delete",{collection:this.collection,docId:e});return}if(this.updateData){await this.client.send("write",{collection:this.collection,docId:e,data:this.updateData,merge:true});return}if(this.setData){await this.client.send("write",{collection:this.collection,docId:e,data:this.setData,merge:false});return}return this.get()}then(e,t){return this.promise||(this.promise=this._execute()),this.promise.then(e,t)}async get(){let e=this.getDocId(),t=core.uuid2(18);return new Promise((i,o)=>{let n=this.client.subscribe(t,this.collection,e,void 0,s=>{s.type==="snapshot"&&(n(),i(s.data));});setTimeout(()=>{n(),o(new Error("Document fetch timeout"));},1e4);})}onSnapshot(e){let t=this.getDocId(),i=core.uuid2(18);return this.client.subscribe(i,this.collection,t,void 0,e)}},h=class{constructor(e,t,i){this.client=e;this.collection=t;this.id=i;}async get(){return new a(this.client,this.collection,this.id).get()}async set(e){await this.client.send("write",{collection:this.collection,docId:this.id,data:e,merge:false});}async update(e){await this.client.send("write",{collection:this.collection,docId:this.id,data:e,merge:true});}async delete(){await this.client.send("delete",{collection:this.collection,docId:this.id});}onSnapshot(e){let t=core.uuid2(18);return this.client.subscribe(t,this.collection,this.id,void 0,e)}},f=class r{constructor(e,t){this.client=e;this.collection=t;}queries=[];promise;doc(e){return new h(this.client,this.collection,e)}where(e){let t=new r(this.client,this.collection),i=S(e);return t.queries=[...this.queries,...i],t}async get(){return this._execute()}async _execute(){let e=core.uuid2(18);return new Promise((t,i)=>{let o=this.queries.length>0?this.queries:void 0,n=this.client.subscribe(e,this.collection,void 0,o,s=>{s.type==="snapshot"&&(n(),t(Array.isArray(s.data)?s.data:[s.data]));});setTimeout(()=>{n(),i(new Error("Collection fetch timeout"));},1e4);})}then(e,t){return this.promise||(this.promise=this._execute()),this.promise.then(e,t)}onSnapshot(e){let t=core.uuid2(18),i=this.queries.length>0?this.queries:void 0;return this.client.subscribe(t,this.collection,void 0,i,e)}async add(e){let t=core.uuid2(18),i=this.doc(t);return await i.set(e),i}update(e){return new a(this.client,this.collection).update(e)}delete(){return new a(this.client,this.collection).delete()}};async function R(r){let e=r.replace(/-----BEGIN PUBLIC KEY-----/,"").replace(/-----END PUBLIC KEY-----/,"").replace(/\s+/g,""),t=typeof atob<"u"?atob(e):Buffer.from(e,"base64").toString("binary"),i=new Uint8Array(t.length);for(let n=0;n<t.length;n++)i[n]=t.charCodeAt(n);return (globalThis.crypto??(await import('crypto')).webcrypto).subtle.importKey("spki",i.buffer,{name:"RSA-OAEP",hash:"SHA-256"},false,["encrypt"])}async function I(r,e){let t=await R(e),i=new TextEncoder().encode(JSON.stringify(r)),n=await(globalThis.crypto??(await import('crypto')).webcrypto).subtle.encrypt({name:"RSA-OAEP"},t,i),s=typeof btoa<"u"?btoa(String.fromCharCode(...new Uint8Array(n))):Buffer.from(n).toString("base64");return JSON.stringify({enc:"rsa",data:s})}var g=class{socket=null;reconnectInterval;maxReconnectDelay;isConnected=false;shouldReconnect=true;options;messageQueue=[];heartbeatInterval=null;connectionTimeout=null;constructor(e){this.options=e,this.reconnectInterval=e.reconnectDelay||2,this.maxReconnectDelay=e.maxReconnectDelay||60,this.log("Transport initialized",e.url);}connect(){if(this.socket){this.log("Socket already exists, skipping connection");return}this.log("Connecting to",this.options.url),this.socket=new WebSocket(this.options.url),this.connectionTimeout=setTimeout(()=>{this.isConnected||(this.log("Connection timeout"),this.socket?.close(),this.handleReconnect());},1e4),this.socket.onopen=()=>{this.connectionTimeout&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=null),this.isConnected=true,this.reconnectInterval=this.options.reconnectDelay||2,this.log("Connected to server"),this.options.onOpen?.(),this.startHeartbeat(),this.flushQueue();},this.socket.onmessage=e=>{try{let t=JSON.parse(e.data);this.options.onMessage(t);}catch(t){this.log("Parse error",t),this.options.onError?.(t);}},this.socket.onerror=e=>{this.log("WebSocket error",e),this.options.onError?.(new Error("WebSocket error"));},this.socket.onclose=e=>{this.connectionTimeout&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=null),this.isConnected=false,this.socket=null,this.stopHeartbeat(),this.log("Connection closed",e.code,e.reason),this.options.onClose?.(),e.code!==1e3&&this.shouldReconnect&&this.options.autoReconnect&&this.handleReconnect();};}handleReconnect(){let e=this.reconnectInterval*1e3;this.log(`Reconnecting in ${this.reconnectInterval}s...`),setTimeout(()=>{this.reconnectInterval=Math.min(this.reconnectInterval*2,this.maxReconnectDelay),this.connect();},e);}startHeartbeat(){this.heartbeatInterval=setInterval(()=>{this.isConnected&&this.send({type:"ping",id:Date.now().toString(),ts:Date.now()});},3e4);}stopHeartbeat(){this.heartbeatInterval&&(clearInterval(this.heartbeatInterval),this.heartbeatInterval=null);}flushQueue(){for(this.log("Flushing message queue",this.messageQueue.length);this.messageQueue.length>0;){let e=this.messageQueue.shift();e&&this.send(e);}}send(e){if(this.socket&&this.socket.readyState===WebSocket.OPEN){let t=i=>{try{this.socket.send(i),this.log("Sent message",e);}catch(o){this.log("Send error",o),this.messageQueue.push(e);}};this.options.publicKey?I(e,this.options.publicKey).then(t).catch(i=>{this.log("RSA encrypt error \u2014 sending plaintext",i),t(JSON.stringify(e));}):t(JSON.stringify(e));}else this.log("Socket not ready, queueing message"),this.messageQueue.push(e);}disconnect(){this.shouldReconnect=false,this.stopHeartbeat(),this.socket&&(this.socket.close(1e3,"Client disconnect"),this.socket=null),this.isConnected=false,this.log("Disconnected");}get connected(){return this.isConnected}log(...e){this.options.debug&&console.log("[FlareTransport]",...e);}};var m=class{transport;config;pendingAcks=new Map;subscriptions=new Map;offlineQueue=[];currentState="disconnected";connectionListeners=[];errorListeners=[];authToken;userId;isDebug=false;constructor(e){this.config={autoReconnect:true,reconnectDelay:2,maxReconnectDelay:60,debug:false,connectionTimeout:1e4,...e},this.isDebug=this.config.debug||false;let{hostname:t,port:i,protocol:o}=new URL(this.config.endpoint),n=o==="https:",c=`${n?"wss":"ws"}://${t}:${i||(n?"443":"80")}/?appId=${this.config.appId}${this.config.apiKey?`&apiKey=${this.config.apiKey}`:""}`;this.transport=new g({url:c,publicKey:this.config.publicKey,autoReconnect:this.config.autoReconnect,reconnectDelay:this.config.reconnectDelay,maxReconnectDelay:this.config.maxReconnectDelay,onMessage:y=>this.handleIncoming(y),onOpen:()=>this.onConnected(),onClose:()=>this.onDisconnected(),onError:y=>this.handleTransportError(y),debug:this.isDebug}),this.log("FlareClient initialized",e);}connect(){this.setState("connecting"),this.transport.connect();}disconnect(){this.transport.disconnect(),this.setState("disconnected");}collection(e){return new f(this,e)}doc(e,t){return t!==void 0?new h(this,e,t):new a(this,e)}async auth(e){let t=await this.send("auth",{token:e});if(t.type==="auth_ok")return this.authToken=e,this.userId=t.uid,this.log("Authentication successful",t.uid),{uid:t.uid,token:e};throw new Error("Authentication failed")}signOut(){this.authToken=void 0,this.userId=void 0,this.log("Signed out");}get currentUser(){return this.userId}get connectionState(){return this.currentState}get isConnected(){return this.currentState==="connected"}onConnectionStateChange(e){return this.connectionListeners.push(e),()=>{this.connectionListeners=this.connectionListeners.filter(t=>t!==e);}}onError(e){return this.errorListeners.push(e),()=>{this.errorListeners=this.errorListeners.filter(t=>t!==e);}}async ping(){let e=Date.now();return await this.send("ping",{}),Date.now()-e}async call(e,t={}){let i=await this.send("call",{topic:e,payload:t});if(!i.success)throw new Error(i.error??`CALL "${e}" failed`);return i.result}async syncOffline(){if(this.offlineQueue.length===0)return;this.log("Syncing offline operations",this.offlineQueue.length);let e=[...this.offlineQueue];this.offlineQueue.length=0;let t=await this.send("offline_sync",{operations:e});t.conflicts&&t.conflicts.length>0&&(this.log("Offline sync conflicts",t.conflicts),t.conflicts.forEach(i=>{let o=e.find(n=>n.id===i.operationId);o&&this.offlineQueue.push(o);}));}handleTransportError(e){this.log("Transport error",e),this.errorListeners.forEach(t=>{try{t(e);}catch(i){this.log("Error listener error",i);}});}onConnected(){this.setState("connected"),this.log("Connected to FlareServer"),this.offlineQueue.length>0&&this.syncOffline().catch(e=>{this.log("Offline sync failed",e);});}onDisconnected(){this.currentState!=="disconnected"&&this.setState("reconnecting"),this.log("Disconnected from FlareServer");}setState(e){this.currentState!==e&&(this.currentState=e,this.log("Connection state changed",e),this.connectionListeners.forEach(t=>{try{t(e);}catch(i){this.log("Connection listener error",i);}}));}handleIncoming(e){if(this.log("Received message",e.type,e),e.type==="ack"||e.type==="pong"||e.type==="auth_ok"||e.type==="call_response"){let t=this.pendingAcks.get(e.correlationId||e.id);t&&(t(e),this.pendingAcks.delete(e.correlationId||e.id));return}if(e.type==="error"){this.log("Server error",e.code,e.message);let t=new Error(`[${e.code}] ${e.message}`);if(this.errorListeners.forEach(i=>{try{i(t);}catch(o){this.log("Error listener error",o);}}),e.correlationId){let i=this.pendingAcks.get(e.correlationId);i&&(i(e),this.pendingAcks.delete(e.correlationId));}return}if(e.type==="snapshot"||e.type==="change"){let t=this.subscriptions.get(e.subscriptionId);if(t){let i={subscriptionId:e.subscriptionId,collection:e.collection,docId:e.docId,data:e.data,type:e.type==="snapshot"?"snapshot":"change",operation:e.operation};try{t(i);}catch(o){this.log("Subscription callback error",o);}}}}async send(e,t){return new Promise((i,o)=>{let n=core.uuid2(18),s={id:n,type:e,ts:Date.now(),...t};this.pendingAcks.set(n,d=>{d.type==="error"?o(new Error(`[${d.code}] ${d.message}`)):i(d);}),this.isConnected?this.transport.send(s):(this.log("Queueing message for offline",s),this.offlineQueue.push(s),o(new Error("Not connected - message queued"))),setTimeout(()=>{this.pendingAcks.has(n)&&(this.pendingAcks.delete(n),o(new Error("Request timeout")));},this.config.connectionTimeout);})}subscribe(e,t,i,o,n){return this.log("Creating subscription",e,t,i),this.subscriptions.set(e,n),this.send("subscribe",{collection:t,docId:i,query:o}).then(s=>{s.subscriptionId&&(this.subscriptions.delete(e),this.subscriptions.set(s.subscriptionId,n));}).catch(s=>{this.log("Subscription failed",s),this.subscriptions.delete(e);}),()=>{this.log("Unsubscribing",e),this.subscriptions.delete(e),this.isConnected&&this.send("unsubscribe",{subscriptionId:e}).catch(s=>this.log("Unsubscribe failed",s));}}log(...e){this.isDebug&&console.log("[FlareClient]",...e);}},b=m;var T=class extends Error{constructor(t,i,o){super(t);this.code=i;this.cause=o;this.name="ZuzFlareError";}};var l=null,q=r=>(l||(l=new b(r),l.connect()),l),$=()=>l,G=()=>{l&&(l.disconnect(),l=null);};var J=b;
3
+ Object.defineProperty(exports,"AuthGuard",{enumerable:true,get:function(){return auth.AuthGuard}});Object.defineProperty(exports,"Dropbox",{enumerable:true,get:function(){return auth.Dropbox}});Object.defineProperty(exports,"Google",{enumerable:true,get:function(){return auth.Google}});exports.CollectionReference=f;exports.DocumentQueryBuilder=a;exports.DocumentReference=h;exports.FlareAction=C;exports.FlareError=T;exports.FlareEvent=v;exports.connectApp=q;exports.default=J;exports.disconnectFlare=G;exports.getFlare=$;
@@ -0,0 +1,379 @@
1
+ export { AuthGuard, AuthToken, Dropbox, Google, NormalizedProfile, OAuthProvider, ProviderId } from '@zuzjs/auth';
2
+
3
+ /**
4
+ * Client Configuration
5
+ */
6
+ interface FlareConfig {
7
+ /** WebSocket endpoint URL (e.g., 'http://localhost:5050' or 'wss://api.example.com') */
8
+ endpoint: string;
9
+ /** Application/Project ID */
10
+ appId: string;
11
+ /** API Key for authentication (optional) */
12
+ apiKey?: string;
13
+ /**
14
+ * RSA public key (PEM) returned by `flare app create`.
15
+ * When provided, all outgoing messages are wrapped in an RSA-OAEP encrypted
16
+ * envelope: `{ enc: "rsa", data: "<base64>" }` before being sent over the wire.
17
+ */
18
+ publicKey?: string;
19
+ /** Enable automatic reconnection (default: true) */
20
+ autoReconnect?: boolean;
21
+ /** Initial reconnect delay in seconds (default: 2) */
22
+ reconnectDelay?: number;
23
+ /** Maximum reconnect delay in seconds (default: 60) */
24
+ maxReconnectDelay?: number;
25
+ /** Enable debug logging (default: false) */
26
+ debug?: boolean;
27
+ /** Connection timeout in milliseconds (default: 10000) */
28
+ connectionTimeout?: number;
29
+ }
30
+ /**
31
+ * Query configuration (NEW v0.2: Object-based query syntax)
32
+ */
33
+ interface QueryConfig {
34
+ field: string;
35
+ op: QueryOperator;
36
+ value: unknown;
37
+ }
38
+ /**
39
+ * Where condition (NEW v0.2: Object-based syntax)
40
+ * Example: { age: ">= 25", role: "admin" }
41
+ */
42
+ type WhereCondition = Record<string, string | number | boolean | any[]>;
43
+ type QueryOperator = "==" | "!=" | "<" | "<=" | ">" | ">=" | "in" | "not-in" | "array-contains";
44
+ /**
45
+ * Subscription callback
46
+ */
47
+ type SubscriptionCallback<T = any> = (data: SubscriptionData<T>) => void;
48
+ /**
49
+ * Subscription data received from server
50
+ */
51
+ interface SubscriptionData<T = any> {
52
+ subscriptionId: string;
53
+ collection: string;
54
+ docId?: string;
55
+ data: T | T[];
56
+ type: 'snapshot' | 'change';
57
+ operation?: 'insert' | 'update' | 'delete' | 'replace';
58
+ }
59
+ /**
60
+ * Document reference
61
+ */
62
+ interface DocumentSnapshot<T = any> {
63
+ id: string;
64
+ data: T | null;
65
+ exists: boolean;
66
+ }
67
+ /**
68
+ * Collection query result
69
+ */
70
+ interface QuerySnapshot<T = any> {
71
+ docs: DocumentSnapshot<T>[];
72
+ size: number;
73
+ empty: boolean;
74
+ }
75
+ /**
76
+ * Offline operation
77
+ */
78
+ interface OfflineOperation {
79
+ id: string;
80
+ type: 'write' | 'delete';
81
+ collection: string;
82
+ docId: string;
83
+ data?: Record<string, unknown>;
84
+ merge?: boolean;
85
+ clientTs: number;
86
+ }
87
+ /**
88
+ * Authentication result
89
+ */
90
+ interface AuthResult {
91
+ uid: string;
92
+ token?: string;
93
+ }
94
+ /**
95
+ * Connection state
96
+ */
97
+ type ConnectionState = 'connecting' | 'connected' | 'disconnected' | 'reconnecting' | 'error';
98
+
99
+ /**
100
+ * Query builder for document operations (ORM-style)
101
+ * Supports: doc('users').update({...}).where({ id: 'alice' })
102
+ *
103
+ * This class is thenable - you can await it directly without .execute() or .get()
104
+ * @example
105
+ * await doc('users').where({ id: 'alice' })
106
+ * await doc('users').update({ name: 'Alice' }).where({ id: 'alice' })
107
+ */
108
+ declare class DocumentQueryBuilder<T = any> implements PromiseLike<T | null | void> {
109
+ private client;
110
+ private collection;
111
+ private legacyId?;
112
+ private whereCondition?;
113
+ private updateData?;
114
+ private setData?;
115
+ private deleteOp;
116
+ private promise?;
117
+ constructor(client: FlareClient, collection: string, legacyId?: string | undefined);
118
+ /**
119
+ * Set where condition
120
+ */
121
+ where(condition: WhereCondition): this;
122
+ /**
123
+ * Set update data (for update operations)
124
+ */
125
+ update(data: Partial<T>): this;
126
+ /**
127
+ * Set data (for set operations)
128
+ */
129
+ set(data: Partial<T>): this;
130
+ /**
131
+ * Mark for deletion
132
+ */
133
+ delete(): this;
134
+ /**
135
+ * Get the document ID from where condition or legacy id
136
+ */
137
+ private getDocId;
138
+ /**
139
+ * Execute the query
140
+ * @deprecated Use await directly instead of .execute()
141
+ */
142
+ execute(): Promise<T | null | void>;
143
+ /**
144
+ * Internal execute method
145
+ */
146
+ private _execute;
147
+ /**
148
+ * Make this class thenable so it can be awaited directly
149
+ */
150
+ then<TResult1 = T | null | void, TResult2 = never>(onfulfilled?: ((value: T | null | void) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): PromiseLike<TResult1 | TResult2>;
151
+ /**
152
+ * Get the document data once
153
+ */
154
+ get(): Promise<T | null>;
155
+ /**
156
+ * Subscribe to real-time updates
157
+ */
158
+ onSnapshot(callback: SubscriptionCallback<T>): () => void;
159
+ }
160
+ /**
161
+ * Legacy document reference (for backward compatibility)
162
+ */
163
+ declare class DocumentReference<T = any> {
164
+ private client;
165
+ readonly collection: string;
166
+ readonly id: string;
167
+ constructor(client: FlareClient, collection: string, id: string);
168
+ get(): Promise<T | null>;
169
+ set(data: Partial<T>): Promise<void>;
170
+ update(data: Partial<T>): Promise<void>;
171
+ delete(): Promise<void>;
172
+ onSnapshot(callback: SubscriptionCallback<T>): () => void;
173
+ }
174
+ /**
175
+ * Collection reference with ORM-style query builder
176
+ *
177
+ * This class is thenable - you can await it directly without .get()
178
+ * @example
179
+ * await collection('users').where({ age: '>= 25' })
180
+ */
181
+ declare class CollectionReference<T = any> implements PromiseLike<T[]> {
182
+ private client;
183
+ readonly collection: string;
184
+ private queries;
185
+ private promise?;
186
+ constructor(client: FlareClient, collection: string);
187
+ /**
188
+ * Get a document reference (legacy API)
189
+ */
190
+ doc(id: string): DocumentReference<T>;
191
+ /**
192
+ * Add a where filter (NEW ORM-style API)
193
+ * @example .where({ age: ">= 25", role: "admin" })
194
+ */
195
+ where(condition: WhereCondition): CollectionReference<T>;
196
+ /**
197
+ * Get all documents once
198
+ * @deprecated Use await directly instead of .get()
199
+ */
200
+ get(): Promise<T[]>;
201
+ /**
202
+ * Internal execute method
203
+ */
204
+ private _execute;
205
+ /**
206
+ * Make this class thenable so it can be awaited directly
207
+ */
208
+ then<TResult1 = T[], TResult2 = never>(onfulfilled?: ((value: T[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): PromiseLike<TResult1 | TResult2>;
209
+ /**
210
+ * Subscribe to real-time updates
211
+ */
212
+ onSnapshot(callback: SubscriptionCallback<T[]>): () => void;
213
+ /**
214
+ * Add a new document with auto-generated ID
215
+ */
216
+ add(data: Partial<T>): Promise<DocumentReference<T>>;
217
+ /**
218
+ * Update documents matching the where condition
219
+ */
220
+ update(data: Partial<T>): DocumentQueryBuilder<T>;
221
+ /**
222
+ * Delete documents matching the where condition
223
+ */
224
+ delete(): DocumentQueryBuilder<T>;
225
+ }
226
+
227
+ /** Client Request */
228
+ declare enum FlareAction {
229
+ SUBSCRIBE = "subscribe",
230
+ UNSUBSCRIBE = "unsubscribe",
231
+ WRITE = "write",
232
+ DELETE = "delete",
233
+ AUTH = "auth",
234
+ PING = "ping",
235
+ OFFLINE_SYNC = "offline_sync",
236
+ /** General-purpose RPC / topic call */
237
+ CALL = "call"
238
+ }
239
+ /** Server Response */
240
+ declare enum FlareEvent {
241
+ SNAPSHOT = "snapshot",
242
+ CHANGE = "change",
243
+ ERROR = "error",
244
+ ACK = "ack",
245
+ PONG = "pong",
246
+ AUTH_OK = "auth_ok",
247
+ OFFLINE_ACK = "offline_ack",
248
+ /** Response to a CALL */
249
+ CALL_RESPONSE = "call_response"
250
+ }
251
+ interface BaseMessage {
252
+ id: string;
253
+ type: FlareAction | FlareEvent;
254
+ ts: number;
255
+ }
256
+
257
+ type ConnectionListener = (state: ConnectionState) => void;
258
+ type ErrorListener = (error: Error) => void;
259
+ declare class FlareClient {
260
+ private transport;
261
+ private readonly config;
262
+ private readonly pendingAcks;
263
+ private readonly subscriptions;
264
+ private readonly offlineQueue;
265
+ private currentState;
266
+ private connectionListeners;
267
+ private errorListeners;
268
+ private authToken?;
269
+ private userId?;
270
+ private isDebug;
271
+ constructor(config: FlareConfig);
272
+ /**
273
+ * Connect to the server
274
+ */
275
+ connect(): void;
276
+ /**
277
+ * Disconnect from the server
278
+ */
279
+ disconnect(): void;
280
+ /**
281
+ * Get a collection reference
282
+ */
283
+ collection<T = any>(name: string): CollectionReference<T>;
284
+ /**
285
+ * Get a document query builder (NEW ORM-style API)
286
+ * @example flare.doc('users').update({...}).where({ id: 'alice' })
287
+ */
288
+ doc<T = any>(collection: string): DocumentQueryBuilder<T>;
289
+ /**
290
+ * Get a document reference (Legacy API - deprecated)
291
+ * @deprecated Use doc(collection).where({ id: '...' }) instead
292
+ */
293
+ doc<T = any>(collection: string, id: string): DocumentReference<T>;
294
+ /**
295
+ * Authenticate with a token
296
+ */
297
+ auth(token: string): Promise<AuthResult>;
298
+ /**
299
+ * Sign out
300
+ */
301
+ signOut(): void;
302
+ /**
303
+ * Get current user ID
304
+ */
305
+ get currentUser(): string | undefined;
306
+ /**
307
+ * Get connection state
308
+ */
309
+ get connectionState(): ConnectionState;
310
+ /**
311
+ * Check if connected
312
+ */
313
+ get isConnected(): boolean;
314
+ /**
315
+ * Listen to connection state changes
316
+ */
317
+ onConnectionStateChange(listener: ConnectionListener): () => void;
318
+ /**
319
+ * Listen to errors
320
+ */
321
+ onError(callback: ErrorListener): () => void;
322
+ /**
323
+ * Ping the server
324
+ */
325
+ ping(): Promise<number>;
326
+ /**
327
+ * Invoke a server-side CALL handler by topic and await its response.
328
+ *
329
+ * @example
330
+ * const result = await flare.call("agent:backup", { name: "db-daily" });
331
+ * console.log(result.jobId);
332
+ *
333
+ * @param topic The topic registered with `flare.registerCallHandler(topic, fn)` on the server.
334
+ * @param payload Arbitrary JSON payload forwarded to the handler.
335
+ * @returns The object returned by the server handler.
336
+ * @throws If the server returns `success: false` or the request times out.
337
+ */
338
+ call<T = Record<string, unknown>>(topic: string, payload?: Record<string, unknown>): Promise<T>;
339
+ /**
340
+ * Sync offline operations
341
+ */
342
+ syncOffline(): Promise<void>;
343
+ private handleTransportError;
344
+ private onConnected;
345
+ private onDisconnected;
346
+ private setState;
347
+ private handleIncoming;
348
+ /**
349
+ * Internal: Send message to server
350
+ */
351
+ send(type: FlareAction, payload: any): Promise<any>;
352
+ /**
353
+ * Internal: Create a subscription
354
+ */
355
+ subscribe(subId: string, collection: string, docId: string | undefined, query: QueryConfig | undefined, callback: SubscriptionCallback): () => void;
356
+ private log;
357
+ }
358
+
359
+ declare class FlareError extends Error {
360
+ readonly code: string;
361
+ readonly cause?: unknown | undefined;
362
+ constructor(message: string, code: string, cause?: unknown | undefined);
363
+ }
364
+
365
+ /**
366
+ * Initialize and connect to FlareServer
367
+ * Returns a singleton instance
368
+ */
369
+ declare const connectApp: (config: FlareConfig) => FlareClient;
370
+ /**
371
+ * Get the current Flare instance
372
+ */
373
+ declare const getFlare: () => FlareClient | null;
374
+ /**
375
+ * Disconnect and reset the instance
376
+ */
377
+ declare const disconnectFlare: () => void;
378
+
379
+ export { type AuthResult, type BaseMessage, CollectionReference, type ConnectionState, DocumentQueryBuilder, DocumentReference, type DocumentSnapshot, FlareAction, type FlareConfig, FlareError, FlareEvent, type OfflineOperation, type QueryConfig, type QueryOperator, type QuerySnapshot, type SubscriptionCallback, type SubscriptionData, type WhereCondition, connectApp, FlareClient as default, disconnectFlare, getFlare };
@@ -0,0 +1,379 @@
1
+ export { AuthGuard, AuthToken, Dropbox, Google, NormalizedProfile, OAuthProvider, ProviderId } from '@zuzjs/auth';
2
+
3
+ /**
4
+ * Client Configuration
5
+ */
6
+ interface FlareConfig {
7
+ /** WebSocket endpoint URL (e.g., 'http://localhost:5050' or 'wss://api.example.com') */
8
+ endpoint: string;
9
+ /** Application/Project ID */
10
+ appId: string;
11
+ /** API Key for authentication (optional) */
12
+ apiKey?: string;
13
+ /**
14
+ * RSA public key (PEM) returned by `flare app create`.
15
+ * When provided, all outgoing messages are wrapped in an RSA-OAEP encrypted
16
+ * envelope: `{ enc: "rsa", data: "<base64>" }` before being sent over the wire.
17
+ */
18
+ publicKey?: string;
19
+ /** Enable automatic reconnection (default: true) */
20
+ autoReconnect?: boolean;
21
+ /** Initial reconnect delay in seconds (default: 2) */
22
+ reconnectDelay?: number;
23
+ /** Maximum reconnect delay in seconds (default: 60) */
24
+ maxReconnectDelay?: number;
25
+ /** Enable debug logging (default: false) */
26
+ debug?: boolean;
27
+ /** Connection timeout in milliseconds (default: 10000) */
28
+ connectionTimeout?: number;
29
+ }
30
+ /**
31
+ * Query configuration (NEW v0.2: Object-based query syntax)
32
+ */
33
+ interface QueryConfig {
34
+ field: string;
35
+ op: QueryOperator;
36
+ value: unknown;
37
+ }
38
+ /**
39
+ * Where condition (NEW v0.2: Object-based syntax)
40
+ * Example: { age: ">= 25", role: "admin" }
41
+ */
42
+ type WhereCondition = Record<string, string | number | boolean | any[]>;
43
+ type QueryOperator = "==" | "!=" | "<" | "<=" | ">" | ">=" | "in" | "not-in" | "array-contains";
44
+ /**
45
+ * Subscription callback
46
+ */
47
+ type SubscriptionCallback<T = any> = (data: SubscriptionData<T>) => void;
48
+ /**
49
+ * Subscription data received from server
50
+ */
51
+ interface SubscriptionData<T = any> {
52
+ subscriptionId: string;
53
+ collection: string;
54
+ docId?: string;
55
+ data: T | T[];
56
+ type: 'snapshot' | 'change';
57
+ operation?: 'insert' | 'update' | 'delete' | 'replace';
58
+ }
59
+ /**
60
+ * Document reference
61
+ */
62
+ interface DocumentSnapshot<T = any> {
63
+ id: string;
64
+ data: T | null;
65
+ exists: boolean;
66
+ }
67
+ /**
68
+ * Collection query result
69
+ */
70
+ interface QuerySnapshot<T = any> {
71
+ docs: DocumentSnapshot<T>[];
72
+ size: number;
73
+ empty: boolean;
74
+ }
75
+ /**
76
+ * Offline operation
77
+ */
78
+ interface OfflineOperation {
79
+ id: string;
80
+ type: 'write' | 'delete';
81
+ collection: string;
82
+ docId: string;
83
+ data?: Record<string, unknown>;
84
+ merge?: boolean;
85
+ clientTs: number;
86
+ }
87
+ /**
88
+ * Authentication result
89
+ */
90
+ interface AuthResult {
91
+ uid: string;
92
+ token?: string;
93
+ }
94
+ /**
95
+ * Connection state
96
+ */
97
+ type ConnectionState = 'connecting' | 'connected' | 'disconnected' | 'reconnecting' | 'error';
98
+
99
+ /**
100
+ * Query builder for document operations (ORM-style)
101
+ * Supports: doc('users').update({...}).where({ id: 'alice' })
102
+ *
103
+ * This class is thenable - you can await it directly without .execute() or .get()
104
+ * @example
105
+ * await doc('users').where({ id: 'alice' })
106
+ * await doc('users').update({ name: 'Alice' }).where({ id: 'alice' })
107
+ */
108
+ declare class DocumentQueryBuilder<T = any> implements PromiseLike<T | null | void> {
109
+ private client;
110
+ private collection;
111
+ private legacyId?;
112
+ private whereCondition?;
113
+ private updateData?;
114
+ private setData?;
115
+ private deleteOp;
116
+ private promise?;
117
+ constructor(client: FlareClient, collection: string, legacyId?: string | undefined);
118
+ /**
119
+ * Set where condition
120
+ */
121
+ where(condition: WhereCondition): this;
122
+ /**
123
+ * Set update data (for update operations)
124
+ */
125
+ update(data: Partial<T>): this;
126
+ /**
127
+ * Set data (for set operations)
128
+ */
129
+ set(data: Partial<T>): this;
130
+ /**
131
+ * Mark for deletion
132
+ */
133
+ delete(): this;
134
+ /**
135
+ * Get the document ID from where condition or legacy id
136
+ */
137
+ private getDocId;
138
+ /**
139
+ * Execute the query
140
+ * @deprecated Use await directly instead of .execute()
141
+ */
142
+ execute(): Promise<T | null | void>;
143
+ /**
144
+ * Internal execute method
145
+ */
146
+ private _execute;
147
+ /**
148
+ * Make this class thenable so it can be awaited directly
149
+ */
150
+ then<TResult1 = T | null | void, TResult2 = never>(onfulfilled?: ((value: T | null | void) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): PromiseLike<TResult1 | TResult2>;
151
+ /**
152
+ * Get the document data once
153
+ */
154
+ get(): Promise<T | null>;
155
+ /**
156
+ * Subscribe to real-time updates
157
+ */
158
+ onSnapshot(callback: SubscriptionCallback<T>): () => void;
159
+ }
160
+ /**
161
+ * Legacy document reference (for backward compatibility)
162
+ */
163
+ declare class DocumentReference<T = any> {
164
+ private client;
165
+ readonly collection: string;
166
+ readonly id: string;
167
+ constructor(client: FlareClient, collection: string, id: string);
168
+ get(): Promise<T | null>;
169
+ set(data: Partial<T>): Promise<void>;
170
+ update(data: Partial<T>): Promise<void>;
171
+ delete(): Promise<void>;
172
+ onSnapshot(callback: SubscriptionCallback<T>): () => void;
173
+ }
174
+ /**
175
+ * Collection reference with ORM-style query builder
176
+ *
177
+ * This class is thenable - you can await it directly without .get()
178
+ * @example
179
+ * await collection('users').where({ age: '>= 25' })
180
+ */
181
+ declare class CollectionReference<T = any> implements PromiseLike<T[]> {
182
+ private client;
183
+ readonly collection: string;
184
+ private queries;
185
+ private promise?;
186
+ constructor(client: FlareClient, collection: string);
187
+ /**
188
+ * Get a document reference (legacy API)
189
+ */
190
+ doc(id: string): DocumentReference<T>;
191
+ /**
192
+ * Add a where filter (NEW ORM-style API)
193
+ * @example .where({ age: ">= 25", role: "admin" })
194
+ */
195
+ where(condition: WhereCondition): CollectionReference<T>;
196
+ /**
197
+ * Get all documents once
198
+ * @deprecated Use await directly instead of .get()
199
+ */
200
+ get(): Promise<T[]>;
201
+ /**
202
+ * Internal execute method
203
+ */
204
+ private _execute;
205
+ /**
206
+ * Make this class thenable so it can be awaited directly
207
+ */
208
+ then<TResult1 = T[], TResult2 = never>(onfulfilled?: ((value: T[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): PromiseLike<TResult1 | TResult2>;
209
+ /**
210
+ * Subscribe to real-time updates
211
+ */
212
+ onSnapshot(callback: SubscriptionCallback<T[]>): () => void;
213
+ /**
214
+ * Add a new document with auto-generated ID
215
+ */
216
+ add(data: Partial<T>): Promise<DocumentReference<T>>;
217
+ /**
218
+ * Update documents matching the where condition
219
+ */
220
+ update(data: Partial<T>): DocumentQueryBuilder<T>;
221
+ /**
222
+ * Delete documents matching the where condition
223
+ */
224
+ delete(): DocumentQueryBuilder<T>;
225
+ }
226
+
227
+ /** Client Request */
228
+ declare enum FlareAction {
229
+ SUBSCRIBE = "subscribe",
230
+ UNSUBSCRIBE = "unsubscribe",
231
+ WRITE = "write",
232
+ DELETE = "delete",
233
+ AUTH = "auth",
234
+ PING = "ping",
235
+ OFFLINE_SYNC = "offline_sync",
236
+ /** General-purpose RPC / topic call */
237
+ CALL = "call"
238
+ }
239
+ /** Server Response */
240
+ declare enum FlareEvent {
241
+ SNAPSHOT = "snapshot",
242
+ CHANGE = "change",
243
+ ERROR = "error",
244
+ ACK = "ack",
245
+ PONG = "pong",
246
+ AUTH_OK = "auth_ok",
247
+ OFFLINE_ACK = "offline_ack",
248
+ /** Response to a CALL */
249
+ CALL_RESPONSE = "call_response"
250
+ }
251
+ interface BaseMessage {
252
+ id: string;
253
+ type: FlareAction | FlareEvent;
254
+ ts: number;
255
+ }
256
+
257
+ type ConnectionListener = (state: ConnectionState) => void;
258
+ type ErrorListener = (error: Error) => void;
259
+ declare class FlareClient {
260
+ private transport;
261
+ private readonly config;
262
+ private readonly pendingAcks;
263
+ private readonly subscriptions;
264
+ private readonly offlineQueue;
265
+ private currentState;
266
+ private connectionListeners;
267
+ private errorListeners;
268
+ private authToken?;
269
+ private userId?;
270
+ private isDebug;
271
+ constructor(config: FlareConfig);
272
+ /**
273
+ * Connect to the server
274
+ */
275
+ connect(): void;
276
+ /**
277
+ * Disconnect from the server
278
+ */
279
+ disconnect(): void;
280
+ /**
281
+ * Get a collection reference
282
+ */
283
+ collection<T = any>(name: string): CollectionReference<T>;
284
+ /**
285
+ * Get a document query builder (NEW ORM-style API)
286
+ * @example flare.doc('users').update({...}).where({ id: 'alice' })
287
+ */
288
+ doc<T = any>(collection: string): DocumentQueryBuilder<T>;
289
+ /**
290
+ * Get a document reference (Legacy API - deprecated)
291
+ * @deprecated Use doc(collection).where({ id: '...' }) instead
292
+ */
293
+ doc<T = any>(collection: string, id: string): DocumentReference<T>;
294
+ /**
295
+ * Authenticate with a token
296
+ */
297
+ auth(token: string): Promise<AuthResult>;
298
+ /**
299
+ * Sign out
300
+ */
301
+ signOut(): void;
302
+ /**
303
+ * Get current user ID
304
+ */
305
+ get currentUser(): string | undefined;
306
+ /**
307
+ * Get connection state
308
+ */
309
+ get connectionState(): ConnectionState;
310
+ /**
311
+ * Check if connected
312
+ */
313
+ get isConnected(): boolean;
314
+ /**
315
+ * Listen to connection state changes
316
+ */
317
+ onConnectionStateChange(listener: ConnectionListener): () => void;
318
+ /**
319
+ * Listen to errors
320
+ */
321
+ onError(callback: ErrorListener): () => void;
322
+ /**
323
+ * Ping the server
324
+ */
325
+ ping(): Promise<number>;
326
+ /**
327
+ * Invoke a server-side CALL handler by topic and await its response.
328
+ *
329
+ * @example
330
+ * const result = await flare.call("agent:backup", { name: "db-daily" });
331
+ * console.log(result.jobId);
332
+ *
333
+ * @param topic The topic registered with `flare.registerCallHandler(topic, fn)` on the server.
334
+ * @param payload Arbitrary JSON payload forwarded to the handler.
335
+ * @returns The object returned by the server handler.
336
+ * @throws If the server returns `success: false` or the request times out.
337
+ */
338
+ call<T = Record<string, unknown>>(topic: string, payload?: Record<string, unknown>): Promise<T>;
339
+ /**
340
+ * Sync offline operations
341
+ */
342
+ syncOffline(): Promise<void>;
343
+ private handleTransportError;
344
+ private onConnected;
345
+ private onDisconnected;
346
+ private setState;
347
+ private handleIncoming;
348
+ /**
349
+ * Internal: Send message to server
350
+ */
351
+ send(type: FlareAction, payload: any): Promise<any>;
352
+ /**
353
+ * Internal: Create a subscription
354
+ */
355
+ subscribe(subId: string, collection: string, docId: string | undefined, query: QueryConfig | undefined, callback: SubscriptionCallback): () => void;
356
+ private log;
357
+ }
358
+
359
+ declare class FlareError extends Error {
360
+ readonly code: string;
361
+ readonly cause?: unknown | undefined;
362
+ constructor(message: string, code: string, cause?: unknown | undefined);
363
+ }
364
+
365
+ /**
366
+ * Initialize and connect to FlareServer
367
+ * Returns a singleton instance
368
+ */
369
+ declare const connectApp: (config: FlareConfig) => FlareClient;
370
+ /**
371
+ * Get the current Flare instance
372
+ */
373
+ declare const getFlare: () => FlareClient | null;
374
+ /**
375
+ * Disconnect and reset the instance
376
+ */
377
+ declare const disconnectFlare: () => void;
378
+
379
+ export { type AuthResult, type BaseMessage, CollectionReference, type ConnectionState, DocumentQueryBuilder, DocumentReference, type DocumentSnapshot, FlareAction, type FlareConfig, FlareError, FlareEvent, type OfflineOperation, type QueryConfig, type QueryOperator, type QuerySnapshot, type SubscriptionCallback, type SubscriptionData, type WhereCondition, connectApp, FlareClient as default, disconnectFlare, getFlare };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import {uuid2}from'@zuzjs/core';export{AuthGuard,Dropbox,Google}from'@zuzjs/auth';var v=(c=>(c.SUBSCRIBE="subscribe",c.UNSUBSCRIBE="unsubscribe",c.WRITE="write",c.DELETE="delete",c.AUTH="auth",c.PING="ping",c.OFFLINE_SYNC="offline_sync",c.CALL="call",c))(v||{}),S=(c=>(c.SNAPSHOT="snapshot",c.CHANGE="change",c.ERROR="error",c.ACK="ack",c.PONG="pong",c.AUTH_OK="auth_ok",c.OFFLINE_ACK="offline_ack",c.CALL_RESPONSE="call_response",c))(S||{});function w(r){let e=[];for(let[t,i]of Object.entries(r))if(typeof i=="string"){let o=i.match(/^(>=|<=|!=|>|<|==)\s*(.+)$/);if(o){let[,n,s]=o;e.push({field:t,op:n,value:R(s.trim())});}else e.push({field:t,op:"==",value:i});}else Array.isArray(i)?e.push({field:t,op:"in",value:i}):e.push({field:t,op:"==",value:i});return e}function R(r){if(!isNaN(Number(r)))return Number(r);if(r==="true")return true;if(r==="false")return false;if(r==="null")return null;if(r!=="undefined")return r}var a=class{constructor(e,t,i){this.client=e;this.collection=t;this.legacyId=i;}whereCondition;updateData;setData;deleteOp=false;promise;where(e){return this.whereCondition=e,this}update(e){return this.updateData=e,this}set(e){return this.setData=e,this}delete(){return this.deleteOp=true,this}getDocId(){if(this.legacyId)return this.legacyId;if(this.whereCondition&&this.whereCondition.id){let e=this.whereCondition.id;if(typeof e=="string")return e}throw new Error('Document ID not specified. Use .where({ id: "..." }) or doc(collection, id)')}async execute(){return this._execute()}async _execute(){let e=this.getDocId();if(this.deleteOp){await this.client.send("delete",{collection:this.collection,docId:e});return}if(this.updateData){await this.client.send("write",{collection:this.collection,docId:e,data:this.updateData,merge:true});return}if(this.setData){await this.client.send("write",{collection:this.collection,docId:e,data:this.setData,merge:false});return}return this.get()}then(e,t){return this.promise||(this.promise=this._execute()),this.promise.then(e,t)}async get(){let e=this.getDocId(),t=uuid2(18);return new Promise((i,o)=>{let n=this.client.subscribe(t,this.collection,e,void 0,s=>{s.type==="snapshot"&&(n(),i(s.data));});setTimeout(()=>{n(),o(new Error("Document fetch timeout"));},1e4);})}onSnapshot(e){let t=this.getDocId(),i=uuid2(18);return this.client.subscribe(i,this.collection,t,void 0,e)}},h=class{constructor(e,t,i){this.client=e;this.collection=t;this.id=i;}async get(){return new a(this.client,this.collection,this.id).get()}async set(e){await this.client.send("write",{collection:this.collection,docId:this.id,data:e,merge:false});}async update(e){await this.client.send("write",{collection:this.collection,docId:this.id,data:e,merge:true});}async delete(){await this.client.send("delete",{collection:this.collection,docId:this.id});}onSnapshot(e){let t=uuid2(18);return this.client.subscribe(t,this.collection,this.id,void 0,e)}},g=class r{constructor(e,t){this.client=e;this.collection=t;}queries=[];promise;doc(e){return new h(this.client,this.collection,e)}where(e){let t=new r(this.client,this.collection),i=w(e);return t.queries=[...this.queries,...i],t}async get(){return this._execute()}async _execute(){let e=uuid2(18);return new Promise((t,i)=>{let o=this.queries.length>0?this.queries:void 0,n=this.client.subscribe(e,this.collection,void 0,o,s=>{s.type==="snapshot"&&(n(),t(Array.isArray(s.data)?s.data:[s.data]));});setTimeout(()=>{n(),i(new Error("Collection fetch timeout"));},1e4);})}then(e,t){return this.promise||(this.promise=this._execute()),this.promise.then(e,t)}onSnapshot(e){let t=uuid2(18),i=this.queries.length>0?this.queries:void 0;return this.client.subscribe(t,this.collection,void 0,i,e)}async add(e){let t=uuid2(18),i=this.doc(t);return await i.set(e),i}update(e){return new a(this.client,this.collection).update(e)}delete(){return new a(this.client,this.collection).delete()}};async function I(r){let e=r.replace(/-----BEGIN PUBLIC KEY-----/,"").replace(/-----END PUBLIC KEY-----/,"").replace(/\s+/g,""),t=typeof atob<"u"?atob(e):Buffer.from(e,"base64").toString("binary"),i=new Uint8Array(t.length);for(let n=0;n<t.length;n++)i[n]=t.charCodeAt(n);return (globalThis.crypto??(await import('crypto')).webcrypto).subtle.importKey("spki",i.buffer,{name:"RSA-OAEP",hash:"SHA-256"},false,["encrypt"])}async function E(r,e){let t=await I(e),i=new TextEncoder().encode(JSON.stringify(r)),n=await(globalThis.crypto??(await import('crypto')).webcrypto).subtle.encrypt({name:"RSA-OAEP"},t,i),s=typeof btoa<"u"?btoa(String.fromCharCode(...new Uint8Array(n))):Buffer.from(n).toString("base64");return JSON.stringify({enc:"rsa",data:s})}var y=class{socket=null;reconnectInterval;maxReconnectDelay;isConnected=false;shouldReconnect=true;options;messageQueue=[];heartbeatInterval=null;connectionTimeout=null;constructor(e){this.options=e,this.reconnectInterval=e.reconnectDelay||2,this.maxReconnectDelay=e.maxReconnectDelay||60,this.log("Transport initialized",e.url);}connect(){if(this.socket){this.log("Socket already exists, skipping connection");return}this.log("Connecting to",this.options.url),this.socket=new WebSocket(this.options.url),this.connectionTimeout=setTimeout(()=>{this.isConnected||(this.log("Connection timeout"),this.socket?.close(),this.handleReconnect());},1e4),this.socket.onopen=()=>{this.connectionTimeout&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=null),this.isConnected=true,this.reconnectInterval=this.options.reconnectDelay||2,this.log("Connected to server"),this.options.onOpen?.(),this.startHeartbeat(),this.flushQueue();},this.socket.onmessage=e=>{try{let t=JSON.parse(e.data);this.options.onMessage(t);}catch(t){this.log("Parse error",t),this.options.onError?.(t);}},this.socket.onerror=e=>{this.log("WebSocket error",e),this.options.onError?.(new Error("WebSocket error"));},this.socket.onclose=e=>{this.connectionTimeout&&(clearTimeout(this.connectionTimeout),this.connectionTimeout=null),this.isConnected=false,this.socket=null,this.stopHeartbeat(),this.log("Connection closed",e.code,e.reason),this.options.onClose?.(),e.code!==1e3&&this.shouldReconnect&&this.options.autoReconnect&&this.handleReconnect();};}handleReconnect(){let e=this.reconnectInterval*1e3;this.log(`Reconnecting in ${this.reconnectInterval}s...`),setTimeout(()=>{this.reconnectInterval=Math.min(this.reconnectInterval*2,this.maxReconnectDelay),this.connect();},e);}startHeartbeat(){this.heartbeatInterval=setInterval(()=>{this.isConnected&&this.send({type:"ping",id:Date.now().toString(),ts:Date.now()});},3e4);}stopHeartbeat(){this.heartbeatInterval&&(clearInterval(this.heartbeatInterval),this.heartbeatInterval=null);}flushQueue(){for(this.log("Flushing message queue",this.messageQueue.length);this.messageQueue.length>0;){let e=this.messageQueue.shift();e&&this.send(e);}}send(e){if(this.socket&&this.socket.readyState===WebSocket.OPEN){let t=i=>{try{this.socket.send(i),this.log("Sent message",e);}catch(o){this.log("Send error",o),this.messageQueue.push(e);}};this.options.publicKey?E(e,this.options.publicKey).then(t).catch(i=>{this.log("RSA encrypt error \u2014 sending plaintext",i),t(JSON.stringify(e));}):t(JSON.stringify(e));}else this.log("Socket not ready, queueing message"),this.messageQueue.push(e);}disconnect(){this.shouldReconnect=false,this.stopHeartbeat(),this.socket&&(this.socket.close(1e3,"Client disconnect"),this.socket=null),this.isConnected=false,this.log("Disconnected");}get connected(){return this.isConnected}log(...e){this.options.debug&&console.log("[FlareTransport]",...e);}};var b=class{transport;config;pendingAcks=new Map;subscriptions=new Map;offlineQueue=[];currentState="disconnected";connectionListeners=[];errorListeners=[];authToken;userId;isDebug=false;constructor(e){this.config={autoReconnect:true,reconnectDelay:2,maxReconnectDelay:60,debug:false,connectionTimeout:1e4,...e},this.isDebug=this.config.debug||false;let{hostname:t,port:i,protocol:o}=new URL(this.config.endpoint),n=o==="https:",c=`${n?"wss":"ws"}://${t}:${i||(n?"443":"80")}/?appId=${this.config.appId}${this.config.apiKey?`&apiKey=${this.config.apiKey}`:""}`;this.transport=new y({url:c,publicKey:this.config.publicKey,autoReconnect:this.config.autoReconnect,reconnectDelay:this.config.reconnectDelay,maxReconnectDelay:this.config.maxReconnectDelay,onMessage:m=>this.handleIncoming(m),onOpen:()=>this.onConnected(),onClose:()=>this.onDisconnected(),onError:m=>this.handleTransportError(m),debug:this.isDebug}),this.log("FlareClient initialized",e);}connect(){this.setState("connecting"),this.transport.connect();}disconnect(){this.transport.disconnect(),this.setState("disconnected");}collection(e){return new g(this,e)}doc(e,t){return t!==void 0?new h(this,e,t):new a(this,e)}async auth(e){let t=await this.send("auth",{token:e});if(t.type==="auth_ok")return this.authToken=e,this.userId=t.uid,this.log("Authentication successful",t.uid),{uid:t.uid,token:e};throw new Error("Authentication failed")}signOut(){this.authToken=void 0,this.userId=void 0,this.log("Signed out");}get currentUser(){return this.userId}get connectionState(){return this.currentState}get isConnected(){return this.currentState==="connected"}onConnectionStateChange(e){return this.connectionListeners.push(e),()=>{this.connectionListeners=this.connectionListeners.filter(t=>t!==e);}}onError(e){return this.errorListeners.push(e),()=>{this.errorListeners=this.errorListeners.filter(t=>t!==e);}}async ping(){let e=Date.now();return await this.send("ping",{}),Date.now()-e}async call(e,t={}){let i=await this.send("call",{topic:e,payload:t});if(!i.success)throw new Error(i.error??`CALL "${e}" failed`);return i.result}async syncOffline(){if(this.offlineQueue.length===0)return;this.log("Syncing offline operations",this.offlineQueue.length);let e=[...this.offlineQueue];this.offlineQueue.length=0;let t=await this.send("offline_sync",{operations:e});t.conflicts&&t.conflicts.length>0&&(this.log("Offline sync conflicts",t.conflicts),t.conflicts.forEach(i=>{let o=e.find(n=>n.id===i.operationId);o&&this.offlineQueue.push(o);}));}handleTransportError(e){this.log("Transport error",e),this.errorListeners.forEach(t=>{try{t(e);}catch(i){this.log("Error listener error",i);}});}onConnected(){this.setState("connected"),this.log("Connected to FlareServer"),this.offlineQueue.length>0&&this.syncOffline().catch(e=>{this.log("Offline sync failed",e);});}onDisconnected(){this.currentState!=="disconnected"&&this.setState("reconnecting"),this.log("Disconnected from FlareServer");}setState(e){this.currentState!==e&&(this.currentState=e,this.log("Connection state changed",e),this.connectionListeners.forEach(t=>{try{t(e);}catch(i){this.log("Connection listener error",i);}}));}handleIncoming(e){if(this.log("Received message",e.type,e),e.type==="ack"||e.type==="pong"||e.type==="auth_ok"||e.type==="call_response"){let t=this.pendingAcks.get(e.correlationId||e.id);t&&(t(e),this.pendingAcks.delete(e.correlationId||e.id));return}if(e.type==="error"){this.log("Server error",e.code,e.message);let t=new Error(`[${e.code}] ${e.message}`);if(this.errorListeners.forEach(i=>{try{i(t);}catch(o){this.log("Error listener error",o);}}),e.correlationId){let i=this.pendingAcks.get(e.correlationId);i&&(i(e),this.pendingAcks.delete(e.correlationId));}return}if(e.type==="snapshot"||e.type==="change"){let t=this.subscriptions.get(e.subscriptionId);if(t){let i={subscriptionId:e.subscriptionId,collection:e.collection,docId:e.docId,data:e.data,type:e.type==="snapshot"?"snapshot":"change",operation:e.operation};try{t(i);}catch(o){this.log("Subscription callback error",o);}}}}async send(e,t){return new Promise((i,o)=>{let n=uuid2(18),s={id:n,type:e,ts:Date.now(),...t};this.pendingAcks.set(n,d=>{d.type==="error"?o(new Error(`[${d.code}] ${d.message}`)):i(d);}),this.isConnected?this.transport.send(s):(this.log("Queueing message for offline",s),this.offlineQueue.push(s),o(new Error("Not connected - message queued"))),setTimeout(()=>{this.pendingAcks.has(n)&&(this.pendingAcks.delete(n),o(new Error("Request timeout")));},this.config.connectionTimeout);})}subscribe(e,t,i,o,n){return this.log("Creating subscription",e,t,i),this.subscriptions.set(e,n),this.send("subscribe",{collection:t,docId:i,query:o}).then(s=>{s.subscriptionId&&(this.subscriptions.delete(e),this.subscriptions.set(s.subscriptionId,n));}).catch(s=>{this.log("Subscription failed",s),this.subscriptions.delete(e);}),()=>{this.log("Unsubscribing",e),this.subscriptions.delete(e),this.isConnected&&this.send("unsubscribe",{subscriptionId:e}).catch(s=>this.log("Unsubscribe failed",s));}}log(...e){this.isDebug&&console.log("[FlareClient]",...e);}},T=b;var C=class extends Error{constructor(t,i,o){super(t);this.code=i;this.cause=o;this.name="ZuzFlareError";}};var l=null,$=r=>(l||(l=new T(r),l.connect()),l),G=()=>l,J=()=>{l&&(l.disconnect(),l=null);};var M=T;
2
+ export{g as CollectionReference,a as DocumentQueryBuilder,h as DocumentReference,v as FlareAction,C as FlareError,S as FlareEvent,$ as connectApp,M as default,J as disconnectFlare,G as getFlare};
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@zuzjs/flare",
3
+ "version": "0.2.1",
4
+ "keywords": [
5
+ "core",
6
+ "zuz",
7
+ "zuz.js",
8
+ "zuz flare",
9
+ "zuz flare client",
10
+ "zuzjs"
11
+ ],
12
+ "description": "Official JavaScript/TypeScript client for ZuzFlare Server - Self-hosted real-time database",
13
+ "author": "Zuz.js Team <support@zuz.com.pk>",
14
+ "license": "MIT",
15
+ "type": "module",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/zuzjs/flare-client.git"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/zuzjs/flare-client/issues"
22
+ },
23
+ "homepage": "https://flare.zedgon.io",
24
+ "main": "./dist/index.cjs",
25
+ "module": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js",
31
+ "require": "./dist/index.cjs"
32
+ }
33
+ },
34
+ "files": [
35
+ "dist"
36
+ ],
37
+ "engines": {
38
+ "node": ">=18.17.0"
39
+ },
40
+ "sideEffects": [
41
+ "reflect-metadata"
42
+ ],
43
+ "dependencies": {
44
+ "@zuzjs/auth": "^0.1.7",
45
+ "@zuzjs/core": "^0.3.8"
46
+ }
47
+ }