@zuzjs/flare-admin 0.1.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.
@@ -0,0 +1,175 @@
1
+ /**
2
+ * @zuzjs/flare-admin
3
+ *
4
+ * Server-side admin SDK for FlareServer.
5
+ * Runs only on your backend, never in a browser.
6
+ *
7
+ * ─── Quick start ──────────────────────────────────────────────────────────────
8
+ *
9
+ * import { connectApp } from "@zuzjs/flare-admin";
10
+ *
11
+ * const admin = connectApp({
12
+ * serverUrl: process.env.FLARE_URL!,
13
+ * appId: process.env.FLARE_APP_ID!,
14
+ * adminKey: process.env.FLARE_ADMIN_KEY!,
15
+ * });
16
+ *
17
+ * // Mint a custom auth token (for use by the browser client)
18
+ * const token = await admin.auth().createCustomToken(String(user.id), {
19
+ * role: user.isAdmin ? "admin" : "user",
20
+ * claims: { email: user.email, plan: user.plan },
21
+ * });
22
+ *
23
+ * // One-shot DB queries (bypasses security rules)
24
+ * const users = await admin.db().collection("users").get();
25
+ * await admin.db().collection("users").doc("alice").set({ name: "Alice" });
26
+ *
27
+ * // Rich queries
28
+ * const seniors = await admin.db()
29
+ * .collection("users")
30
+ * .where({ age: ">= 60" })
31
+ * .orderBy("name")
32
+ * .limit(10)
33
+ * .get();
34
+ *
35
+ * // Real-time subscriptions over WebSocket
36
+ * const unsub = admin.connection()
37
+ * .collection("orders")
38
+ * .where({ status: "pending" })
39
+ * .orderBy("createdAt", "desc")
40
+ * .onSnapshot((snap) => console.log(snap));
41
+ */
42
+ export type { AdminDocAddedCallback, AdminDocChangedCallback, AdminDocDeletedCallback, AdminDocUpdatedCallback, AdminPushSendInput, AdminPushSendResult, AdminPushToken, AdminSnapshotCallback, AdminSnapshotData, AdminSubscriptionError, AdminSubscriptionErrorCallback, AdminSubscriptionHandle, AggregateFunction, AggregateSpec, AnyFilter, CreateCustomTokenOptions, CursorValue, FlareAdminAuth, FlareAdminConfig, FlareAdminDb, FlareAdminNotifications, GroupByClause, HavingClause, JoinClause, OrderByClause, OrFilter, QueryOperator, StructuredQuery, VectorSearchClause, WhereCondition, WhereFilter } from "./types";
43
+ export { AdminCollectionReference } from "./db/Collection";
44
+ export { AdminDocumentReference } from "./db/Document";
45
+ export { FlareAdminDbService } from "./db/index";
46
+ export { FlareAdminAuthService } from "./lib/auth";
47
+ export { FlareAdminNotificationsService } from "./lib/notifications";
48
+ export { FlareAdminConnection } from "./realtime/Connection";
49
+ export { AdminLiveCollectionReference } from "./realtime/LiveCollection";
50
+ export { AdminLiveDocumentReference } from "./realtime/LiveDocument";
51
+ export { FlareAdminWsConnection } from "./realtime/WsConnection";
52
+ import { FlareAdminConnection } from "./realtime/Connection";
53
+ import { FlareAdminAuth, FlareAdminConfig, FlareAdminDb, FlareAdminNotifications } from "./types";
54
+ /**
55
+ * A FlareAdmin application instance.
56
+ * Create one with `connectApp()` and keep it as a module-level singleton.
57
+ */
58
+ export declare class FlareAdminApp {
59
+ private readonly cfg;
60
+ private _auth?;
61
+ private _db?;
62
+ private _conn?;
63
+ private _notifications?;
64
+ /**
65
+ * Access the auth service.
66
+ *
67
+ * @example
68
+ * const token = await admin.auth().createCustomToken(uid, { role: "user" });
69
+ */
70
+ auth(): FlareAdminAuth;
71
+ /**
72
+ * Access the database service.
73
+ * All operations bypass security rules — admin has full read/write access.
74
+ *
75
+ * Supports the full StructuredQuery builder API:
76
+ * `where`, `and`, `or`, `orderBy`, `limit`, `offset`,
77
+ * `startAt/After`, `endAt/Before`, `count/sum/avg/min/max/distinct`,
78
+ * `groupBy`, `having`, `Join`, `select`, `distinctField`, `vectorSearch`.
79
+ *
80
+ * @example
81
+ * const users = await admin.db().collection("users").get();
82
+ * const admins = await admin.db().collection("users")
83
+ * .where({ role: "admin" }).orderBy("name").get();
84
+ * const [{ count }] = await admin.db().collection("users").count().get();
85
+ * await admin.db().collection("users").doc("alice").update({ plan: "pro" });
86
+ */
87
+ db(): FlareAdminDb;
88
+ /**
89
+ * Open (or reuse) a persistent admin WebSocket connection.
90
+ * One socket is shared per app instance — call `.disconnect()` when done.
91
+ *
92
+ * Supports the full query-builder API on live subscriptions:
93
+ * `where`, `and`, `or`, `orderBy`, `limit`, `offset`,
94
+ * `startAt/After`, `endAt/Before`, `count/sum/avg/min/max/distinct`,
95
+ * `groupBy`, `having`, `Join`, `select`, `distinctField`, `vectorSearch`.
96
+ *
97
+ * @example
98
+ * const unsub = admin.connection()
99
+ * .collection("orders")
100
+ * .where({ status: "pending" })
101
+ * .orderBy("createdAt", "desc")
102
+ * .limit(50)
103
+ * .onSnapshot((snap) => {
104
+ * if (snap.type === "snapshot") console.log("initial:", snap.data);
105
+ * else console.log(snap.operation, snap.data);
106
+ * });
107
+ *
108
+ * const unsub2 = admin.connection()
109
+ * .collection("users").doc("alice")
110
+ * .onSnapshot((snap) => console.log(snap.data));
111
+ *
112
+ * unsub();
113
+ * unsub2();
114
+ * admin.connection().disconnect();
115
+ */
116
+ connection(): FlareAdminConnection;
117
+ /**
118
+ * Access push notification management APIs.
119
+ */
120
+ notifications(): FlareAdminNotifications;
121
+ }
122
+ /**
123
+ * Initialize a FlareAdmin app instance.
124
+ * Call once at server boot. Calling again with the same name is idempotent.
125
+ *
126
+ * @param config Server coordinates + admin key.
127
+ * @param name App name for multi-tenant setups (default: `"[DEFAULT]"`).
128
+ *
129
+ * @example
130
+ * const admin = connectApp({
131
+ * serverUrl: process.env.FLARE_URL!,
132
+ * appId: process.env.FLARE_APP_ID!,
133
+ * adminKey: process.env.FLARE_ADMIN_KEY!,
134
+ * });
135
+ */
136
+ export declare function connectApp(config: FlareAdminConfig, name?: string): FlareAdminApp;
137
+ /**
138
+ * Retrieve an already-initialized app by name.
139
+ * @throws If the app has not been initialized yet.
140
+ */
141
+ export declare function getApp(name?: string): FlareAdminApp;
142
+ /**
143
+ * Get the auth service from the default app.
144
+ * Equivalent to `getApp().auth()`.
145
+ *
146
+ * @example
147
+ * import { auth } from "@zuzjs/flare-admin";
148
+ * const token = await auth().createCustomToken(uid);
149
+ */
150
+ export declare function auth(name?: string): FlareAdminAuth;
151
+ /**
152
+ * Get the db service from the default app.
153
+ * Equivalent to `getApp().db()`.
154
+ *
155
+ * @example
156
+ * import { db } from "@zuzjs/flare-admin";
157
+ * const users = await db().collection("users").get();
158
+ */
159
+ export declare function db(name?: string): FlareAdminDb;
160
+ /**
161
+ * Get the real-time WebSocket connection from the default app.
162
+ * Equivalent to `getApp().connection()`.
163
+ *
164
+ * @example
165
+ * import { connection } from "@zuzjs/flare-admin";
166
+ * const unsub = connection().collection("users")
167
+ * .where({ role: "admin" })
168
+ * .onSnapshot((snap) => console.log(snap));
169
+ */
170
+ export declare function connection(name?: string): FlareAdminConnection;
171
+ /**
172
+ * Get the notifications service from the default app.
173
+ * Equivalent to `getApp().notifications()`.
174
+ */
175
+ export declare function notifications(name?: string): FlareAdminNotifications;
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import q from'ws';function S(o){let e=[];for(let[n,r]of Object.entries(o))if(typeof r=="string"){let t=r.match(/^(>=|<=|!=|>|<|==)\s*(.+)$/);if(t){let[,i,s]=t;e.push({field:n,op:i,value:E(s.trim())});}else e.push({field:n,op:"==",value:r});}else Array.isArray(r)?e.push({field:n,op:"in",value:r}):e.push({field:n,op:"==",value:r});return e}function E(o){return isNaN(Number(o))?o==="true"?true:o==="false"?false:o==="null"?null:o:Number(o)}function T(){return Math.random().toString(36).slice(2,12)+Date.now().toString(36)}var f=class{constructor(e,n,r){this.cfg=e;this.collection=n;this.id=r;}get baseUrl(){return `${this.cfg.serverUrl}/admin/db/${this.cfg.appId}/${this.collection}/${this.id}`}get headers(){return {"Content-Type":"application/json",Authorization:`Bearer ${this.cfg.adminKey}`}}async request(e,n){let r;try{r=await fetch(this.baseUrl,{method:e,headers:this.headers,...n!==void 0?{body:JSON.stringify(n)}:{}});}catch(i){let s=i instanceof Error?i.message:String(i);throw new Error(`[flare-admin] Network error (${e} ${this.baseUrl}): ${s}`)}let t=await r.json().catch(()=>({}));if(!r.ok)throw new Error(`[flare-admin] ${e} ${this.baseUrl} failed (HTTP ${r.status}): `+(t.error??"Unknown error"));return t}async get(){let e;try{e=await fetch(this.baseUrl,{headers:this.headers});}catch(r){let t=r instanceof Error?r.message:String(r);throw new Error(`[flare-admin] Network error (GET ${this.baseUrl}): ${t}`)}if(e.status===404)return null;let n=await e.json().catch(()=>({}));if(!e.ok)throw new Error(`[flare-admin] GET ${this.baseUrl} failed (HTTP ${e.status}): `+(n.error??"Unknown error"));return n.data??null}async set(e){await this.request("PUT",e);}async update(e){await this.request("PATCH",e);}async delete(){await this.request("DELETE");}parent(){return new m(this.cfg,this.collection)}};var m=class o{constructor(e,n){this.cfg=e;this.name=n;this.sq={};this.opts={allowSensitiveAuthUserFields:true};}get baseUrl(){return `${this.cfg.serverUrl}/admin/db/${this.cfg.appId}/${this.name}`}get headers(){return {"Content-Type":"application/json",Authorization:`Bearer ${this.cfg.adminKey}`}}clone(e){let n=new o(this.cfg,this.name);return n.sq={...this.sq,...e},n.opts={...this.opts},n}allowSensitiveAuthUserFields(e=true){let n=this.clone({});return n.opts.allowSensitiveAuthUserFields=!!e,n}normalizeFilterValue(e,n){return e==="in"||e==="not-in"||e==="array-contains-any"?Array.isArray(n)?n:[n]:n}toQueryFilters(e){return S(e).map(r=>typeof r=="object"&&r!=null&&"field"in r&&"op"in r?{...r,value:this.normalizeFilterValue(r.op,r.value)}:r)}appendAndFilters(e){return this.clone({where:[...this.sq.where??[],...e]})}appendOrFilters(e){let n=[...this.sq.where??[]];if(n.length===0)return this.clone({where:[{or:e}]});let r=n[0];if(n.length===1&&typeof r=="object"&&r!=null&&"or"in r)return this.clone({where:[{or:[...r.or,...e]}]});let i=n.length===1?n[0]:{and:n};return this.clone({where:[{or:[i,...e]}]})}appendFilters(e,n){return n==="or"?this.appendOrFilters(e):this.appendAndFilters(e)}appendOperatorFilter(e,n,r,t){return this.appendFilters([{field:e,op:n,value:this.normalizeFilterValue(n,r)}],t)}where(e){return this.appendFilters(this.toQueryFilters(e),"and")}and(e){return this.appendFilters(this.toQueryFilters(e),"and")}or(e){return this.appendFilters(this.toQueryFilters(e),"or")}in(e,n){return this.appendOperatorFilter(e,"in",n,"and")}andIn(e,n){return this.appendOperatorFilter(e,"in",n,"and")}orIn(e,n){return this.appendOperatorFilter(e,"in",n,"or")}notIn(e,n){return this.appendOperatorFilter(e,"not-in",n,"and")}andNotIn(e,n){return this.appendOperatorFilter(e,"not-in",n,"and")}orNotIn(e,n){return this.appendOperatorFilter(e,"not-in",n,"or")}arrayContains(e,n){return this.appendOperatorFilter(e,"array-contains",n,"and")}andArrayContains(e,n){return this.appendOperatorFilter(e,"array-contains",n,"and")}orArrayContains(e,n){return this.appendOperatorFilter(e,"array-contains",n,"or")}arrayContainsAny(e,n){return this.appendOperatorFilter(e,"array-contains-any",n,"and")}andArrayContainsAny(e,n){return this.appendOperatorFilter(e,"array-contains-any",n,"and")}orArrayContainsAny(e,n){return this.appendOperatorFilter(e,"array-contains-any",n,"or")}some(e,n){return this.appendOperatorFilter(e,"elem-match",n,"and")}andSome(e,n){return this.appendOperatorFilter(e,"elem-match",n,"and")}orSome(e,n){return this.appendOperatorFilter(e,"elem-match",n,"or")}like(e,n){return this.appendOperatorFilter(e,"like",n,"and")}andLike(e,n){return this.appendOperatorFilter(e,"like",n,"and")}orLike(e,n){return this.appendOperatorFilter(e,"like",n,"or")}notLike(e,n){return this.appendOperatorFilter(e,"not-like",n,"and")}andNotLike(e,n){return this.appendOperatorFilter(e,"not-like",n,"and")}orNotLike(e,n){return this.appendOperatorFilter(e,"not-like",n,"or")}exists(e){return this.appendOperatorFilter(e,"exists",true,"and")}andExists(e){return this.appendOperatorFilter(e,"exists",true,"and")}orExists(e){return this.appendOperatorFilter(e,"exists",true,"or")}notExists(e){return this.appendOperatorFilter(e,"not-exists",true,"and")}andNotExists(e){return this.appendOperatorFilter(e,"not-exists",true,"and")}orNotExists(e){return this.appendOperatorFilter(e,"not-exists",true,"or")}latest(){return this.clone({orderBy:[...this.sq.orderBy??[],{field:"_seq",dir:"desc"}]})}newest(){return this.latest()}oldest(){return this.clone({orderBy:[...this.sq.orderBy??[],{field:"_seq",dir:"asc"}]})}orderBy(e,n="asc"){return this.clone({orderBy:[...this.sq.orderBy??[],{field:e,dir:n}]})}limit(e){return this.clone({limit:e})}offset(e){return this.clone({offset:e})}startAt(...e){return this.clone({startAt:{values:e}})}startAfter(...e){return this.clone({startAfter:{values:e}})}endAt(...e){return this.clone({endAt:{values:e}})}endBefore(...e){return this.clone({endBefore:{values:e}})}aggregate(...e){return this.clone({aggregate:[...this.sq.aggregate??[],...e]})}count(e="count"){return this.aggregate({fn:"count",alias:e})}sum(e,n){return this.aggregate({fn:"sum",field:e,alias:n??`sum_${e}`})}avg(e,n){return this.aggregate({fn:"avg",field:e,alias:n??`avg_${e}`})}min(e,n){return this.aggregate({fn:"min",field:e,alias:n??`min_${e}`})}max(e,n){return this.aggregate({fn:"max",field:e,alias:n??`max_${e}`})}distinct(e,n){return this.aggregate({fn:"distinct",field:e,alias:n??`distinct_${e}`})}groupBy(...e){return this.clone({groupBy:{fields:e}})}having(e,n,r){return this.clone({having:[...this.sq.having??[],{field:e,op:n,value:r}]})}buildStructuredJoin(e,n){let r={from:String(e??""),localField:String(n?.source??""),foreignField:String(n?.target??""),as:String(n?.as??""),single:n?.single};return Array.isArray(n?.where)&&(r.where=n.where),Array.isArray(n?.orderBy)&&(r.orderBy=n.orderBy),typeof n?.limit=="number"&&(r.limit=n.limit),typeof n?.offset=="number"&&(r.offset=n.offset),Array.isArray(n?.select)&&(r.select=n.select),Array.isArray(n?.joins)&&(r.joins=n.joins.map(t=>this.buildStructuredJoin(String(t?.collection??""),t))),r}cloneStructuredJoin(e){let n={...e};return Array.isArray(e.where)&&(n.where=e.where.map(r=>({...r}))),Array.isArray(e.orderBy)&&(n.orderBy=e.orderBy.map(r=>({...r}))),Array.isArray(e.select)&&(n.select=[...e.select]),Array.isArray(e.joins)&&(n.joins=e.joins.map(r=>this.cloneStructuredJoin(r))),n}appendNestedJoinByAlias(e,n,r){for(let t of e){if(t.as===n)return t.joins=[...t.joins??[],r],true;if(Array.isArray(t.joins)&&this.appendNestedJoinByAlias(t.joins,n,r))return true}return false}parseRelationRef(e){let r=String(e??"").trim().match(/^([A-Za-z0-9_.]+)\s*->\s*([A-Za-z0-9_]+)\.([A-Za-z0-9_.]+)(?:\s+as\s+([A-Za-z0-9_]+))?$/i);if(!r)throw new Error(`Invalid relation format: "${e}". Expected "source.path->collection.target"`);return {source:r[1],collection:r[2],target:r[3],alias:r[4]}}join(e,n){let r=this.buildStructuredJoin(e,n);return this.clone({joins:[...this.sq.joins??[],r]})}joinNested(e,n,r){let t=String(e??"").trim();if(!t)throw new Error("joinNested requires parentAlias");let i=(this.sq.joins??[]).map(a=>this.cloneStructuredJoin(a));if(i.length===0)throw new Error(`joinNested parent alias "${t}" not found`);let s=this.buildStructuredJoin(n,r);if(!this.appendNestedJoinByAlias(i,t,s))throw new Error(`joinNested parent alias "${t}" not found`);return this.clone({joins:i})}Join(e,n){return this.join(e,n)}JoinNested(e,n,r){return this.joinNested(e,n,r)}withRelation(e,n={}){let r=this.parseRelationRef(e);return this.join(r.collection,{...n,source:r.source,target:r.target,as:n.as??r.alias??r.collection})}select(...e){return this.clone({select:e})}distinctField(e){return this.clone({distinctField:e})}vectorSearch(e){return this.clone({vectorSearch:e})}getRawQuery(){let e={...this.sq};return Array.isArray(this.sq.where)&&(e.where=this.sq.where.map(n=>({...n}))),Array.isArray(this.sq.orderBy)&&(e.orderBy=this.sq.orderBy.map(n=>({...n}))),Array.isArray(this.sq.aggregate)&&(e.aggregate=this.sq.aggregate.map(n=>({...n}))),Array.isArray(this.sq.having)&&(e.having=this.sq.having.map(n=>({...n}))),Array.isArray(this.sq.select)&&(e.select=[...this.sq.select]),Array.isArray(this.sq.joins)&&(e.joins=this.sq.joins.map(n=>this.cloneStructuredJoin(n))),this.sq.groupBy?.fields&&(e.groupBy={fields:[...this.sq.groupBy.fields]}),this.sq.startAt?.values&&(e.startAt={values:[...this.sq.startAt.values]}),this.sq.startAfter?.values&&(e.startAfter={values:[...this.sq.startAfter.values]}),this.sq.endAt?.values&&(e.endAt={values:[...this.sq.endAt.values]}),this.sq.endBefore?.values&&(e.endBefore={values:[...this.sq.endBefore.values]}),{collection:this.name,query:e}}async get(){let e=new URL(this.baseUrl);e.searchParams.set("query",JSON.stringify(this.sq)),e.searchParams.set("allowSensitiveAuthUserFields",this.opts.allowSensitiveAuthUserFields?"1":"0");let n;try{n=await fetch(e.toString(),{headers:this.headers});}catch(t){let i=t instanceof Error?t.message:String(t);throw new Error(`[flare-admin] Network error (GET ${this.baseUrl}): ${i}`)}let r=await n.json().catch(()=>({}));if(!n.ok)throw new Error(`[flare-admin] GET ${this.baseUrl} failed (HTTP ${n.status}): ${r.error??"Unknown error"}`);return r.data??[]}async first(){let e=await this.get();return e.length>0?e[0]:null}async last(){let e=await this.get();return e.length>0?e[e.length-1]:null}async add(e){let n;try{n=await fetch(this.baseUrl,{method:"POST",headers:this.headers,body:JSON.stringify(e)});}catch(t){let i=t instanceof Error?t.message:String(t);throw new Error(`[flare-admin] Network error (POST ${this.baseUrl}): ${i}`)}let r=await n.json().catch(()=>({}));if(!n.ok)throw new Error(`[flare-admin] POST ${this.baseUrl} failed (HTTP ${n.status}): ${r.error??"Unknown error"}`);return new f(this.cfg,this.name,r.id)}async deleteMany(){let e;try{e=await fetch(this.baseUrl,{method:"DELETE",headers:this.headers,body:JSON.stringify({where:this.sq.where??[]})});}catch(r){let t=r instanceof Error?r.message:String(r);throw new Error(`[flare-admin] Network error (DELETE ${this.baseUrl}): ${t}`)}let n=await e.json().catch(()=>({}));if(!e.ok)throw new Error(`[flare-admin] DELETE ${this.baseUrl} failed (HTTP ${e.status}): ${n.error??"Unknown error"}`);return n.deleted??0}doc(e){return new f(this.cfg,this.name,e)}};var g=class{constructor(e){this.cfg=e;}collection(e){return new m(this.cfg,e)}};var A=class{constructor(e){this.cfg=e;}async createCustomToken(e,n={}){let r=`${this.cfg.serverUrl.replace(/\/$/,"")}/admin/token`,t=JSON.stringify({appId:this.cfg.appId,uid:String(e),role:n.role??"user",claims:n.claims??{},ttl:n.ttl??this.cfg.defaultTtl}),i;try{i=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.cfg.adminKey}`},body:t});}catch(a){let p=a instanceof Error?a.message:String(a);throw new Error(`[flare-admin] Could not reach FlareServer at "${r}": ${p}
2
+ Make sure FLARE_URL is set correctly and the server is running.`)}let s;try{s=await i.json();}catch{throw new Error(`[flare-admin] Server returned non-JSON response (status ${i.status})`)}if(!i.ok)throw new Error(`[flare-admin] createCustomToken failed (HTTP ${i.status}): `+(s.error??"Unknown error"));if(typeof s.token!="string")throw new Error('[flare-admin] Server response missing "token" field');return s.token}};var y=class{constructor(e){this.cfg=e;}get baseUrl(){return `${this.cfg.serverUrl.replace(/\/$/,"")}/admin/notify/${this.cfg.appId}`}get headers(){return {"Content-Type":"application/json",Authorization:`Bearer ${this.cfg.adminKey}`}}async send(e){let n;try{n=await fetch(`${this.baseUrl}/send`,{method:"POST",headers:this.headers,body:JSON.stringify(e??{})});}catch(t){let i=t instanceof Error?t.message:String(t);throw new Error(`[flare-admin] Network error (POST ${this.baseUrl}/send): ${i}`)}let r=await n.json().catch(()=>({}));if(!n.ok)throw new Error(`[flare-admin] Notification send failed (HTTP ${n.status}): `+(r.error??"Unknown error"));return {sent:!!r.sent,appId:String(r.appId??this.cfg.appId),targetCount:Number(r.targetCount??0),successCount:Number(r.successCount??0),failureCount:Number(r.failureCount??0),invalidatedTokenCount:Number(r.invalidatedTokenCount??0),dryRun:!!r.dryRun}}async tokens(){let e;try{e=await fetch(`${this.baseUrl}/tokens`,{method:"GET",headers:this.headers});}catch(r){let t=r instanceof Error?r.message:String(r);throw new Error(`[flare-admin] Network error (GET ${this.baseUrl}/tokens): ${t}`)}let n=await e.json().catch(()=>({}));if(!e.ok)throw new Error(`[flare-admin] Tokens fetch failed (HTTP ${e.status}): `+(n.error??"Unknown error"));return {appId:String(n.appId??this.cfg.appId),hasPushGateway:!!n.hasPushGateway,total:Number(n.total??0),tokens:Array.isArray(n.tokens)?n.tokens:[]}}};var w=class{constructor(e){this.cfg=e;this.ws=null;this.pendingAcks=new Map;this.subscriptions=new Map;this.activeSubscriptions=new Map;this.subscriptionErrorHandlers=new Map;this.subscriptionPermissionHandlers=new Map;this.subscriptionLastErrors=new Map;this.connected=false;this.shouldReconnect=true;this.reconnectDelay=2e3;let n=e.serverUrl.replace(/^http/,"ws");this.wsUrl=`${n}/?appId=${encodeURIComponent(e.appId)}&adminKey=${encodeURIComponent(e.adminKey)}`,this._readyPromise=new Promise(r=>{this._readyResolve=r;}),this._connect();}ready(){return this._readyPromise}disconnect(){this.shouldReconnect=false,this.ws?.close(1e3,"Admin disconnect"),this.ws=null,this.connected=false;}send(e,n){return new Promise((r,t)=>{let i=T(),s={id:i,type:e,ts:Date.now(),...n},a=setTimeout(()=>{this.pendingAcks.delete(i),t(new Error(`[flare-admin] WS request timeout (${e})`));},1e4);this.pendingAcks.set(i,c=>{clearTimeout(a),c.type==="error"?t(new Error(`[flare-admin] Server error: ${c.message}`)):r(c);});let p=()=>this.ws?.send(JSON.stringify(s));this.connected&&this.ws?.readyState===q.OPEN?p():this.ready().then(p).catch(t);})}subscribe(e,n,r,t,i={}){let s=T(),a={baseId:s,liveId:s,collection:e,docId:n,structuredQuery:r,callback:t,options:i};this.activeSubscriptions.set(s,a),this.subscriptionErrorHandlers.set(s,new Set),this.subscriptionPermissionHandlers.set(s,new Set),this.activateSubscription(a);let p=()=>{let h=this.activeSubscriptions.get(s)?.liveId??s;this.activeSubscriptions.delete(s),this.subscriptions.delete(s),this.subscriptions.delete(h),this.subscriptionErrorHandlers.delete(s),this.subscriptionPermissionHandlers.delete(s),this.subscriptionLastErrors.delete(s),this.connected&&this.send("unsubscribe",{subscriptionId:h}).catch(()=>{});},c=p;return c.unsubscribe=p,c.onError=u=>{this.subscriptionErrorHandlers.get(s)?.add(u);let h=this.subscriptionLastErrors.get(s);if(h)try{u(h);}catch{}return c},c.onPermissionDenied=u=>{this.subscriptionPermissionHandlers.get(s)?.add(u);let h=this.subscriptionLastErrors.get(s);if(h?.permissionDenied)try{u(h);}catch{}return c},c.catch=u=>c.onError(u),c}activateSubscription(e){let n=()=>{this.subscriptions.set(e.liveId,e.callback);let r={collection:e.collection};e.docId&&(r.docId=e.docId),e.structuredQuery&&(r.query=e.structuredQuery),e.options.skipSnapshot&&(r.skipSnapshot=true),this.send("subscribe",r).then(t=>{let i=t.subscriptionId;if(i&&i!==e.liveId){let s=this.subscriptions.get(e.liveId);s&&(this.subscriptions.delete(e.liveId),this.subscriptions.set(i,s),e.liveId=i);}}).catch(()=>{this.subscriptions.delete(e.liveId),this.emitSubscriptionError(e.baseId,this.toSubscriptionError(new Error("Subscribe failed")));});};this.connected?n():this.ready().then(n).catch(r=>{this.emitSubscriptionError(e.baseId,this.toSubscriptionError(r));});}async replayActiveSubscriptions(){if(!this.connected)return;let e=Array.from(this.activeSubscriptions.values());for(let n of e){if(!this.activeSubscriptions.has(n.baseId))continue;let r=n.liveId;this.subscriptions.delete(r),n.liveId=n.baseId,r&&r!==n.baseId&&await this.send("unsubscribe",{subscriptionId:r}).catch(()=>{}),this.activateSubscription(n);}}toSubscriptionError(e){let n=e instanceof Error?e.message:String(e??"Unknown subscription error"),r=n.match(/^\[([^\]]+)\]\s*(.*)$/),t=r?.[1],i=(r?.[2]??n).trim()||n,s=t==="PERMISSION_DENIED"||n.includes("PERMISSION_DENIED");return {code:t,message:i,permissionDenied:s,raw:e}}emitSubscriptionError(e,n){this.subscriptionLastErrors.set(e,n);let r=this.subscriptionErrorHandlers.get(e);if(r)for(let t of r)try{t(n);}catch{}if(n.permissionDenied){let t=this.subscriptionPermissionHandlers.get(e);if(t)for(let i of t)try{i(n);}catch{}}}_connect(){this._readyPromise=new Promise(e=>{this._readyResolve=e;}),this.ws=new q(this.wsUrl),this.ws.on("message",e=>{let n;try{n=JSON.parse(e.toString());}catch{return}this._handle(n);}),this.ws.on("close",e=>{this.connected=false,this.shouldReconnect&&e!==1e3&&setTimeout(()=>{this.reconnectDelay=Math.min(this.reconnectDelay*2,3e4),this._connect();},this.reconnectDelay);}),this.ws.on("error",()=>{});}_handle(e){let n=e.type;if(n==="auth_ok"){this.connected||(this.connected=true,this.reconnectDelay=2e3,this._readyResolve(),this.replayActiveSubscriptions().catch(()=>{}));return}if(n==="ack"||n==="pong"||n==="call_response"){let r=e.correlationId??e.id,t=this.pendingAcks.get(r);t&&(t(e),this.pendingAcks.delete(r));return}if(n==="error"){let r=e.correlationId;if(r){let t=this.pendingAcks.get(r);t&&(t(e),this.pendingAcks.delete(r));let i=Array.from(this.activeSubscriptions.values()).find(s=>s.liveId===r||s.baseId===r);if(i){let s=this.toSubscriptionError(new Error(`[${String(e.code??"ERROR")}] ${String(e.message??"Unknown error")}`));this.emitSubscriptionError(i.baseId,s);}}return}if(n==="snapshot"||n==="change"){let r=e.subscriptionId,t=this.subscriptions.get(r);t&&t({subscriptionId:r,collection:e.collection,docId:e.docId,data:e.data,type:n==="snapshot"?"snapshot":"change",operation:e.operation});}}};var C=class{constructor(e,n,r){this.conn=e;this.collection=n;this.id=r;}onSnapshot(e){let n=()=>{};return n=this.conn.subscribe(this.collection,this.id,void 0,r=>{r.type==="snapshot"&&(e(r),n());}),n}};var b=class o{constructor(e,n){this.conn=e;this.name=n;this.sq={};}clone(e){let n=new o(this.conn,this.name);return n.sq={...this.sq,...e},n}normalizeFilterValue(e,n){return e==="in"||e==="not-in"||e==="array-contains-any"?Array.isArray(n)?n:[n]:n}toQueryFilters(e){return S(e).map(r=>typeof r=="object"&&r!=null&&"field"in r&&"op"in r?{...r,value:this.normalizeFilterValue(r.op,r.value)}:r)}appendAndFilters(e){return this.clone({where:[...this.sq.where??[],...e]})}appendOrFilters(e){let n=[...this.sq.where??[]];if(n.length===0)return this.clone({where:[{or:e}]});let r=n[0];if(n.length===1&&typeof r=="object"&&r!=null&&"or"in r)return this.clone({where:[{or:[...r.or,...e]}]});let i=n.length===1?n[0]:{and:n};return this.clone({where:[{or:[i,...e]}]})}appendFilters(e,n){return n==="or"?this.appendOrFilters(e):this.appendAndFilters(e)}appendOperatorFilter(e,n,r,t){return this.appendFilters([{field:e,op:n,value:this.normalizeFilterValue(n,r)}],t)}where(e){return this.appendFilters(this.toQueryFilters(e),"and")}and(e){return this.appendFilters(this.toQueryFilters(e),"and")}or(e){return this.appendFilters(this.toQueryFilters(e),"or")}in(e,n){return this.appendOperatorFilter(e,"in",n,"and")}andIn(e,n){return this.appendOperatorFilter(e,"in",n,"and")}orIn(e,n){return this.appendOperatorFilter(e,"in",n,"or")}notIn(e,n){return this.appendOperatorFilter(e,"not-in",n,"and")}andNotIn(e,n){return this.appendOperatorFilter(e,"not-in",n,"and")}orNotIn(e,n){return this.appendOperatorFilter(e,"not-in",n,"or")}arrayContains(e,n){return this.appendOperatorFilter(e,"array-contains",n,"and")}andArrayContains(e,n){return this.appendOperatorFilter(e,"array-contains",n,"and")}orArrayContains(e,n){return this.appendOperatorFilter(e,"array-contains",n,"or")}arrayContainsAny(e,n){return this.appendOperatorFilter(e,"array-contains-any",n,"and")}andArrayContainsAny(e,n){return this.appendOperatorFilter(e,"array-contains-any",n,"and")}orArrayContainsAny(e,n){return this.appendOperatorFilter(e,"array-contains-any",n,"or")}some(e,n){return this.appendOperatorFilter(e,"elem-match",n,"and")}andSome(e,n){return this.appendOperatorFilter(e,"elem-match",n,"and")}orSome(e,n){return this.appendOperatorFilter(e,"elem-match",n,"or")}like(e,n){return this.appendOperatorFilter(e,"like",n,"and")}andLike(e,n){return this.appendOperatorFilter(e,"like",n,"and")}orLike(e,n){return this.appendOperatorFilter(e,"like",n,"or")}notLike(e,n){return this.appendOperatorFilter(e,"not-like",n,"and")}andNotLike(e,n){return this.appendOperatorFilter(e,"not-like",n,"and")}orNotLike(e,n){return this.appendOperatorFilter(e,"not-like",n,"or")}exists(e){return this.appendOperatorFilter(e,"exists",true,"and")}andExists(e){return this.appendOperatorFilter(e,"exists",true,"and")}orExists(e){return this.appendOperatorFilter(e,"exists",true,"or")}notExists(e){return this.appendOperatorFilter(e,"not-exists",true,"and")}andNotExists(e){return this.appendOperatorFilter(e,"not-exists",true,"and")}orNotExists(e){return this.appendOperatorFilter(e,"not-exists",true,"or")}latest(){return this.clone({orderBy:[...this.sq.orderBy??[],{field:"_seq",dir:"desc"}]})}newest(){return this.latest()}oldest(){return this.clone({orderBy:[...this.sq.orderBy??[],{field:"_seq",dir:"asc"}]})}orderBy(e,n="asc"){return this.clone({orderBy:[...this.sq.orderBy??[],{field:e,dir:n}]})}limit(e){return this.clone({limit:e})}offset(e){return this.clone({offset:e})}startAt(...e){return this.clone({startAt:{values:e}})}startAfter(...e){return this.clone({startAfter:{values:e}})}endAt(...e){return this.clone({endAt:{values:e}})}endBefore(...e){return this.clone({endBefore:{values:e}})}aggregate(...e){return this.clone({aggregate:[...this.sq.aggregate??[],...e]})}count(e="count"){return this.aggregate({fn:"count",alias:e})}sum(e,n){return this.aggregate({fn:"sum",field:e,alias:n??`sum_${e}`})}avg(e,n){return this.aggregate({fn:"avg",field:e,alias:n??`avg_${e}`})}min(e,n){return this.aggregate({fn:"min",field:e,alias:n??`min_${e}`})}max(e,n){return this.aggregate({fn:"max",field:e,alias:n??`max_${e}`})}distinct(e,n){return this.aggregate({fn:"distinct",field:e,alias:n??`distinct_${e}`})}groupBy(...e){return this.clone({groupBy:{fields:e}})}having(e,n,r){return this.clone({having:[...this.sq.having??[],{field:e,op:n,value:r}]})}buildStructuredJoin(e,n){let r={from:String(e??""),localField:String(n?.source??""),foreignField:String(n?.target??""),as:String(n?.as??""),single:n?.single};return Array.isArray(n?.where)&&(r.where=n.where),Array.isArray(n?.orderBy)&&(r.orderBy=n.orderBy),typeof n?.limit=="number"&&(r.limit=n.limit),typeof n?.offset=="number"&&(r.offset=n.offset),Array.isArray(n?.select)&&(r.select=n.select),Array.isArray(n?.joins)&&(r.joins=n.joins.map(t=>this.buildStructuredJoin(String(t?.collection??""),t))),r}cloneStructuredJoin(e){let n={...e};return Array.isArray(e.where)&&(n.where=e.where.map(r=>({...r}))),Array.isArray(e.orderBy)&&(n.orderBy=e.orderBy.map(r=>({...r}))),Array.isArray(e.select)&&(n.select=[...e.select]),Array.isArray(e.joins)&&(n.joins=e.joins.map(r=>this.cloneStructuredJoin(r))),n}appendNestedJoinByAlias(e,n,r){for(let t of e){if(t.as===n)return t.joins=[...t.joins??[],r],true;if(Array.isArray(t.joins)&&this.appendNestedJoinByAlias(t.joins,n,r))return true}return false}parseRelationRef(e){let r=String(e??"").trim().match(/^([A-Za-z0-9_.]+)\s*->\s*([A-Za-z0-9_]+)\.([A-Za-z0-9_.]+)(?:\s+as\s+([A-Za-z0-9_]+))?$/i);if(!r)throw new Error(`Invalid relation format: "${e}". Expected "source.path->collection.target"`);return {source:r[1],collection:r[2],target:r[3],alias:r[4]}}join(e,n){let r=this.buildStructuredJoin(e,n);return this.clone({joins:[...this.sq.joins??[],r]})}joinNested(e,n,r){let t=String(e??"").trim();if(!t)throw new Error("joinNested requires parentAlias");let i=(this.sq.joins??[]).map(a=>this.cloneStructuredJoin(a));if(i.length===0)throw new Error(`joinNested parent alias "${t}" not found`);let s=this.buildStructuredJoin(n,r);if(!this.appendNestedJoinByAlias(i,t,s))throw new Error(`joinNested parent alias "${t}" not found`);return this.clone({joins:i})}Join(e,n){return this.join(e,n)}JoinNested(e,n,r){return this.joinNested(e,n,r)}withRelation(e,n={}){let r=this.parseRelationRef(e);return this.join(r.collection,{...n,source:r.source,target:r.target,as:n.as??r.alias??r.collection})}select(...e){return this.clone({select:e})}distinctField(e){return this.clone({distinctField:e})}vectorSearch(e){return this.clone({vectorSearch:e})}doc(e){return new C(this.conn,this.name,e)}getRawQuery(){let e={...this.sq};return Array.isArray(this.sq.where)&&(e.where=this.sq.where.map(n=>({...n}))),Array.isArray(this.sq.orderBy)&&(e.orderBy=this.sq.orderBy.map(n=>({...n}))),Array.isArray(this.sq.aggregate)&&(e.aggregate=this.sq.aggregate.map(n=>({...n}))),Array.isArray(this.sq.having)&&(e.having=this.sq.having.map(n=>({...n}))),Array.isArray(this.sq.select)&&(e.select=[...this.sq.select]),Array.isArray(this.sq.joins)&&(e.joins=this.sq.joins.map(n=>this.cloneStructuredJoin(n))),this.sq.groupBy?.fields&&(e.groupBy={fields:[...this.sq.groupBy.fields]}),this.sq.startAt?.values&&(e.startAt={values:[...this.sq.startAt.values]}),this.sq.startAfter?.values&&(e.startAfter={values:[...this.sq.startAfter.values]}),this.sq.endAt?.values&&(e.endAt={values:[...this.sq.endAt.values]}),this.sq.endBefore?.values&&(e.endBefore={values:[...this.sq.endBefore.values]}),{collection:this.name,query:e}}onSnapshot(e){let n=this._buildSq(),r=()=>{};return r=this.conn.subscribe(this.name,void 0,n,t=>{t.type==="snapshot"&&(e(t),r());}),r}onDocAdded(e){let n=this._buildSq();return this.conn.subscribe(this.name,void 0,n,r=>{r.type==="change"&&r.operation==="insert"&&r.data!=null&&e(r.data,r.docId);},{skipSnapshot:true})}onDocUpdated(e){let n=this._buildSq();return this.conn.subscribe(this.name,void 0,n,r=>{r.type==="change"&&(r.operation==="update"||r.operation==="replace")&&r.data!=null&&e(r.data,r.docId);},{skipSnapshot:true})}onDocDeleted(e){let n=this._buildSq();return this.conn.subscribe(this.name,void 0,n,r=>{r.type==="change"&&r.operation==="delete"&&e(r.docId);},{skipSnapshot:true})}onDocChanged(e){let n=this._buildSq();return this.conn.subscribe(this.name,void 0,n,r=>{r.type==="change"&&e(r.data??null,r.docId,r.operation);},{skipSnapshot:true})}_buildSq(){let e={};return this.sq.where&&this.sq.where.length>0&&(e.where=this.sq.where),this.sq.orderBy&&this.sq.orderBy.length>0&&(e.orderBy=this.sq.orderBy),this.sq.limit!==void 0&&(e.limit=this.sq.limit),this.sq.offset!==void 0&&(e.offset=this.sq.offset),this.sq.startAt&&(e.startAt=this.sq.startAt),this.sq.startAfter&&(e.startAfter=this.sq.startAfter),this.sq.endAt&&(e.endAt=this.sq.endAt),this.sq.endBefore&&(e.endBefore=this.sq.endBefore),this.sq.aggregate&&this.sq.aggregate.length>0&&(e.aggregate=this.sq.aggregate),this.sq.groupBy&&(e.groupBy=this.sq.groupBy),this.sq.having&&this.sq.having.length>0&&(e.having=this.sq.having),this.sq.joins&&this.sq.joins.length>0&&(e.joins=this.sq.joins),this.sq.vectorSearch&&(e.vectorSearch=this.sq.vectorSearch),this.sq.select&&this.sq.select.length>0&&(e.select=this.sq.select),this.sq.distinctField&&(e.distinctField=this.sq.distinctField),e}};var v=class{constructor(e){this._ws=new w(e);}collection(e){return new b(this._ws,e)}ready(){return this._ws.ready()}disconnect(){this._ws.disconnect();}};var F=class{constructor(e){this.cfg={defaultTtl:"24h",...e,serverUrl:e.serverUrl.replace(/\/$/,"")};}auth(){return this._auth??(this._auth=new A(this.cfg))}db(){return this._db??(this._db=new g(this.cfg))}connection(){return this._conn??(this._conn=new v(this.cfg))}notifications(){return this._notifications??(this._notifications=new y(this.cfg))}},R=new Map;function de(o,e="[DEFAULT]"){if(R.has(e))return R.get(e);let n=new F(o);return R.set(e,n),n}function k(o="[DEFAULT]"){let e=R.get(o);if(!e)throw new Error(`[flare-admin] No app named "${o}" found. Call connectApp() before getApp().`);return e}function ue(o="[DEFAULT]"){return k(o).auth()}function he(o="[DEFAULT]"){return k(o).db()}function pe(o="[DEFAULT]"){return k(o).connection()}function me(o="[DEFAULT]"){return k(o).notifications()}export{m as AdminCollectionReference,f as AdminDocumentReference,b as AdminLiveCollectionReference,C as AdminLiveDocumentReference,F as FlareAdminApp,A as FlareAdminAuthService,v as FlareAdminConnection,g as FlareAdminDbService,y as FlareAdminNotificationsService,w as FlareAdminWsConnection,ue as auth,de as connectApp,pe as connection,he as db,k as getApp,me as notifications};
@@ -0,0 +1,6 @@
1
+ import { FlareAdminConfig, FlareAdminAuth, CreateCustomTokenOptions } from "../types";
2
+ export declare class FlareAdminAuthService implements FlareAdminAuth {
3
+ private readonly cfg;
4
+ constructor(cfg: Required<FlareAdminConfig>);
5
+ createCustomToken(uid: string | number, opts?: CreateCustomTokenOptions): Promise<string>;
6
+ }
@@ -0,0 +1,14 @@
1
+ import { AdminPushSendInput, AdminPushSendResult, FlareAdminConfig, FlareAdminNotifications, AdminPushToken } from "../types";
2
+ export declare class FlareAdminNotificationsService implements FlareAdminNotifications {
3
+ private readonly cfg;
4
+ constructor(cfg: Required<FlareAdminConfig>);
5
+ private get baseUrl();
6
+ private get headers();
7
+ send(input: AdminPushSendInput): Promise<AdminPushSendResult>;
8
+ tokens(): Promise<{
9
+ appId: string;
10
+ hasPushGateway: boolean;
11
+ total: number;
12
+ tokens: AdminPushToken[];
13
+ }>;
14
+ }
@@ -0,0 +1,5 @@
1
+ import { WhereCondition, WhereFilter } from "../types";
2
+ /** Parse ORM-style shorthand: `{ age: ">= 25" }` → WhereFilter[] */
3
+ export declare function parseWhereCondition(condition: WhereCondition): WhereFilter[];
4
+ export declare function parseScalar(val: string): unknown;
5
+ export declare function shortId(): string;
@@ -0,0 +1,33 @@
1
+ import { AdminLiveCollectionReference } from "./LiveCollection";
2
+ /**
3
+ * The real-time connection service returned by `admin.connection()`.
4
+ *
5
+ * One `FlareAdminConnection` (and its underlying WebSocket) is shared per
6
+ * `FlareAdminApp` instance. Call `.disconnect()` when you no longer need it.
7
+ *
8
+ * @example
9
+ * const unsub = admin.connection()
10
+ * .collection("orders")
11
+ * .where({ status: "pending" })
12
+ * .or({ priority: "high" })
13
+ * .orderBy("createdAt", "desc")
14
+ * .onSnapshot((snap) => console.log(snap));
15
+ *
16
+ * const unsub2 = admin.connection()
17
+ * .collection("users").doc("alice")
18
+ * .onSnapshot((snap) => console.log(snap.data));
19
+ *
20
+ * unsub(); unsub2();
21
+ * admin.connection().disconnect();
22
+ */
23
+ export declare class FlareAdminConnection {
24
+ /**
25
+ * Reference a collection for real-time subscriptions.
26
+ * Returns an `AdminLiveCollectionReference` with the full query-builder API.
27
+ */
28
+ collection<T = Record<string, unknown>>(name: string): AdminLiveCollectionReference<T>;
29
+ /** Wait until the WebSocket is open and authenticated. */
30
+ ready(): Promise<void>;
31
+ /** Close the WebSocket connection permanently (no reconnect). */
32
+ disconnect(): void;
33
+ }
@@ -0,0 +1,93 @@
1
+ import { WhereCondition, HavingClause, JoinClause, AggregateSpec, VectorSearchClause, StructuredQuery, AdminDocAddedCallback, AdminDocChangedCallback, AdminDocDeletedCallback, AdminDocUpdatedCallback, AdminSnapshotCallback, AdminSubscribeOptions, AdminSubscriptionHandle } from "../types";
2
+ import { AdminLiveDocumentReference } from "./LiveDocument";
3
+ type AdminRealtimeSubscriber = {
4
+ subscribe: (collection: string, docId: string | undefined, structuredQuery: StructuredQuery | undefined, callback: AdminSnapshotCallback, options?: AdminSubscribeOptions) => AdminSubscriptionHandle;
5
+ };
6
+ export declare class AdminLiveCollectionReference<T = Record<string, unknown>> {
7
+ private readonly conn;
8
+ readonly name: string;
9
+ private sq;
10
+ constructor(conn: AdminRealtimeSubscriber, name: string);
11
+ private clone;
12
+ private normalizeFilterValue;
13
+ private toQueryFilters;
14
+ private appendAndFilters;
15
+ private appendOrFilters;
16
+ private appendFilters;
17
+ private appendOperatorFilter;
18
+ where(condition: WhereCondition): AdminLiveCollectionReference<T>;
19
+ and(condition: WhereCondition): AdminLiveCollectionReference<T>;
20
+ or(condition: WhereCondition): AdminLiveCollectionReference<T>;
21
+ in(field: string, values: unknown[] | unknown): AdminLiveCollectionReference<T>;
22
+ andIn(field: string, values: unknown[] | unknown): AdminLiveCollectionReference<T>;
23
+ orIn(field: string, values: unknown[] | unknown): AdminLiveCollectionReference<T>;
24
+ notIn(field: string, values: unknown[] | unknown): AdminLiveCollectionReference<T>;
25
+ andNotIn(field: string, values: unknown[] | unknown): AdminLiveCollectionReference<T>;
26
+ orNotIn(field: string, values: unknown[] | unknown): AdminLiveCollectionReference<T>;
27
+ arrayContains(field: string, value: unknown): AdminLiveCollectionReference<T>;
28
+ andArrayContains(field: string, value: unknown): AdminLiveCollectionReference<T>;
29
+ orArrayContains(field: string, value: unknown): AdminLiveCollectionReference<T>;
30
+ arrayContainsAny(field: string, values: unknown[] | unknown): AdminLiveCollectionReference<T>;
31
+ andArrayContainsAny(field: string, values: unknown[] | unknown): AdminLiveCollectionReference<T>;
32
+ orArrayContainsAny(field: string, values: unknown[] | unknown): AdminLiveCollectionReference<T>;
33
+ some(field: string, condition: Record<string, unknown>): AdminLiveCollectionReference<T>;
34
+ andSome(field: string, condition: Record<string, unknown>): AdminLiveCollectionReference<T>;
35
+ orSome(field: string, condition: Record<string, unknown>): AdminLiveCollectionReference<T>;
36
+ like(field: string, value: string): AdminLiveCollectionReference<T>;
37
+ andLike(field: string, value: string): AdminLiveCollectionReference<T>;
38
+ orLike(field: string, value: string): AdminLiveCollectionReference<T>;
39
+ notLike(field: string, value: string): AdminLiveCollectionReference<T>;
40
+ andNotLike(field: string, value: string): AdminLiveCollectionReference<T>;
41
+ orNotLike(field: string, value: string): AdminLiveCollectionReference<T>;
42
+ exists(field: string): AdminLiveCollectionReference<T>;
43
+ andExists(field: string): AdminLiveCollectionReference<T>;
44
+ orExists(field: string): AdminLiveCollectionReference<T>;
45
+ notExists(field: string): AdminLiveCollectionReference<T>;
46
+ andNotExists(field: string): AdminLiveCollectionReference<T>;
47
+ orNotExists(field: string): AdminLiveCollectionReference<T>;
48
+ latest(): AdminLiveCollectionReference<T>;
49
+ newest(): AdminLiveCollectionReference<T>;
50
+ oldest(): AdminLiveCollectionReference<T>;
51
+ orderBy(field: string, dir?: "asc" | "desc"): AdminLiveCollectionReference<T>;
52
+ limit(n: number): AdminLiveCollectionReference<T>;
53
+ offset(n: number): AdminLiveCollectionReference<T>;
54
+ startAt(...values: unknown[]): AdminLiveCollectionReference<T>;
55
+ startAfter(...values: unknown[]): AdminLiveCollectionReference<T>;
56
+ endAt(...values: unknown[]): AdminLiveCollectionReference<T>;
57
+ endBefore(...values: unknown[]): AdminLiveCollectionReference<T>;
58
+ aggregate(...specs: AggregateSpec[]): AdminLiveCollectionReference<T>;
59
+ count(alias?: string): AdminLiveCollectionReference<T>;
60
+ sum(field: string, alias?: string): AdminLiveCollectionReference<T>;
61
+ avg(field: string, alias?: string): AdminLiveCollectionReference<T>;
62
+ min(field: string, alias?: string): AdminLiveCollectionReference<T>;
63
+ max(field: string, alias?: string): AdminLiveCollectionReference<T>;
64
+ distinct(field: string, alias?: string): AdminLiveCollectionReference<T>;
65
+ groupBy(...fields: string[]): AdminLiveCollectionReference<T>;
66
+ having(field: string, op: HavingClause["op"], value: number): AdminLiveCollectionReference<T>;
67
+ private buildStructuredJoin;
68
+ private cloneStructuredJoin;
69
+ private appendNestedJoinByAlias;
70
+ private parseRelationRef;
71
+ join(collectionName: string, j: JoinClause): AdminLiveCollectionReference<T>;
72
+ joinNested(parentAlias: string, collectionName: string, j: JoinClause): AdminLiveCollectionReference<T>;
73
+ Join(collectionName: string, j: JoinClause): AdminLiveCollectionReference<T>;
74
+ JoinNested(parentAlias: string, collectionName: string, j: JoinClause): AdminLiveCollectionReference<T>;
75
+ withRelation(relation: string, options?: (Omit<JoinClause, "source" | "target" | "as"> & {
76
+ as?: string;
77
+ })): AdminLiveCollectionReference<T>;
78
+ select(...fields: string[]): AdminLiveCollectionReference<T>;
79
+ distinctField(field: string): AdminLiveCollectionReference<T>;
80
+ vectorSearch(opts: VectorSearchClause): AdminLiveCollectionReference<T>;
81
+ doc(id: string): AdminLiveDocumentReference<T>;
82
+ getRawQuery(): {
83
+ collection: string;
84
+ query: StructuredQuery;
85
+ };
86
+ onSnapshot(callback: AdminSnapshotCallback<T[]>): () => void;
87
+ onDocAdded(callback: AdminDocAddedCallback<T>): () => void;
88
+ onDocUpdated(callback: AdminDocUpdatedCallback<T>): () => void;
89
+ onDocDeleted(callback: AdminDocDeletedCallback): () => void;
90
+ onDocChanged(callback: AdminDocChangedCallback<T>): () => void;
91
+ private _buildSq;
92
+ }
93
+ export {};
@@ -0,0 +1,32 @@
1
+ import { AdminSnapshotCallback, AdminSubscribeOptions, AdminSubscriptionHandle, StructuredQuery } from "../types";
2
+ type AdminRealtimeSubscriber = {
3
+ subscribe: (collection: string, docId: string | undefined, structuredQuery: StructuredQuery | undefined, callback: AdminSnapshotCallback, options?: AdminSubscribeOptions) => AdminSubscriptionHandle;
4
+ };
5
+ /**
6
+ * Real-time reference to a single document.
7
+ *
8
+ * @example
9
+ * const unsub = admin.connection()
10
+ * .collection("users").doc("alice")
11
+ * .onSnapshot((snap) => console.log(snap.data));
12
+ */
13
+ export declare class AdminLiveDocumentReference<T = Record<string, unknown>> {
14
+ private readonly conn;
15
+ /** Collection name */
16
+ readonly collection: string;
17
+ /** Document ID */
18
+ readonly id: string;
19
+ constructor(conn: AdminRealtimeSubscriber,
20
+ /** Collection name */
21
+ collection: string,
22
+ /** Document ID */
23
+ id: string);
24
+ /**
25
+ * Subscribe to real-time changes for this document.
26
+ * The callback fires once with the initial snapshot, then on every update.
27
+ *
28
+ * @returns An unsubscribe function — call it to stop listening.
29
+ */
30
+ onSnapshot(callback: AdminSnapshotCallback<T>): () => void;
31
+ }
32
+ export {};
@@ -0,0 +1,43 @@
1
+ import { FlareAdminConfig, AdminSnapshotCallback, AdminSubscribeOptions, StructuredQuery, AdminSubscriptionHandle } from "../types";
2
+ /**
3
+ * Manages a persistent admin WebSocket connection to FlareServer.
4
+ * Passes `adminKey` as a query param — the server immediately elevates the
5
+ * socket to role "admin", bypassing all security rules.
6
+ */
7
+ export declare class FlareAdminWsConnection {
8
+ private readonly cfg;
9
+ private ws;
10
+ private readonly pendingAcks;
11
+ private readonly subscriptions;
12
+ private readonly activeSubscriptions;
13
+ private readonly subscriptionErrorHandlers;
14
+ private readonly subscriptionPermissionHandlers;
15
+ private readonly subscriptionLastErrors;
16
+ private connected;
17
+ private shouldReconnect;
18
+ private reconnectDelay;
19
+ private readonly wsUrl;
20
+ private _readyResolve;
21
+ private _readyPromise;
22
+ constructor(cfg: Required<FlareAdminConfig>);
23
+ /** Resolves once the WS is open and AUTH_OK is received from the server. */
24
+ ready(): Promise<void>;
25
+ /** Close the connection permanently (no reconnect). */
26
+ disconnect(): void;
27
+ /**
28
+ * Send a typed message and await its ACK / response.
29
+ */
30
+ send(type: string, payload: Record<string, unknown>): Promise<Record<string, unknown>>;
31
+ /**
32
+ * Subscribe to a collection / document in real-time.
33
+ * Passes a full `StructuredQuery` (or docId) to the server.
34
+ * Returns an unsubscribe function.
35
+ */
36
+ subscribe(collection: string, docId: string | undefined, structuredQuery: StructuredQuery | undefined, callback: AdminSnapshotCallback, options?: AdminSubscribeOptions): AdminSubscriptionHandle;
37
+ private activateSubscription;
38
+ private replayActiveSubscriptions;
39
+ private toSubscriptionError;
40
+ private emitSubscriptionError;
41
+ private _connect;
42
+ private _handle;
43
+ }