@sovaria/sdk 0.0.0-stage → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +248 -2
- package/dist/analytics.d.ts +92 -0
- package/dist/analytics.js +236 -0
- package/dist/client.d.ts +146 -0
- package/dist/client.js +583 -0
- package/dist/collections.d.ts +255 -0
- package/dist/collections.js +569 -0
- package/dist/complete-api.d.ts +54 -0
- package/dist/complete-api.js +71 -0
- package/dist/crashes.d.ts +111 -0
- package/dist/crashes.js +244 -0
- package/dist/crypto.d.ts +37 -0
- package/dist/crypto.js +189 -0
- package/dist/errors.d.ts +40 -0
- package/dist/errors.js +258 -0
- package/dist/firestore.d.ts +239 -0
- package/dist/firestore.js +431 -0
- package/dist/generated.d.ts +2462 -0
- package/dist/generated.js +1962 -0
- package/dist/inapp.d.ts +52 -0
- package/dist/inapp.js +48 -0
- package/dist/index.d.ts +3562 -0
- package/dist/index.js +3018 -0
- package/dist/offline.d.ts +92 -0
- package/dist/offline.js +575 -0
- package/dist/passkeys.d.ts +17 -0
- package/dist/passkeys.js +76 -0
- package/dist/perf.d.ts +76 -0
- package/dist/perf.js +212 -0
- package/dist/remote-config.d.ts +87 -0
- package/dist/remote-config.js +182 -0
- package/dist/types.d.ts +102 -0
- package/dist/types.js +1 -0
- package/dist/webpush.d.ts +46 -0
- package/dist/webpush.js +99 -0
- package/package.json +74 -4
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Errors thrown by the SDK.
|
|
3
|
+
*
|
|
4
|
+
* `message` is written for the people using your app ("You don't have permission to do that."),
|
|
5
|
+
* so `catch (e) { show(e.message) }` never leaks policy names, table names or SQL to end users.
|
|
6
|
+
* The server's own text stays available for developers in `technicalMessage` / `detail`,
|
|
7
|
+
* and `code` is a stable value to branch on.
|
|
8
|
+
*/
|
|
9
|
+
export type SovariaErrorCode = 'NETWORK_ERROR' | 'UNAUTHENTICATED' | 'PERMISSION_DENIED' | 'NOT_FOUND' | 'CONFLICT' | 'INVALID_INPUT' | 'PAYLOAD_TOO_LARGE' | 'UNSUPPORTED_TYPE' | 'RATE_LIMITED' | 'PLAN_LIMIT_REACHED' | 'PLAN_FEATURE_REQUIRED' | 'PLAN_QUOTA_EXHAUSTED' | 'SERVICE_UNAVAILABLE' | 'SERVER_ERROR' | 'BAD_REQUEST' | 'CONFIG_ERROR' | 'UNKNOWN';
|
|
10
|
+
export declare class SovariaError extends Error {
|
|
11
|
+
/** HTTP status (0 when the request never got a response). */
|
|
12
|
+
readonly statusCode: number;
|
|
13
|
+
/** The server's response body (or the SDK's own explanation). */
|
|
14
|
+
readonly detail: unknown;
|
|
15
|
+
/** Stable category to branch on. */
|
|
16
|
+
readonly code: SovariaErrorCode;
|
|
17
|
+
/** The server code when one was sent (e.g. PLAN_LIMIT_REACHED), else `code`. */
|
|
18
|
+
readonly serverCode: string;
|
|
19
|
+
/** `HTTP <status>: <server text>` — for logs and developers, not for end users. */
|
|
20
|
+
technicalMessage: string;
|
|
21
|
+
/**
|
|
22
|
+
* The request's trace id (W3C traceparent). In the console, Logs › Request traces (or
|
|
23
|
+
* `sov.api.observability…` / `sov logs why <id>`) says which check refused the request and why.
|
|
24
|
+
*/
|
|
25
|
+
traceId?: string;
|
|
26
|
+
constructor(statusCode: number, detail: unknown, code?: SovariaErrorCode);
|
|
27
|
+
/** Attach the request's trace id (kept in `technicalMessage` for logs). */
|
|
28
|
+
withTraceId(traceId: string | null | undefined): this;
|
|
29
|
+
/** A mistake in how the SDK is set up or called; the message is written for the developer. */
|
|
30
|
+
static config(message: string, statusCode?: number): SovariaError;
|
|
31
|
+
/** The message to show your users (same as `message`). */
|
|
32
|
+
get userMessage(): string;
|
|
33
|
+
toString(): string;
|
|
34
|
+
}
|
|
35
|
+
/** A friendly message for any thrown value: SovariaError text as-is, anything else generic. */
|
|
36
|
+
export declare function userMessageFor(error: unknown, fallback?: string): string;
|
|
37
|
+
/** A W3C trace id (32 hex) for one logical request; every retry of it shares the id. */
|
|
38
|
+
export declare function newTraceId(): string;
|
|
39
|
+
/** `traceparent` for one attempt: the request's trace id and a fresh span id. */
|
|
40
|
+
export declare function traceparentFor(traceId: string): string;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Errors thrown by the SDK.
|
|
3
|
+
*
|
|
4
|
+
* `message` is written for the people using your app ("You don't have permission to do that."),
|
|
5
|
+
* so `catch (e) { show(e.message) }` never leaks policy names, table names or SQL to end users.
|
|
6
|
+
* The server's own text stays available for developers in `technicalMessage` / `detail`,
|
|
7
|
+
* and `code` is a stable value to branch on.
|
|
8
|
+
*/
|
|
9
|
+
const FRIENDLY = {
|
|
10
|
+
NETWORK_ERROR: "Can't reach the server. Check your connection and try again.",
|
|
11
|
+
UNAUTHENTICATED: 'Your session has ended. Please sign in again.',
|
|
12
|
+
PERMISSION_DENIED: "You don't have permission to do that.",
|
|
13
|
+
NOT_FOUND: "We couldn't find what you were looking for.",
|
|
14
|
+
CONFLICT: 'That already exists or was changed by someone else.',
|
|
15
|
+
INVALID_INPUT: 'Some of the information entered is not valid.',
|
|
16
|
+
PAYLOAD_TOO_LARGE: 'That file or request is too large.',
|
|
17
|
+
UNSUPPORTED_TYPE: "That file type isn't allowed here.",
|
|
18
|
+
RATE_LIMITED: 'Too many requests. Please wait a moment and try again.',
|
|
19
|
+
PLAN_LIMIT_REACHED: "This project has reached its plan limit.",
|
|
20
|
+
PLAN_FEATURE_REQUIRED: "This feature isn't available on the current plan.",
|
|
21
|
+
PLAN_QUOTA_EXHAUSTED: "This project's usage quota is used up for now.",
|
|
22
|
+
SERVICE_UNAVAILABLE: 'The service is busy right now. Please try again shortly.',
|
|
23
|
+
SERVER_ERROR: 'Something went wrong on our side. Please try again.',
|
|
24
|
+
BAD_REQUEST: "That request couldn't be completed.",
|
|
25
|
+
CONFIG_ERROR: 'The app is not configured correctly.',
|
|
26
|
+
UNKNOWN: 'Something went wrong. Please try again.',
|
|
27
|
+
};
|
|
28
|
+
const PLAN_CODES = new Set(['PLAN_LIMIT_REACHED', 'PLAN_FEATURE_REQUIRED', 'PLAN_QUOTA_EXHAUSTED']);
|
|
29
|
+
function serverText(detail) {
|
|
30
|
+
if (detail == null)
|
|
31
|
+
return '';
|
|
32
|
+
if (typeof detail === 'string')
|
|
33
|
+
return detail;
|
|
34
|
+
if (Array.isArray(detail))
|
|
35
|
+
return detail.map(serverText).filter(Boolean).join('; ');
|
|
36
|
+
if (typeof detail === 'object') {
|
|
37
|
+
const d = detail;
|
|
38
|
+
for (const key of ['detail', 'message', 'msg', 'error', 'error_description']) {
|
|
39
|
+
if (d[key] != null) {
|
|
40
|
+
const text = serverText(d[key]);
|
|
41
|
+
if (text)
|
|
42
|
+
return text;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
return JSON.stringify(detail);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return String(detail);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return String(detail);
|
|
53
|
+
}
|
|
54
|
+
function explicitCode(detail) {
|
|
55
|
+
if (!detail || typeof detail !== 'object' || Array.isArray(detail))
|
|
56
|
+
return undefined;
|
|
57
|
+
const d = detail;
|
|
58
|
+
if (typeof d.code === 'string')
|
|
59
|
+
return d.code;
|
|
60
|
+
for (const key of ['detail', 'error']) {
|
|
61
|
+
const inner = explicitCode(d[key]);
|
|
62
|
+
if (inner)
|
|
63
|
+
return inner;
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
function planMessage(detail, codes = PLAN_CODES) {
|
|
68
|
+
if (!detail || typeof detail !== 'object' || Array.isArray(detail))
|
|
69
|
+
return undefined;
|
|
70
|
+
const d = detail;
|
|
71
|
+
if (typeof d.code === 'string' && codes.has(d.code) && typeof d.message === 'string')
|
|
72
|
+
return d.message;
|
|
73
|
+
for (const key of ['detail', 'error']) {
|
|
74
|
+
const inner = planMessage(d[key], codes);
|
|
75
|
+
if (inner)
|
|
76
|
+
return inner;
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
// The project's blocking auth hook refused a sign-up or sign-in: its message is written for the app's users.
|
|
81
|
+
const HOOK_DENIED = new Set(['AUTH_HOOK_DENIED']);
|
|
82
|
+
/** FastAPI/pydantic validation errors: [{loc: [..., "field"], msg}] → "title: field required". */
|
|
83
|
+
function validationMessage(detail) {
|
|
84
|
+
const items = Array.isArray(detail)
|
|
85
|
+
? detail
|
|
86
|
+
: detail && typeof detail === 'object' && Array.isArray(detail.detail)
|
|
87
|
+
? (detail.detail)
|
|
88
|
+
: undefined;
|
|
89
|
+
if (!items?.length)
|
|
90
|
+
return undefined;
|
|
91
|
+
const first = items[0];
|
|
92
|
+
if (typeof first?.msg !== 'string')
|
|
93
|
+
return undefined;
|
|
94
|
+
const field = Array.isArray(first.loc) ? first.loc.filter((p) => typeof p === 'string' && p !== 'body').pop() : undefined;
|
|
95
|
+
const raw = first.msg.replace(/^Value error, /, '');
|
|
96
|
+
const plain = humanizeValidation(raw);
|
|
97
|
+
const name = field ? capitalize(String(field).replace(/_/g, ' ')) : '';
|
|
98
|
+
if (plain)
|
|
99
|
+
return name ? `${name} ${plain}` : capitalize(plain);
|
|
100
|
+
return name ? `${name}: ${raw}` : capitalize(raw);
|
|
101
|
+
}
|
|
102
|
+
function capitalize(text) {
|
|
103
|
+
return text ? text.charAt(0).toUpperCase() + text.slice(1) : text;
|
|
104
|
+
}
|
|
105
|
+
/** pydantic wording → plain English, phrased to follow the field name ("Email is required."). */
|
|
106
|
+
function humanizeValidation(msg) {
|
|
107
|
+
const rules = [
|
|
108
|
+
[/^Field required$/i, () => 'is required.'],
|
|
109
|
+
[/^String should have at least (\d+) characters?$/i, (m) => `must be at least ${m[1]} characters.`],
|
|
110
|
+
[/^String should have at most (\d+) characters?$/i, (m) => `must be at most ${m[1]} characters.`],
|
|
111
|
+
[/^Input should be greater than or equal to (\S+)$/i, (m) => `must be ${m[1]} or more.`],
|
|
112
|
+
[/^Input should be less than or equal to (\S+)$/i, (m) => `must be ${m[1]} or less.`],
|
|
113
|
+
[/^Input should be a valid (\w+)/i, (m) => `must be a valid ${m[1]}.`],
|
|
114
|
+
[/not a valid email address/i, () => 'is not a valid email address.'],
|
|
115
|
+
[/^String should match pattern/i, () => 'is not in the right format.'],
|
|
116
|
+
];
|
|
117
|
+
for (const [re, fmt] of rules) {
|
|
118
|
+
const m = msg.match(re);
|
|
119
|
+
if (m)
|
|
120
|
+
return fmt(m);
|
|
121
|
+
}
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
/** Postgres constraint text that is safe and useful to show (no table or policy names). */
|
|
125
|
+
function databaseMessage(text) {
|
|
126
|
+
const t = text.toLowerCase();
|
|
127
|
+
if (t.includes('row-level security') || t.includes('guard denied') || t.includes('permission denied')) {
|
|
128
|
+
return { code: 'PERMISSION_DENIED', message: FRIENDLY.PERMISSION_DENIED };
|
|
129
|
+
}
|
|
130
|
+
if (t.includes('duplicate key') || t.includes('unique constraint')) {
|
|
131
|
+
return { code: 'CONFLICT', message: 'That already exists.' };
|
|
132
|
+
}
|
|
133
|
+
if (t.includes('violates foreign key')) {
|
|
134
|
+
return { code: 'CONFLICT', message: 'That refers to something that no longer exists, or is still in use.' };
|
|
135
|
+
}
|
|
136
|
+
if (t.includes('violates check constraint') || t.includes('violates not-null') || t.includes('invalid input syntax')) {
|
|
137
|
+
return { code: 'INVALID_INPUT', message: FRIENDLY.INVALID_INPUT };
|
|
138
|
+
}
|
|
139
|
+
if (t.includes('value too long'))
|
|
140
|
+
return { code: 'INVALID_INPUT', message: 'One of the values is too long.' };
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
const WRONG_CREDENTIALS = /invalid (email|phone|username|login)[^.]* or password|invalid credentials|incorrect password|wrong password/i;
|
|
144
|
+
function classify(status, detail) {
|
|
145
|
+
const raw = explicitCode(detail);
|
|
146
|
+
if (raw && PLAN_CODES.has(raw)) {
|
|
147
|
+
return { code: raw, message: planMessage(detail) ?? FRIENDLY[raw] };
|
|
148
|
+
}
|
|
149
|
+
if (raw === 'AUTH_HOOK_DENIED') {
|
|
150
|
+
return { code: 'PERMISSION_DENIED', message: planMessage(detail, HOOK_DENIED) ?? FRIENDLY.PERMISSION_DENIED };
|
|
151
|
+
}
|
|
152
|
+
if (raw === 'AUTH_HOOK_UNAVAILABLE')
|
|
153
|
+
return { code: 'SERVICE_UNAVAILABLE', message: FRIENDLY.SERVICE_UNAVAILABLE };
|
|
154
|
+
const text = serverText(detail);
|
|
155
|
+
const db = databaseMessage(text);
|
|
156
|
+
if (status === 0)
|
|
157
|
+
return { code: 'NETWORK_ERROR', message: FRIENDLY.NETWORK_ERROR };
|
|
158
|
+
if (status === 401) {
|
|
159
|
+
// A refused sign-in is not an ended session: the person never had one.
|
|
160
|
+
if (WRONG_CREDENTIALS.test(text))
|
|
161
|
+
return { code: 'UNAUTHENTICATED', message: 'Wrong email or password.' };
|
|
162
|
+
return { code: 'UNAUTHENTICATED', message: FRIENDLY.UNAUTHENTICATED };
|
|
163
|
+
}
|
|
164
|
+
if (status === 403)
|
|
165
|
+
return { code: 'PERMISSION_DENIED', message: FRIENDLY.PERMISSION_DENIED };
|
|
166
|
+
if (db)
|
|
167
|
+
return db;
|
|
168
|
+
if (status === 404)
|
|
169
|
+
return { code: 'NOT_FOUND', message: FRIENDLY.NOT_FOUND };
|
|
170
|
+
if (status === 409)
|
|
171
|
+
return { code: 'CONFLICT', message: FRIENDLY.CONFLICT };
|
|
172
|
+
if (status === 413)
|
|
173
|
+
return { code: 'PAYLOAD_TOO_LARGE', message: FRIENDLY.PAYLOAD_TOO_LARGE };
|
|
174
|
+
if (status === 415)
|
|
175
|
+
return { code: 'UNSUPPORTED_TYPE', message: FRIENDLY.UNSUPPORTED_TYPE };
|
|
176
|
+
if (status === 422)
|
|
177
|
+
return { code: 'INVALID_INPUT', message: validationMessage(detail) ?? FRIENDLY.INVALID_INPUT };
|
|
178
|
+
if (status === 429)
|
|
179
|
+
return { code: 'RATE_LIMITED', message: FRIENDLY.RATE_LIMITED };
|
|
180
|
+
if (status === 502 || status === 503 || status === 504) {
|
|
181
|
+
return { code: 'SERVICE_UNAVAILABLE', message: FRIENDLY.SERVICE_UNAVAILABLE };
|
|
182
|
+
}
|
|
183
|
+
if (status >= 500)
|
|
184
|
+
return { code: 'SERVER_ERROR', message: FRIENDLY.SERVER_ERROR };
|
|
185
|
+
if (status === 400)
|
|
186
|
+
return { code: 'BAD_REQUEST', message: FRIENDLY.BAD_REQUEST };
|
|
187
|
+
return { code: 'UNKNOWN', message: FRIENDLY.UNKNOWN };
|
|
188
|
+
}
|
|
189
|
+
export class SovariaError extends Error {
|
|
190
|
+
/** HTTP status (0 when the request never got a response). */
|
|
191
|
+
statusCode;
|
|
192
|
+
/** The server's response body (or the SDK's own explanation). */
|
|
193
|
+
detail;
|
|
194
|
+
/** Stable category to branch on. */
|
|
195
|
+
code;
|
|
196
|
+
/** The server code when one was sent (e.g. PLAN_LIMIT_REACHED), else `code`. */
|
|
197
|
+
serverCode;
|
|
198
|
+
/** `HTTP <status>: <server text>` — for logs and developers, not for end users. */
|
|
199
|
+
technicalMessage;
|
|
200
|
+
/**
|
|
201
|
+
* The request's trace id (W3C traceparent). In the console, Logs › Request traces (or
|
|
202
|
+
* `sov.api.observability…` / `sov logs why <id>`) says which check refused the request and why.
|
|
203
|
+
*/
|
|
204
|
+
traceId;
|
|
205
|
+
constructor(statusCode, detail, code) {
|
|
206
|
+
const classified = code
|
|
207
|
+
? { code, message: code === 'CONFIG_ERROR' && typeof detail === 'string' ? detail : FRIENDLY[code] }
|
|
208
|
+
: classify(statusCode, detail);
|
|
209
|
+
super(classified.message);
|
|
210
|
+
this.name = 'SovariaError';
|
|
211
|
+
this.statusCode = statusCode;
|
|
212
|
+
this.detail = detail;
|
|
213
|
+
this.code = classified.code;
|
|
214
|
+
this.serverCode = explicitCode(detail) ?? classified.code;
|
|
215
|
+
this.technicalMessage = `HTTP ${statusCode}: ${serverText(detail)}`;
|
|
216
|
+
}
|
|
217
|
+
/** Attach the request's trace id (kept in `technicalMessage` for logs). */
|
|
218
|
+
withTraceId(traceId) {
|
|
219
|
+
if (traceId) {
|
|
220
|
+
this.traceId = traceId;
|
|
221
|
+
this.technicalMessage = `${this.technicalMessage} (trace ${traceId})`;
|
|
222
|
+
}
|
|
223
|
+
return this;
|
|
224
|
+
}
|
|
225
|
+
/** A mistake in how the SDK is set up or called; the message is written for the developer. */
|
|
226
|
+
static config(message, statusCode = 400) {
|
|
227
|
+
return new SovariaError(statusCode, message, 'CONFIG_ERROR');
|
|
228
|
+
}
|
|
229
|
+
/** The message to show your users (same as `message`). */
|
|
230
|
+
get userMessage() {
|
|
231
|
+
return this.message;
|
|
232
|
+
}
|
|
233
|
+
toString() {
|
|
234
|
+
return `SovariaError [${this.code}] ${this.message} (${this.technicalMessage})`;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/** A friendly message for any thrown value: SovariaError text as-is, anything else generic. */
|
|
238
|
+
export function userMessageFor(error, fallback = FRIENDLY.UNKNOWN) {
|
|
239
|
+
if (error instanceof SovariaError)
|
|
240
|
+
return error.message;
|
|
241
|
+
if (error instanceof TypeError && /fetch|network/i.test(error.message))
|
|
242
|
+
return FRIENDLY.NETWORK_ERROR;
|
|
243
|
+
return fallback;
|
|
244
|
+
}
|
|
245
|
+
/** A W3C trace id (32 hex) for one logical request; every retry of it shares the id. */
|
|
246
|
+
export function newTraceId() {
|
|
247
|
+
const bytes = new Uint8Array(16);
|
|
248
|
+
globalThis.crypto?.getRandomValues?.(bytes);
|
|
249
|
+
if (bytes.every((b) => b === 0))
|
|
250
|
+
for (let i = 0; i < 16; i += 1)
|
|
251
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
252
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
253
|
+
}
|
|
254
|
+
/** `traceparent` for one attempt: the request's trace id and a fresh span id. */
|
|
255
|
+
export function traceparentFor(traceId) {
|
|
256
|
+
const span = newTraceId().slice(0, 16);
|
|
257
|
+
return `00-${traceId}-${span}-01`;
|
|
258
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { type DocumentSnapshot as SovDocumentSnapshot, DocumentReference as SovDocumentReference, Query as SovQuery, type QuerySnapshot as SovQuerySnapshot, Transaction as SovTransaction, type VectorQuery as SovVectorQuery, WriteBatch as SovWriteBatch, type QueryFilter, type WhereOp } from './collections.js';
|
|
2
|
+
export type DocumentData = Record<string, any>;
|
|
3
|
+
export type WhereFilterOp = WhereOp;
|
|
4
|
+
export type OrderByDirection = 'asc' | 'desc';
|
|
5
|
+
export type Unsubscribe = () => void;
|
|
6
|
+
export interface SetOptions {
|
|
7
|
+
merge?: boolean;
|
|
8
|
+
mergeFields?: string[];
|
|
9
|
+
}
|
|
10
|
+
/** What getFirestore needs from a Sovaria client (any `SovariaSDK`). */
|
|
11
|
+
import { type OfflineStorage } from './offline.js';
|
|
12
|
+
export interface SovariaLike {
|
|
13
|
+
collection<T = any>(path: string): SovQuery<T>;
|
|
14
|
+
collectionGroup?<T = any>(collectionId: string): SovQuery<T>;
|
|
15
|
+
doc<T = any>(path: string): SovDocumentReference<T>;
|
|
16
|
+
batch(): SovWriteBatch;
|
|
17
|
+
runTransaction<R>(fn: (tx: SovTransaction) => Promise<R>, maxAttempts?: number): Promise<R>;
|
|
18
|
+
}
|
|
19
|
+
export declare class Firestore {
|
|
20
|
+
readonly sov: SovariaLike;
|
|
21
|
+
readonly type = "firestore";
|
|
22
|
+
constructor(sov: SovariaLike);
|
|
23
|
+
}
|
|
24
|
+
/** The Firestore handle for a Sovaria client. */
|
|
25
|
+
export declare function getFirestore(sov: SovariaLike): Firestore;
|
|
26
|
+
export type LocalCache = {
|
|
27
|
+
kind: 'persistent' | 'memory';
|
|
28
|
+
storage?: OfflineStorage;
|
|
29
|
+
};
|
|
30
|
+
/** Firebase's `persistentLocalCache()`: IndexedDB in browsers (memory where there is none). */
|
|
31
|
+
export declare function persistentLocalCache(options?: {
|
|
32
|
+
storage?: OfflineStorage;
|
|
33
|
+
}): LocalCache;
|
|
34
|
+
/** Firebase's `memoryLocalCache()`: offline cache for this page/process only. */
|
|
35
|
+
export declare function memoryLocalCache(): LocalCache;
|
|
36
|
+
/** `initializeFirestore(sov, { localCache: persistentLocalCache() })` turns on offline mode, like Firebase. */
|
|
37
|
+
export declare function initializeFirestore(sov: SovariaLike, settings?: {
|
|
38
|
+
localCache?: LocalCache;
|
|
39
|
+
}): Firestore;
|
|
40
|
+
/** Firebase's (older) way to turn on the persistent cache. */
|
|
41
|
+
export declare function enableIndexedDbPersistence(db: Firestore): Promise<void>;
|
|
42
|
+
export declare class DocumentReference<T = DocumentData> {
|
|
43
|
+
readonly firestore: Firestore;
|
|
44
|
+
readonly _ref: SovDocumentReference<T>;
|
|
45
|
+
readonly type = "document";
|
|
46
|
+
constructor(firestore: Firestore, _ref: SovDocumentReference<T>);
|
|
47
|
+
get id(): string;
|
|
48
|
+
get path(): string;
|
|
49
|
+
get parent(): CollectionReference<T>;
|
|
50
|
+
}
|
|
51
|
+
export declare class Query<T = DocumentData> {
|
|
52
|
+
readonly firestore: Firestore;
|
|
53
|
+
readonly _query: SovQuery<T>;
|
|
54
|
+
readonly type: 'query' | 'collection';
|
|
55
|
+
constructor(firestore: Firestore, _query: SovQuery<T>);
|
|
56
|
+
/** Vector search, Admin-SDK style: `collection(db, 'items').findNearest({...}).get()`. */
|
|
57
|
+
findNearest(options: VectorQueryOptions): VectorQuery<T>;
|
|
58
|
+
}
|
|
59
|
+
/** A vector value (Firebase `vector([...])`); stored as an array of numbers. */
|
|
60
|
+
export declare class VectorValue {
|
|
61
|
+
readonly type = "vector";
|
|
62
|
+
private readonly values;
|
|
63
|
+
constructor(values: number[]);
|
|
64
|
+
toArray(): number[];
|
|
65
|
+
isEqual(other: VectorValue): boolean;
|
|
66
|
+
}
|
|
67
|
+
export declare function vector(values: number[]): VectorValue;
|
|
68
|
+
export interface VectorQueryOptions {
|
|
69
|
+
vectorField: string;
|
|
70
|
+
queryVector: number[] | VectorValue;
|
|
71
|
+
limit: number;
|
|
72
|
+
distanceMeasure?: 'COSINE' | 'EUCLIDEAN' | 'DOT_PRODUCT';
|
|
73
|
+
distanceResultField?: string;
|
|
74
|
+
distanceThreshold?: number;
|
|
75
|
+
}
|
|
76
|
+
/** A nearest-neighbour query; read it with `getDocs(vq)` or `vq.get()`. */
|
|
77
|
+
export declare class VectorQuery<T = DocumentData> {
|
|
78
|
+
readonly firestore: Firestore;
|
|
79
|
+
readonly _vector: SovVectorQuery<T>;
|
|
80
|
+
readonly type = "vectorQuery";
|
|
81
|
+
constructor(firestore: Firestore, _vector: SovVectorQuery<T>);
|
|
82
|
+
get(): Promise<QuerySnapshot<T>>;
|
|
83
|
+
}
|
|
84
|
+
/** Firestore `findNearest`: the query's filters first, then the `limit` closest documents (closest first). */
|
|
85
|
+
export declare function findNearest<T = DocumentData>(q: Query<T>, options: VectorQueryOptions): VectorQuery<T>;
|
|
86
|
+
export declare class CollectionReference<T = DocumentData> extends Query<T> {
|
|
87
|
+
readonly type: "collection";
|
|
88
|
+
get id(): string;
|
|
89
|
+
get path(): string;
|
|
90
|
+
}
|
|
91
|
+
type Parent = Firestore | CollectionReference<any> | DocumentReference<any>;
|
|
92
|
+
/** `collection(db, 'users')`, `collection(db, 'users', uid, 'posts')`, `collection(docRef, 'posts')`. */
|
|
93
|
+
export declare function collection<T = DocumentData>(parent: Parent, path: string, ...segments: string[]): CollectionReference<T>;
|
|
94
|
+
/** Every collection with this id at any depth (`comments`, `posts/p1/comments`, …). */
|
|
95
|
+
export declare function collectionGroup<T = DocumentData>(db: Firestore, collectionId: string): Query<T>;
|
|
96
|
+
/** `doc(db, 'users', uid)`, `doc(db, 'users/uid')`, `doc(collectionRef)` (new id), `doc(collectionRef, id)`. */
|
|
97
|
+
export declare function doc<T = DocumentData>(parent: Parent, path?: string, ...segments: string[]): DocumentReference<T>;
|
|
98
|
+
/** `where(documentId(), '==', id)` filters on the document id. */
|
|
99
|
+
export declare function documentId(): string;
|
|
100
|
+
export type QueryConstraint = (q: SovQuery<any>) => SovQuery<any>;
|
|
101
|
+
/** A filter constraint (`where`, `or`, `and`): usable in `query()` and inside `or()` / `and()`. */
|
|
102
|
+
export type QueryFilterConstraint = QueryConstraint & {
|
|
103
|
+
readonly _filter: QueryFilter;
|
|
104
|
+
};
|
|
105
|
+
export type QueryFieldFilterConstraint = QueryFilterConstraint;
|
|
106
|
+
export type QueryCompositeFilterConstraint = QueryFilterConstraint;
|
|
107
|
+
export declare function where(field: string, op: WhereFilterOp, value: unknown): QueryFilterConstraint;
|
|
108
|
+
/** Firebase `or()`: documents matching any of the filters (at most 30 OR terms after expansion). */
|
|
109
|
+
export declare function or(...filters: QueryFilterConstraint[]): QueryCompositeFilterConstraint;
|
|
110
|
+
/** Firebase `and()`: documents matching all of the filters, for nesting inside `or()`. */
|
|
111
|
+
export declare function and(...filters: QueryFilterConstraint[]): QueryCompositeFilterConstraint;
|
|
112
|
+
export declare function orderBy(field: string, direction?: OrderByDirection): QueryConstraint;
|
|
113
|
+
export declare function limit(n: number): QueryConstraint;
|
|
114
|
+
export declare function limitToLast(n: number): QueryConstraint;
|
|
115
|
+
export declare function startAt(...values: unknown[]): QueryConstraint;
|
|
116
|
+
export declare function startAfter(...values: unknown[]): QueryConstraint;
|
|
117
|
+
export declare function endAt(...values: unknown[]): QueryConstraint;
|
|
118
|
+
export declare function endBefore(...values: unknown[]): QueryConstraint;
|
|
119
|
+
export declare function query<T = DocumentData>(base: Query<T>, ...constraints: QueryConstraint[]): Query<T>;
|
|
120
|
+
export declare class DocumentSnapshot<T = DocumentData> {
|
|
121
|
+
readonly ref: DocumentReference<T>;
|
|
122
|
+
readonly _snap: SovDocumentSnapshot<T>;
|
|
123
|
+
constructor(ref: DocumentReference<T>, _snap: SovDocumentSnapshot<T>);
|
|
124
|
+
get id(): string;
|
|
125
|
+
exists(): boolean;
|
|
126
|
+
data(): T | undefined;
|
|
127
|
+
/** A field by dotted path (`get('address.city')`). */
|
|
128
|
+
get(field: string): any;
|
|
129
|
+
/** Offline mode (enableIndexedDbPersistence / persistentLocalCache): from the cache, or a write not yet synced. */
|
|
130
|
+
get metadata(): {
|
|
131
|
+
hasPendingWrites: boolean;
|
|
132
|
+
fromCache: boolean;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
export declare class QueryDocumentSnapshot<T = DocumentData> extends DocumentSnapshot<T> {
|
|
136
|
+
data(): T;
|
|
137
|
+
}
|
|
138
|
+
export interface DocumentChange<T = DocumentData> {
|
|
139
|
+
type: 'added' | 'modified' | 'removed';
|
|
140
|
+
doc: QueryDocumentSnapshot<T>;
|
|
141
|
+
oldIndex: number;
|
|
142
|
+
newIndex: number;
|
|
143
|
+
}
|
|
144
|
+
export declare class QuerySnapshot<T = DocumentData> {
|
|
145
|
+
readonly query: Query<T> | VectorQuery<T>;
|
|
146
|
+
readonly docs: QueryDocumentSnapshot<T>[];
|
|
147
|
+
private readonly changes;
|
|
148
|
+
constructor(query: Query<T> | VectorQuery<T>, raw: SovQuerySnapshot<T>);
|
|
149
|
+
get size(): number;
|
|
150
|
+
get empty(): boolean;
|
|
151
|
+
forEach(callback: (doc: QueryDocumentSnapshot<T>) => void): void;
|
|
152
|
+
docChanges(): DocumentChange<T>[];
|
|
153
|
+
/** Offline mode: answered from the cache, or holding writes not yet synced. */
|
|
154
|
+
get metadata(): {
|
|
155
|
+
hasPendingWrites: boolean;
|
|
156
|
+
fromCache: boolean;
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
export declare function getDoc<T = DocumentData>(ref: DocumentReference<T>): Promise<DocumentSnapshot<T>>;
|
|
160
|
+
export declare function getDocs<T = DocumentData>(q: Query<T> | VectorQuery<T>): Promise<QuerySnapshot<T>>;
|
|
161
|
+
export declare function getCountFromServer(q: Query<any>): Promise<{
|
|
162
|
+
data: () => {
|
|
163
|
+
count: number;
|
|
164
|
+
};
|
|
165
|
+
}>;
|
|
166
|
+
/** Firebase aggregate fields for `getAggregateFromServer`. */
|
|
167
|
+
export declare class AggregateField<R = number | null> {
|
|
168
|
+
readonly aggregateType: 'count' | 'sum' | 'avg';
|
|
169
|
+
readonly _field?: string | undefined;
|
|
170
|
+
readonly _r?: R | undefined;
|
|
171
|
+
readonly type = "AggregateField";
|
|
172
|
+
/** @internal */
|
|
173
|
+
constructor(aggregateType: 'count' | 'sum' | 'avg', _field?: string | undefined, _r?: R | undefined);
|
|
174
|
+
}
|
|
175
|
+
export type AggregateSpec = Record<string, AggregateField<any>>;
|
|
176
|
+
export type AggregateSpecData<S extends AggregateSpec> = {
|
|
177
|
+
[K in keyof S]: S[K] extends AggregateField<infer R> ? R : never;
|
|
178
|
+
};
|
|
179
|
+
export declare function count(): AggregateField<number>;
|
|
180
|
+
export declare function sum(field: string): AggregateField<number>;
|
|
181
|
+
export declare function average(field: string): AggregateField<number | null>;
|
|
182
|
+
/** Firebase `getAggregateFromServer(q, { total: sum('price'), avg: average('rating'), n: count() })`. */
|
|
183
|
+
export declare function getAggregateFromServer<S extends AggregateSpec>(q: Query<any>, spec: S): Promise<{
|
|
184
|
+
data: () => AggregateSpecData<S>;
|
|
185
|
+
}>;
|
|
186
|
+
export declare const getAggregate: typeof getAggregateFromServer;
|
|
187
|
+
type Observer<S> = {
|
|
188
|
+
next?: (snap: S) => void;
|
|
189
|
+
error?: (error: unknown) => void;
|
|
190
|
+
};
|
|
191
|
+
/** Live document or query. Returns its unsubscribe function right away, like Firebase. */
|
|
192
|
+
export declare function onSnapshot<T = DocumentData>(target: DocumentReference<T> | Query<T>, onNextOrObserver: ((snap: any) => void) | Observer<any>, onError?: (error: unknown) => void): Unsubscribe;
|
|
193
|
+
/** A point in time. Stored as an ISO-8601 string in Sovaria. */
|
|
194
|
+
export declare class Timestamp {
|
|
195
|
+
readonly seconds: number;
|
|
196
|
+
readonly nanoseconds: number;
|
|
197
|
+
constructor(seconds: number, nanoseconds: number);
|
|
198
|
+
static now(): Timestamp;
|
|
199
|
+
static fromDate(date: Date): Timestamp;
|
|
200
|
+
static fromMillis(ms: number): Timestamp;
|
|
201
|
+
/** Read back a stored timestamp (an ISO string) as a Timestamp. */
|
|
202
|
+
static fromIso(value: string): Timestamp;
|
|
203
|
+
toMillis(): number;
|
|
204
|
+
toDate(): Date;
|
|
205
|
+
toISOString(): string;
|
|
206
|
+
isEqual(other: Timestamp): boolean;
|
|
207
|
+
}
|
|
208
|
+
export declare function serverTimestamp(): import("./collections.js").FieldTransform;
|
|
209
|
+
export declare function increment(n: number): import("./collections.js").FieldTransform;
|
|
210
|
+
export declare function arrayUnion(...elements: unknown[]): import("./collections.js").FieldTransform;
|
|
211
|
+
export declare function arrayRemove(...elements: unknown[]): import("./collections.js").FieldTransform;
|
|
212
|
+
export declare function deleteField(): import("./collections.js").FieldTransform;
|
|
213
|
+
export declare function setDoc<T = DocumentData>(ref: DocumentReference<T>, data: Partial<T> & DocumentData, options?: SetOptions): Promise<void>;
|
|
214
|
+
export declare function addDoc<T = DocumentData>(ref: CollectionReference<T>, data: Partial<T> & DocumentData): Promise<DocumentReference<T>>;
|
|
215
|
+
/** `updateDoc(ref, {a: 1})` or `updateDoc(ref, 'a', 1, 'b.c', 2)`. */
|
|
216
|
+
export declare function updateDoc(ref: DocumentReference<any>, dataOrField: string | DocumentData, ...rest: unknown[]): Promise<void>;
|
|
217
|
+
export declare function deleteDoc(ref: DocumentReference<any>): Promise<void>;
|
|
218
|
+
export declare class WriteBatch {
|
|
219
|
+
private readonly batch;
|
|
220
|
+
constructor(batch: SovWriteBatch);
|
|
221
|
+
set(ref: DocumentReference<any>, data: DocumentData, options?: SetOptions): WriteBatch;
|
|
222
|
+
update(ref: DocumentReference<any>, dataOrField: string | DocumentData, ...rest: unknown[]): WriteBatch;
|
|
223
|
+
delete(ref: DocumentReference<any>): WriteBatch;
|
|
224
|
+
commit(): Promise<void>;
|
|
225
|
+
}
|
|
226
|
+
export declare function writeBatch(db: Firestore): WriteBatch;
|
|
227
|
+
export declare class Transaction {
|
|
228
|
+
private readonly tx;
|
|
229
|
+
constructor(tx: SovTransaction);
|
|
230
|
+
get<T = DocumentData>(ref: DocumentReference<T>): Promise<DocumentSnapshot<T>>;
|
|
231
|
+
set(ref: DocumentReference<any>, data: DocumentData, options?: SetOptions): Transaction;
|
|
232
|
+
update(ref: DocumentReference<any>, dataOrField: string | DocumentData, ...rest: unknown[]): Transaction;
|
|
233
|
+
delete(ref: DocumentReference<any>): Transaction;
|
|
234
|
+
}
|
|
235
|
+
/** Reads then writes atomically; retried when a document it read changed meanwhile (default 5 attempts). */
|
|
236
|
+
export declare function runTransaction<R>(db: Firestore, fn: (tx: Transaction) => Promise<R>, options?: {
|
|
237
|
+
maxAttempts?: number;
|
|
238
|
+
}): Promise<R>;
|
|
239
|
+
export {};
|