@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.
- package/README.md +267 -0
- package/dist/db/Collection.d.ts +92 -0
- package/dist/db/Document.d.ts +27 -0
- package/dist/db/index.d.ts +22 -0
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +793 -0
- package/dist/index.d.ts +175 -0
- package/dist/index.js +2 -0
- package/dist/lib/auth.d.ts +6 -0
- package/dist/lib/notifications.d.ts +14 -0
- package/dist/lib/utils.d.ts +5 -0
- package/dist/realtime/Connection.d.ts +33 -0
- package/dist/realtime/LiveCollection.d.ts +93 -0
- package/dist/realtime/LiveDocument.d.ts +32 -0
- package/dist/realtime/WsConnection.d.ts +43 -0
- package/dist/types/index.d.ts +275 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# @zuzjs/flare-admin
|
|
2
|
+
|
|
3
|
+
> Server-side admin SDK for [FlareServer](https://www.zedgon.io) — the self-hosted Firebase alternative.
|
|
4
|
+
> Works like `firebase-admin`: runs **only on your backend**, never in a browser.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## How it works
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
Your Express app HTTP REST FlareServer
|
|
12
|
+
────────────────── ──────────► ─────────────────────
|
|
13
|
+
connectApp({ POST /admin/token Validates adminKey
|
|
14
|
+
serverUrl, { uid, role, claims } Signs JWT w/ jwtSecret
|
|
15
|
+
appId, ◄──────────────── Returns { token }
|
|
16
|
+
adminKey,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
admin.auth()
|
|
20
|
+
.createCustomToken(uid)
|
|
21
|
+
→ Promise<string> ──────────────────────► client: flare.auth(token)
|
|
22
|
+
socket elevated ✓
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**No MongoDB URI is ever shared.** The SDK talks to FlareServer's `/admin/token` REST endpoint using only the `adminKey`. This works identically whether you self-host Flare or use it as SaaS at `https://www.zedgon.io`.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install @zuzjs/flare-admin
|
|
33
|
+
# or
|
|
34
|
+
pnpm add @zuzjs/flare-admin
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Quick start
|
|
40
|
+
|
|
41
|
+
### 1. Get your keys
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
flare app create my-app
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This prints two configs:
|
|
48
|
+
|
|
49
|
+
| Config | Safe for... |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `apiKey` | Browser / client-side |
|
|
52
|
+
| `adminKey` | Server-side only — never expose to browser |
|
|
53
|
+
|
|
54
|
+
### 2. Set environment variables
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# .env (server-side only)
|
|
58
|
+
FLARE_URL=http://localhost:5050 # your FlareServer URL
|
|
59
|
+
FLARE_APP_ID=my-app
|
|
60
|
+
FLARE_ADMIN_KEY=FA_ADMIN_xxxxxxxxxxxx
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 3. Initialize once at boot
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { connectApp } from "@zuzjs/flare-admin";
|
|
67
|
+
|
|
68
|
+
const admin = connectApp({
|
|
69
|
+
serverUrl: process.env.FLARE_URL!,
|
|
70
|
+
appId: process.env.FLARE_APP_ID!,
|
|
71
|
+
adminKey: process.env.FLARE_ADMIN_KEY!,
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 4. Mint a token in your login route
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { getApp } from "@zuzjs/flare-admin";
|
|
79
|
+
|
|
80
|
+
app.post("/login", async (req, res) => {
|
|
81
|
+
// --- your existing auth logic ---
|
|
82
|
+
const user = await myAuthLogic(req.body);
|
|
83
|
+
if (!user) return res.status(401).json({ error: "invalid credentials" });
|
|
84
|
+
|
|
85
|
+
// Set your own session / cookie as normal
|
|
86
|
+
req.session.userId = user.id;
|
|
87
|
+
|
|
88
|
+
// --- the bridge ---
|
|
89
|
+
const flareToken = await getApp().auth().createCustomToken(user.id, {
|
|
90
|
+
role: user.isAdmin ? "admin" : "user",
|
|
91
|
+
claims: { email: user.email, plan: user.plan },
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
res.json({ user, flareToken });
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 5. Authenticate the Flare client
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// Browser / React Native
|
|
102
|
+
import FlareClient from "@zuzjs/flare";
|
|
103
|
+
|
|
104
|
+
const flare = new FlareClient({
|
|
105
|
+
endpoint: "http://localhost:5050",
|
|
106
|
+
appId: "my-app",
|
|
107
|
+
apiKey: "FA_xxxxxxxx", // browser-safe key
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
flare.connect();
|
|
111
|
+
|
|
112
|
+
// After login:
|
|
113
|
+
const { flareToken } = await fetch("/login", { method: "POST", body: ... }).then(r => r.json());
|
|
114
|
+
await flare.auth(flareToken);
|
|
115
|
+
// ✅ Socket is now elevated — auth.uid and auth.role are set
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## API
|
|
121
|
+
|
|
122
|
+
### `connectApp(config, name?)`
|
|
123
|
+
|
|
124
|
+
Initialize a FlareAdmin app. Idempotent — safe to call at module scope.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
const admin = connectApp({
|
|
128
|
+
serverUrl: string; // FlareServer base URL
|
|
129
|
+
appId: string; // App ID from `flare app create`
|
|
130
|
+
adminKey: string; // Admin key — server-side only
|
|
131
|
+
defaultTtl?: string; // Default token TTL, e.g. "24h" (default)
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `getApp(name?)`
|
|
136
|
+
|
|
137
|
+
Retrieve an already-initialized app instance.
|
|
138
|
+
|
|
139
|
+
### `auth(name?)`
|
|
140
|
+
|
|
141
|
+
Shorthand for `getApp(name).auth()`.
|
|
142
|
+
|
|
143
|
+
### `admin.auth().createCustomToken(uid, opts?)`
|
|
144
|
+
|
|
145
|
+
Mint a custom auth token.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
const token = await admin.auth().createCustomToken(uid, {
|
|
149
|
+
role?: "user" | "admin" | "anon", // default: "user"
|
|
150
|
+
claims?: Record<string, unknown>, // extra JWT payload fields
|
|
151
|
+
ttl?: string, // e.g. "1h", "7d"
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Security rules on the server
|
|
158
|
+
|
|
159
|
+
Once a client calls `flare.auth(token)`, the FlareServer socket has:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
auth.uid = the uid you passed to createCustomToken()
|
|
163
|
+
auth.role = "user" | "admin" | "anon"
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Use these in your security rules:
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"users": { ".read": "auth != null", ".write": "auth.uid == $docId" },
|
|
171
|
+
"posts": { ".read": "true", ".write": "auth != null" },
|
|
172
|
+
"settings": { ".read": "auth != null", ".write": "auth.role == 'admin'" },
|
|
173
|
+
"*": { ".read": "false", ".write": "false" }
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Multi-tenant / multiple apps
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
const adminA = connectApp({ serverUrl, appId: "app-a", adminKey: "..." }, "a");
|
|
183
|
+
const adminB = connectApp({ serverUrl, appId: "app-b", adminKey: "..." }, "b");
|
|
184
|
+
|
|
185
|
+
const tokenA = await getApp("a").auth().createCustomToken(userId);
|
|
186
|
+
const tokenB = await getApp("b").auth().createCustomToken(userId);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Admin-Only Auth Join Field Control
|
|
190
|
+
|
|
191
|
+
Auth-user joins in admin SDK default to full fields.
|
|
192
|
+
|
|
193
|
+
Use `allowSensitiveAuthUserFields(false)` per query when you want public-profile-only output:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
const safeBoards = await admin.db()
|
|
197
|
+
.collection("boards")
|
|
198
|
+
.join("users", { source: "team.uid", target: "id", as: "team" })
|
|
199
|
+
.allowSensitiveAuthUserFields(false)
|
|
200
|
+
.get();
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Important:
|
|
204
|
+
- This option is admin-only.
|
|
205
|
+
- Normal client/system query paths cannot enable sensitive auth-user fields.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Query Builder Parity (Admin)
|
|
210
|
+
|
|
211
|
+
`admin.db().collection(...)` and `admin.connection().collection(...)` are aligned with the client-style query builder API.
|
|
212
|
+
|
|
213
|
+
### Supported filter helpers
|
|
214
|
+
|
|
215
|
+
`where`, `and`, `or`, `in`, `andIn`, `orIn`, `notIn`, `andNotIn`, `orNotIn`, `arrayContains`, `andArrayContains`, `orArrayContains`, `arrayContainsAny`, `andArrayContainsAny`, `orArrayContainsAny`, `some`, `andSome`, `orSome`, `like`, `andLike`, `orLike`, `notLike`, `andNotLike`, `orNotLike`, `exists`, `andExists`, `orExists`, `notExists`, `andNotExists`, `orNotExists`
|
|
216
|
+
|
|
217
|
+
### Supported sort / cursor / aggregate helpers
|
|
218
|
+
|
|
219
|
+
`latest`, `newest`, `oldest`, `orderBy`, `limit`, `offset`, `startAt`, `startAfter`, `endAt`, `endBefore`, `count`, `sum`, `avg`, `min`, `max`, `distinct`, `groupBy`, `having`, `select`, `distinctField`, `vectorSearch`
|
|
220
|
+
|
|
221
|
+
### Supported join helpers
|
|
222
|
+
|
|
223
|
+
`join`, `Join`, `joinNested`, `JoinNested`, `withRelation`
|
|
224
|
+
|
|
225
|
+
Example:
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
const rows = await admin.db()
|
|
229
|
+
.collection("boards")
|
|
230
|
+
.where({ uid: userId })
|
|
231
|
+
.orSome("team", { uid: userId })
|
|
232
|
+
.join("lists", { source: "id", target: "boardId", as: "lists" })
|
|
233
|
+
.join("users", { source: "team.uid", target: "id", as: "teamMembers" })
|
|
234
|
+
.joinNested("lists", "cards", { source: "id", target: "listId", as: "cards" })
|
|
235
|
+
.withRelation("team.uid->users.id as collaborators")
|
|
236
|
+
.orderBy("updatedAt", "desc")
|
|
237
|
+
.limit(20)
|
|
238
|
+
.get();
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Realtime parity
|
|
242
|
+
|
|
243
|
+
The same query builder surface is available on socket subscriptions:
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
const stop = admin.connection()
|
|
247
|
+
.collection("boards")
|
|
248
|
+
.where({ uid: userId })
|
|
249
|
+
.join("lists", { source: "id", target: "boardId", as: "lists" })
|
|
250
|
+
.onSnapshot((event) => {
|
|
251
|
+
console.log(event.type, event.data);
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Debugging structured query output
|
|
256
|
+
|
|
257
|
+
Both DB and realtime collection references provide `getRawQuery()`:
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
const raw = admin.db()
|
|
261
|
+
.collection("boards")
|
|
262
|
+
.where({ uid: userId })
|
|
263
|
+
.withRelation("team.uid->users.id as collaborators")
|
|
264
|
+
.getRawQuery();
|
|
265
|
+
|
|
266
|
+
console.log(raw.collection, raw.query);
|
|
267
|
+
```
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { FlareAdminConfig, WhereCondition, HavingClause, JoinClause, AggregateSpec, VectorSearchClause, StructuredQuery } from "../types";
|
|
2
|
+
import { AdminDocumentReference } from "./Document";
|
|
3
|
+
export declare class AdminCollectionReference<T = Record<string, unknown>> {
|
|
4
|
+
private readonly cfg;
|
|
5
|
+
readonly name: string;
|
|
6
|
+
private sq;
|
|
7
|
+
private opts;
|
|
8
|
+
constructor(cfg: Required<FlareAdminConfig>, name: string);
|
|
9
|
+
private get baseUrl();
|
|
10
|
+
private get headers();
|
|
11
|
+
private clone;
|
|
12
|
+
allowSensitiveAuthUserFields(enabled?: boolean): AdminCollectionReference<T>;
|
|
13
|
+
private normalizeFilterValue;
|
|
14
|
+
private toQueryFilters;
|
|
15
|
+
private appendAndFilters;
|
|
16
|
+
private appendOrFilters;
|
|
17
|
+
private appendFilters;
|
|
18
|
+
private appendOperatorFilter;
|
|
19
|
+
where(condition: WhereCondition): AdminCollectionReference<T>;
|
|
20
|
+
and(condition: WhereCondition): AdminCollectionReference<T>;
|
|
21
|
+
or(condition: WhereCondition): AdminCollectionReference<T>;
|
|
22
|
+
in(field: string, values: unknown[] | unknown): AdminCollectionReference<T>;
|
|
23
|
+
andIn(field: string, values: unknown[] | unknown): AdminCollectionReference<T>;
|
|
24
|
+
orIn(field: string, values: unknown[] | unknown): AdminCollectionReference<T>;
|
|
25
|
+
notIn(field: string, values: unknown[] | unknown): AdminCollectionReference<T>;
|
|
26
|
+
andNotIn(field: string, values: unknown[] | unknown): AdminCollectionReference<T>;
|
|
27
|
+
orNotIn(field: string, values: unknown[] | unknown): AdminCollectionReference<T>;
|
|
28
|
+
arrayContains(field: string, value: unknown): AdminCollectionReference<T>;
|
|
29
|
+
andArrayContains(field: string, value: unknown): AdminCollectionReference<T>;
|
|
30
|
+
orArrayContains(field: string, value: unknown): AdminCollectionReference<T>;
|
|
31
|
+
arrayContainsAny(field: string, values: unknown[] | unknown): AdminCollectionReference<T>;
|
|
32
|
+
andArrayContainsAny(field: string, values: unknown[] | unknown): AdminCollectionReference<T>;
|
|
33
|
+
orArrayContainsAny(field: string, values: unknown[] | unknown): AdminCollectionReference<T>;
|
|
34
|
+
some(field: string, condition: Record<string, unknown>): AdminCollectionReference<T>;
|
|
35
|
+
andSome(field: string, condition: Record<string, unknown>): AdminCollectionReference<T>;
|
|
36
|
+
orSome(field: string, condition: Record<string, unknown>): AdminCollectionReference<T>;
|
|
37
|
+
like(field: string, value: string): AdminCollectionReference<T>;
|
|
38
|
+
andLike(field: string, value: string): AdminCollectionReference<T>;
|
|
39
|
+
orLike(field: string, value: string): AdminCollectionReference<T>;
|
|
40
|
+
notLike(field: string, value: string): AdminCollectionReference<T>;
|
|
41
|
+
andNotLike(field: string, value: string): AdminCollectionReference<T>;
|
|
42
|
+
orNotLike(field: string, value: string): AdminCollectionReference<T>;
|
|
43
|
+
exists(field: string): AdminCollectionReference<T>;
|
|
44
|
+
andExists(field: string): AdminCollectionReference<T>;
|
|
45
|
+
orExists(field: string): AdminCollectionReference<T>;
|
|
46
|
+
notExists(field: string): AdminCollectionReference<T>;
|
|
47
|
+
andNotExists(field: string): AdminCollectionReference<T>;
|
|
48
|
+
orNotExists(field: string): AdminCollectionReference<T>;
|
|
49
|
+
latest(): AdminCollectionReference<T>;
|
|
50
|
+
newest(): AdminCollectionReference<T>;
|
|
51
|
+
oldest(): AdminCollectionReference<T>;
|
|
52
|
+
orderBy(field: string, dir?: "asc" | "desc"): AdminCollectionReference<T>;
|
|
53
|
+
limit(n: number): AdminCollectionReference<T>;
|
|
54
|
+
offset(n: number): AdminCollectionReference<T>;
|
|
55
|
+
startAt(...values: unknown[]): AdminCollectionReference<T>;
|
|
56
|
+
startAfter(...values: unknown[]): AdminCollectionReference<T>;
|
|
57
|
+
endAt(...values: unknown[]): AdminCollectionReference<T>;
|
|
58
|
+
endBefore(...values: unknown[]): AdminCollectionReference<T>;
|
|
59
|
+
aggregate(...specs: AggregateSpec[]): AdminCollectionReference<T>;
|
|
60
|
+
count(alias?: string): AdminCollectionReference<T>;
|
|
61
|
+
sum(field: string, alias?: string): AdminCollectionReference<T>;
|
|
62
|
+
avg(field: string, alias?: string): AdminCollectionReference<T>;
|
|
63
|
+
min(field: string, alias?: string): AdminCollectionReference<T>;
|
|
64
|
+
max(field: string, alias?: string): AdminCollectionReference<T>;
|
|
65
|
+
distinct(field: string, alias?: string): AdminCollectionReference<T>;
|
|
66
|
+
groupBy(...fields: string[]): AdminCollectionReference<T>;
|
|
67
|
+
having(field: string, op: HavingClause["op"], value: number): AdminCollectionReference<T>;
|
|
68
|
+
private buildStructuredJoin;
|
|
69
|
+
private cloneStructuredJoin;
|
|
70
|
+
private appendNestedJoinByAlias;
|
|
71
|
+
private parseRelationRef;
|
|
72
|
+
join(collectionName: string, j: JoinClause): AdminCollectionReference<T>;
|
|
73
|
+
joinNested(parentAlias: string, collectionName: string, j: JoinClause): AdminCollectionReference<T>;
|
|
74
|
+
Join(collectionName: string, j: JoinClause): AdminCollectionReference<T>;
|
|
75
|
+
JoinNested(parentAlias: string, collectionName: string, j: JoinClause): AdminCollectionReference<T>;
|
|
76
|
+
withRelation(relation: string, options?: (Omit<JoinClause, "source" | "target" | "as"> & {
|
|
77
|
+
as?: string;
|
|
78
|
+
})): AdminCollectionReference<T>;
|
|
79
|
+
select(...fields: string[]): AdminCollectionReference<T>;
|
|
80
|
+
distinctField(field: string): AdminCollectionReference<T>;
|
|
81
|
+
vectorSearch(opts: VectorSearchClause): AdminCollectionReference<T>;
|
|
82
|
+
getRawQuery(): {
|
|
83
|
+
collection: string;
|
|
84
|
+
query: StructuredQuery;
|
|
85
|
+
};
|
|
86
|
+
get(): Promise<T[]>;
|
|
87
|
+
first(): Promise<T | null>;
|
|
88
|
+
last(): Promise<T | null>;
|
|
89
|
+
add(data: Partial<T>): Promise<AdminDocumentReference<T>>;
|
|
90
|
+
deleteMany(): Promise<number>;
|
|
91
|
+
doc(id: string): AdminDocumentReference<T>;
|
|
92
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { FlareAdminConfig } from "../types";
|
|
2
|
+
import { AdminCollectionReference } from "./Collection";
|
|
3
|
+
export declare class AdminDocumentReference<T = Record<string, unknown>> {
|
|
4
|
+
private readonly cfg;
|
|
5
|
+
readonly collection: string;
|
|
6
|
+
readonly id: string;
|
|
7
|
+
constructor(cfg: Required<FlareAdminConfig>, collection: string, id: string);
|
|
8
|
+
private get baseUrl();
|
|
9
|
+
private get headers();
|
|
10
|
+
private request;
|
|
11
|
+
/** Fetch this document. Returns `null` if it does not exist. */
|
|
12
|
+
get(): Promise<T | null>;
|
|
13
|
+
/**
|
|
14
|
+
* Replace (or create) this document entirely.
|
|
15
|
+
* All fields not in `data` are removed.
|
|
16
|
+
*/
|
|
17
|
+
set(data: Partial<T>): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Merge `data` into the existing document (upsert).
|
|
20
|
+
* Only the provided fields are changed.
|
|
21
|
+
*/
|
|
22
|
+
update(data: Partial<T>): Promise<void>;
|
|
23
|
+
/** Delete this document. */
|
|
24
|
+
delete(): Promise<void>;
|
|
25
|
+
/** Return the parent collection reference. */
|
|
26
|
+
parent(): AdminCollectionReference<T>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { FlareAdminConfig, FlareAdminDb } from "../types";
|
|
2
|
+
import { AdminCollectionReference } from "./Collection";
|
|
3
|
+
export declare class FlareAdminDbService implements FlareAdminDb {
|
|
4
|
+
private readonly cfg;
|
|
5
|
+
constructor(cfg: Required<FlareAdminConfig>);
|
|
6
|
+
/**
|
|
7
|
+
* Reference a collection for one-shot queries and mutations.
|
|
8
|
+
*
|
|
9
|
+
* Supports the full StructuredQuery builder API:
|
|
10
|
+
* `where`, `and`, `or`, `orderBy`, `limit`, `offset`,
|
|
11
|
+
* `startAt/After`, `endAt/Before`, `count/sum/avg/min/max/distinct`,
|
|
12
|
+
* `groupBy`, `having`, `Join`, `select`, `distinctField`, `vectorSearch`.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const users = await admin.db().collection<User>("users").get();
|
|
16
|
+
* const admins = await admin.db().collection("users")
|
|
17
|
+
* .where({ role: "admin" }).orderBy("name").get();
|
|
18
|
+
* const ref = await admin.db().collection("users").add({ name: "Alice" });
|
|
19
|
+
* await admin.db().collection("users").doc("alice").update({ plan: "pro" });
|
|
20
|
+
*/
|
|
21
|
+
collection<T = Record<string, unknown>>(name: string): AdminCollectionReference<T>;
|
|
22
|
+
}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
'use strict';var F=require('ws');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var F__default=/*#__PURE__*/_interopDefault(F);/* @zuzjs/flare-admin */
|
|
2
|
+
function v(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:q(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 q(o){return isNaN(Number(o))?o==="true"?true:o==="false"?false:o==="null"?null:o:Number(o)}function k(){return Math.random().toString(36).slice(2,12)+Date.now().toString(36)}var m=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 p(this.cfg,this.collection)}};var p=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 v(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 m(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 m(this.cfg,this.name,e)}};var f=class{constructor(e){this.cfg=e;}collection(e){return new p(this.cfg,e)}};var g=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 h=a instanceof Error?a.message:String(a);throw new Error(`[flare-admin] Could not reach FlareServer at "${r}": ${h}
|
|
3
|
+
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 A=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 y=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=k(),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 h=()=>this.ws?.send(JSON.stringify(s));this.connected&&this.ws?.readyState===F__default.default.OPEN?h():this.ready().then(h).catch(t);})}subscribe(e,n,r,t,i={}){let s=k(),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 h=()=>{let u=this.activeSubscriptions.get(s)?.liveId??s;this.activeSubscriptions.delete(s),this.subscriptions.delete(s),this.subscriptions.delete(u),this.subscriptionErrorHandlers.delete(s),this.subscriptionPermissionHandlers.delete(s),this.subscriptionLastErrors.delete(s),this.connected&&this.send("unsubscribe",{subscriptionId:u}).catch(()=>{});},c=h;return c.unsubscribe=h,c.onError=d=>{this.subscriptionErrorHandlers.get(s)?.add(d);let u=this.subscriptionLastErrors.get(s);if(u)try{d(u);}catch{}return c},c.onPermissionDenied=d=>{this.subscriptionPermissionHandlers.get(s)?.add(d);let u=this.subscriptionLastErrors.get(s);if(u?.permissionDenied)try{d(u);}catch{}return c},c.catch=d=>c.onError(d),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 F__default.default(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 w=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 C=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 v(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 w(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 b=class{constructor(e){this._ws=new y(e);}collection(e){return new C(this._ws,e)}ready(){return this._ws.ready()}disconnect(){this._ws.disconnect();}};var T=class{constructor(e){this.cfg={defaultTtl:"24h",...e,serverUrl:e.serverUrl.replace(/\/$/,"")};}auth(){return this._auth??(this._auth=new g(this.cfg))}db(){return this._db??(this._db=new f(this.cfg))}connection(){return this._conn??(this._conn=new b(this.cfg))}notifications(){return this._notifications??(this._notifications=new A(this.cfg))}},S=new Map;function le(o,e="[DEFAULT]"){if(S.has(e))return S.get(e);let n=new T(o);return S.set(e,n),n}function R(o="[DEFAULT]"){let e=S.get(o);if(!e)throw new Error(`[flare-admin] No app named "${o}" found. Call connectApp() before getApp().`);return e}function de(o="[DEFAULT]"){return R(o).auth()}function ue(o="[DEFAULT]"){return R(o).db()}function he(o="[DEFAULT]"){return R(o).connection()}function pe(o="[DEFAULT]"){return R(o).notifications()}exports.AdminCollectionReference=p;exports.AdminDocumentReference=m;exports.AdminLiveCollectionReference=C;exports.AdminLiveDocumentReference=w;exports.FlareAdminApp=T;exports.FlareAdminAuthService=g;exports.FlareAdminConnection=b;exports.FlareAdminDbService=f;exports.FlareAdminNotificationsService=A;exports.FlareAdminWsConnection=y;exports.auth=de;exports.connectApp=le;exports.connection=he;exports.db=ue;exports.getApp=R;exports.notifications=pe;
|