@edgedev/firebase 1.0.26 → 1.1.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/index.ts +365 -56
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -12,10 +12,68 @@ import {
|
|
|
12
12
|
WhereFilterOp,
|
|
13
13
|
QueryConstraint,
|
|
14
14
|
Unsubscribe,
|
|
15
|
-
where
|
|
15
|
+
where,
|
|
16
|
+
deleteDoc,
|
|
17
|
+
getDocs,
|
|
18
|
+
getDoc,
|
|
19
|
+
orderBy,
|
|
20
|
+
limit,
|
|
21
|
+
Query,
|
|
22
|
+
startAfter,
|
|
23
|
+
DocumentData
|
|
16
24
|
} from "firebase/firestore";
|
|
17
25
|
|
|
18
|
-
|
|
26
|
+
import {
|
|
27
|
+
getAuth,
|
|
28
|
+
setPersistence,
|
|
29
|
+
browserSessionPersistence,
|
|
30
|
+
browserLocalPersistence,
|
|
31
|
+
Persistence,
|
|
32
|
+
signInWithEmailAndPassword,
|
|
33
|
+
onAuthStateChanged,
|
|
34
|
+
signOut
|
|
35
|
+
} from "firebase/auth";
|
|
36
|
+
|
|
37
|
+
interface FirestoreQuery {
|
|
38
|
+
field: string;
|
|
39
|
+
operator: WhereFilterOp; // '==' | '<' | '<=' | '>' | '>=' | 'array-contains' | 'in' | 'array-contains-any';
|
|
40
|
+
value: unknown;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface FirestoreOrderBy {
|
|
44
|
+
field: string;
|
|
45
|
+
direction: "asc" | "desc";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface FirestoreLimit {
|
|
49
|
+
limit: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface CollectionUnsubscribeObject {
|
|
53
|
+
[key: string]: Unsubscribe;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface CollectionDataObject {
|
|
57
|
+
[key: string]: object;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface UserDataObject {
|
|
61
|
+
uid: string | null;
|
|
62
|
+
email: string;
|
|
63
|
+
loggedIn: boolean;
|
|
64
|
+
logInError: boolean;
|
|
65
|
+
logInErrorMessage: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface Credentials {
|
|
69
|
+
email: string;
|
|
70
|
+
password: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface StaticDataResult {
|
|
74
|
+
data: object;
|
|
75
|
+
next: DocumentData | null;
|
|
76
|
+
}
|
|
19
77
|
|
|
20
78
|
const firebaseConfig = {
|
|
21
79
|
apiKey: import.meta.env.VITE_FIREBASE_API_KEY as string,
|
|
@@ -28,38 +86,257 @@ const firebaseConfig = {
|
|
|
28
86
|
};
|
|
29
87
|
|
|
30
88
|
// Initialize Firebase
|
|
31
|
-
const app = initializeApp(firebaseConfig);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
89
|
+
export const app = initializeApp(firebaseConfig);
|
|
90
|
+
export const auth = getAuth(app);
|
|
91
|
+
export const db = getFirestore(app);
|
|
92
|
+
|
|
93
|
+
onAuthStateChanged(auth, (userAuth) => {
|
|
94
|
+
if (userAuth) {
|
|
95
|
+
user.email = userAuth.email;
|
|
96
|
+
user.uid = userAuth.uid;
|
|
97
|
+
user.loggedIn = true;
|
|
98
|
+
user.logInError = false;
|
|
99
|
+
user.logInErrorMessage = "";
|
|
100
|
+
} else {
|
|
101
|
+
user.email = "";
|
|
102
|
+
user.uid = null;
|
|
103
|
+
user.loggedIn = false;
|
|
104
|
+
user.logInError = false;
|
|
105
|
+
user.logInErrorMessage = "";
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Composable to logout
|
|
110
|
+
export const logOut = (): void => {
|
|
111
|
+
signOut(auth)
|
|
112
|
+
.then(() => {
|
|
113
|
+
Object.keys(unsubscibe).forEach((key) => {
|
|
114
|
+
if (unsubscibe[key] instanceof Function) {
|
|
115
|
+
unsubscibe[key]();
|
|
116
|
+
unsubscibe[key] = null;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
})
|
|
120
|
+
.catch(() => {
|
|
121
|
+
// Do nothing
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Composable to login and set persistence
|
|
126
|
+
export const logIn = (credentials: Credentials, isPersistant = false): void => {
|
|
127
|
+
logOut();
|
|
128
|
+
let persistence: Persistence = browserSessionPersistence;
|
|
129
|
+
if (isPersistant) {
|
|
130
|
+
persistence = browserLocalPersistence;
|
|
131
|
+
}
|
|
132
|
+
setPersistence(auth, persistence)
|
|
133
|
+
.then(() => {
|
|
134
|
+
signInWithEmailAndPassword(auth, credentials.email, credentials.password)
|
|
135
|
+
.then(() => {
|
|
136
|
+
// do nothing
|
|
137
|
+
})
|
|
138
|
+
.catch((error) => {
|
|
139
|
+
user.email = "";
|
|
140
|
+
user.uid = null;
|
|
141
|
+
|
|
142
|
+
user.loggedIn = false;
|
|
143
|
+
user.logInError = true;
|
|
144
|
+
user.logInErrorMessage = error.code + ": " + error.message;
|
|
145
|
+
});
|
|
146
|
+
})
|
|
147
|
+
.catch((error) => {
|
|
148
|
+
user.email = "";
|
|
149
|
+
user.uid = null;
|
|
150
|
+
|
|
151
|
+
user.loggedIn = false;
|
|
152
|
+
user.logInError = true;
|
|
153
|
+
user.logInErrorMessage = error.code + ": " + error.message;
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Keeping this for reference on how to Type a Ref.
|
|
158
|
+
// export const user = ref<UserDataObject>({
|
|
159
|
+
// uid: null,
|
|
160
|
+
// email: "",
|
|
161
|
+
// loggedIn: false,
|
|
162
|
+
// logInError: false,
|
|
163
|
+
// logInErrorMessage: ""
|
|
164
|
+
// });
|
|
45
165
|
|
|
46
166
|
// Simple Store Items (add matching key per firebase collection)
|
|
47
167
|
export const data: CollectionDataObject = reactive({});
|
|
48
168
|
export const unsubscibe: CollectionUnsubscribeObject = reactive({});
|
|
49
|
-
export const user =
|
|
169
|
+
export const user: UserDataObject = reactive({
|
|
170
|
+
uid: null,
|
|
171
|
+
email: "",
|
|
172
|
+
loggedIn: false,
|
|
173
|
+
logInError: false,
|
|
174
|
+
logInErrorMessage: ""
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
export const getDocData = async (
|
|
178
|
+
collectionPath: string,
|
|
179
|
+
docId: string
|
|
180
|
+
): Promise<{ [key: string]: unknown }> => {
|
|
181
|
+
const docRef = doc(db, collectionPath, docId);
|
|
182
|
+
const docSnap = await getDoc(docRef);
|
|
183
|
+
const docData = docSnap.data();
|
|
184
|
+
docData.docId = docSnap.id;
|
|
185
|
+
return docData;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export const getStaticData = async (
|
|
189
|
+
collectionPath: string,
|
|
190
|
+
queryList: FirestoreQuery[] = [],
|
|
191
|
+
orderList: FirestoreOrderBy[] = [],
|
|
192
|
+
max = 0,
|
|
193
|
+
last: DocumentData | null = null
|
|
194
|
+
): Promise<StaticDataResult> => {
|
|
195
|
+
const data: object = {};
|
|
196
|
+
|
|
197
|
+
const q = getQuery(collectionPath, queryList, orderList, max, last);
|
|
198
|
+
|
|
199
|
+
const docs = await getDocs(q);
|
|
200
|
+
const nextLast: DocumentData = docs.docs[docs.docs.length - 1];
|
|
201
|
+
|
|
202
|
+
docs.forEach((doc) => {
|
|
203
|
+
const item = doc.data();
|
|
204
|
+
item.docId = doc.id;
|
|
205
|
+
data[doc.id] = item;
|
|
206
|
+
});
|
|
207
|
+
return { data, next: nextLast };
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export class SearchStaticData {
|
|
211
|
+
collectionPath = "";
|
|
212
|
+
queryList: FirestoreQuery[] = [];
|
|
213
|
+
orderList: FirestoreOrderBy[] = [];
|
|
214
|
+
max = 0;
|
|
215
|
+
|
|
216
|
+
data = ref({});
|
|
217
|
+
pagination = ref([]);
|
|
218
|
+
staticIsLastPage = ref<boolean>(true);
|
|
219
|
+
staticIsFirstPage = ref<boolean>(true);
|
|
220
|
+
staticCurrentPage = ref("");
|
|
221
|
+
|
|
222
|
+
prev = async (): Promise<void> => {
|
|
223
|
+
const findIndex = this.pagination.value.findIndex(
|
|
224
|
+
(x) => x.key === this.staticCurrentPage.value
|
|
225
|
+
);
|
|
226
|
+
let last = null;
|
|
227
|
+
if (findIndex === 1) {
|
|
228
|
+
this.staticCurrentPage.value = "";
|
|
229
|
+
this.staticIsLastPage.value = false;
|
|
230
|
+
this.staticIsFirstPage.value = true;
|
|
231
|
+
} else {
|
|
232
|
+
last = this.pagination.value[findIndex - 2].next;
|
|
233
|
+
this.staticCurrentPage.value = this.pagination.value[findIndex - 2].key;
|
|
234
|
+
}
|
|
235
|
+
await this.afterNextPrev(last);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
next = async (): Promise<void> => {
|
|
239
|
+
const findIndex = this.pagination.value.findIndex(
|
|
240
|
+
(x) => x.key === this.staticCurrentPage.value
|
|
241
|
+
);
|
|
242
|
+
const last = this.pagination.value[findIndex].next;
|
|
243
|
+
if (this.pagination.value.length === 1) {
|
|
244
|
+
this.staticIsFirstPage.value = true;
|
|
245
|
+
} else {
|
|
246
|
+
this.staticIsFirstPage.value = false;
|
|
247
|
+
}
|
|
248
|
+
await this.afterNextPrev(last);
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
afterNextPrev = async (last): Promise<void> => {
|
|
252
|
+
let results = await getStaticData(
|
|
253
|
+
"users",
|
|
254
|
+
this.queryList,
|
|
255
|
+
this.orderList,
|
|
256
|
+
this.max,
|
|
257
|
+
last
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
if (last && Object.keys(results.data).length === 0) {
|
|
261
|
+
this.staticIsLastPage.value = true;
|
|
262
|
+
if (this.pagination.value.length === 1) {
|
|
263
|
+
last = null;
|
|
264
|
+
this.staticCurrentPage.value = "";
|
|
265
|
+
this.staticIsFirstPage.value = true;
|
|
266
|
+
} else {
|
|
267
|
+
last = this.pagination.value[this.pagination.value.length - 2].next;
|
|
268
|
+
this.staticCurrentPage.value =
|
|
269
|
+
this.pagination.value[this.pagination.value.length - 2].key;
|
|
270
|
+
}
|
|
271
|
+
results = await getStaticData(
|
|
272
|
+
"users",
|
|
273
|
+
this.queryList,
|
|
274
|
+
this.orderList,
|
|
275
|
+
this.max,
|
|
276
|
+
last
|
|
277
|
+
);
|
|
278
|
+
} else {
|
|
279
|
+
this.staticIsLastPage.value = false;
|
|
280
|
+
if (this.pagination.value.length === 1) {
|
|
281
|
+
this.staticIsFirstPage.value = false;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
this.data.value = results.data;
|
|
285
|
+
this.staticCurrentPage.value = results.next.id;
|
|
286
|
+
if (!this.staticIsLastPage.value) {
|
|
287
|
+
if (results.next) {
|
|
288
|
+
const findItem = this.pagination.value.find(
|
|
289
|
+
(x) => x.key === results.next.id
|
|
290
|
+
);
|
|
291
|
+
if (!findItem) {
|
|
292
|
+
this.pagination.value.push({
|
|
293
|
+
key: results.next.id,
|
|
294
|
+
next: results.next
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
getData = async (
|
|
302
|
+
collectionPath: string,
|
|
303
|
+
queryList: FirestoreQuery[] = [],
|
|
304
|
+
orderList: FirestoreOrderBy[] = [],
|
|
305
|
+
max = 0
|
|
306
|
+
): Promise<void> => {
|
|
307
|
+
this.collectionPath = collectionPath;
|
|
308
|
+
this.queryList = queryList;
|
|
309
|
+
this.orderList = orderList;
|
|
310
|
+
this.max = max;
|
|
311
|
+
this.staticIsLastPage.value = false;
|
|
312
|
+
this.staticIsFirstPage.value = true;
|
|
313
|
+
this.staticCurrentPage.value = "";
|
|
314
|
+
this.pagination.value = [];
|
|
315
|
+
this.pagination.value = [];
|
|
316
|
+
this.data.value = {};
|
|
317
|
+
const results = await getStaticData(
|
|
318
|
+
collectionPath,
|
|
319
|
+
queryList,
|
|
320
|
+
orderList,
|
|
321
|
+
max
|
|
322
|
+
);
|
|
323
|
+
if (Object.keys(results.data).length > 0) {
|
|
324
|
+
this.data.value = results.data;
|
|
325
|
+
this.staticCurrentPage.value = results.next.id;
|
|
326
|
+
this.pagination.value.push({ key: results.next.id, next: results.next });
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
50
330
|
|
|
51
331
|
// Composable to start snapshot listener and set unsubscribe function
|
|
52
332
|
export const startSnapshot = (
|
|
53
333
|
collectionPath: string,
|
|
54
|
-
queryList: FirestoreQuery[] = []
|
|
334
|
+
queryList: FirestoreQuery[] = [],
|
|
335
|
+
orderList: FirestoreOrderBy[] = [],
|
|
336
|
+
max = 0
|
|
55
337
|
): void => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
const queryConditions: QueryConstraint[] = queryList.map((condition) =>
|
|
60
|
-
where(condition.field, condition.operator, condition.value)
|
|
61
|
-
);
|
|
62
|
-
const q = query(collection(db, collectionPath), ...queryConditions);
|
|
338
|
+
data[collectionPath] = {};
|
|
339
|
+
const q = getQuery(collectionPath, queryList, orderList, max);
|
|
63
340
|
const unsubscribe = onSnapshot(q, (querySnapshot) => {
|
|
64
341
|
const items = {};
|
|
65
342
|
querySnapshot.forEach((doc) => {
|
|
@@ -72,30 +349,85 @@ export const startSnapshot = (
|
|
|
72
349
|
unsubscibe[collectionPath] = unsubscribe;
|
|
73
350
|
};
|
|
74
351
|
|
|
75
|
-
|
|
352
|
+
const getQuery = (
|
|
353
|
+
collectionPath: string,
|
|
354
|
+
queryList: FirestoreQuery[] = [],
|
|
355
|
+
orderList: FirestoreOrderBy[] = [],
|
|
356
|
+
max = 0,
|
|
357
|
+
after: DocumentData | null = null
|
|
358
|
+
): Query => {
|
|
359
|
+
const queryConditions: QueryConstraint[] = queryList.map((condition) =>
|
|
360
|
+
where(condition.field, condition.operator, condition.value)
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
const orderConditions: QueryConstraint[] = orderList.map((condition) =>
|
|
364
|
+
orderBy(condition.field, condition.direction)
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
let limitList: FirestoreLimit[] = [];
|
|
368
|
+
if (max > 0) {
|
|
369
|
+
limitList = [{ limit: max }];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const limitConditions: QueryConstraint[] = limitList.map((condition) =>
|
|
373
|
+
limit(condition.limit)
|
|
374
|
+
);
|
|
375
|
+
if (after) {
|
|
376
|
+
return query(
|
|
377
|
+
collection(db, collectionPath),
|
|
378
|
+
...queryConditions,
|
|
379
|
+
...orderConditions,
|
|
380
|
+
...limitConditions,
|
|
381
|
+
startAfter(after)
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
return query(
|
|
385
|
+
collection(db, collectionPath),
|
|
386
|
+
...queryConditions,
|
|
387
|
+
...orderConditions,
|
|
388
|
+
...limitConditions
|
|
389
|
+
);
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// Composable to update/add a document
|
|
393
|
+
export const storeDoc = async (
|
|
394
|
+
collectionPath: string,
|
|
395
|
+
item: object
|
|
396
|
+
): Promise<void> => {
|
|
76
397
|
const cloneItem = JSON.parse(JSON.stringify(item));
|
|
77
|
-
const
|
|
78
|
-
cloneItem.last_updated =
|
|
79
|
-
cloneItem.uid = user
|
|
398
|
+
const currentTime = new Date().getTime();
|
|
399
|
+
cloneItem.last_updated = currentTime;
|
|
400
|
+
cloneItem.uid = user.uid;
|
|
80
401
|
if (!Object.prototype.hasOwnProperty.call(cloneItem, "doc_created_at")) {
|
|
81
|
-
cloneItem.doc_created_at =
|
|
402
|
+
cloneItem.doc_created_at = currentTime;
|
|
82
403
|
}
|
|
83
404
|
if (Object.prototype.hasOwnProperty.call(cloneItem, "docId")) {
|
|
84
405
|
const docId = cloneItem.docId;
|
|
85
406
|
if (Object.prototype.hasOwnProperty.call(data, collectionPath)) {
|
|
86
407
|
data[collectionPath][docId] = cloneItem;
|
|
87
408
|
}
|
|
88
|
-
delete cloneItem.docId;
|
|
89
409
|
const docRef = doc(db, collectionPath, docId);
|
|
90
410
|
updateDoc(docRef, cloneItem);
|
|
91
411
|
} else {
|
|
412
|
+
const docRef = await addDoc(collection(db, collectionPath), cloneItem);
|
|
92
413
|
if (Object.prototype.hasOwnProperty.call(data, collectionPath)) {
|
|
93
|
-
data[collectionPath][
|
|
414
|
+
data[collectionPath][docRef.id] = cloneItem;
|
|
94
415
|
}
|
|
95
|
-
|
|
416
|
+
storeDoc(collectionPath, { ...cloneItem, docId: docRef.id });
|
|
96
417
|
}
|
|
97
418
|
};
|
|
98
419
|
|
|
420
|
+
// Composable to delete a document
|
|
421
|
+
export const removeDoc = (collectionPath: string, docId: string): void => {
|
|
422
|
+
// Just in case getting collection back from firebase is slow:
|
|
423
|
+
if (Object.prototype.hasOwnProperty.call(data, collectionPath)) {
|
|
424
|
+
if (Object.prototype.hasOwnProperty.call(data[collectionPath], docId)) {
|
|
425
|
+
delete data[collectionPath][docId];
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
deleteDoc(doc(db, collectionPath, docId));
|
|
429
|
+
};
|
|
430
|
+
|
|
99
431
|
// Composable to stop snapshot listener
|
|
100
432
|
export const stopSnapshot = (collectionPath: string): void => {
|
|
101
433
|
if (unsubscibe[collectionPath] instanceof Function) {
|
|
@@ -103,26 +435,3 @@ export const stopSnapshot = (collectionPath: string): void => {
|
|
|
103
435
|
unsubscibe[collectionPath] = null;
|
|
104
436
|
}
|
|
105
437
|
};
|
|
106
|
-
|
|
107
|
-
interface Credentials {
|
|
108
|
-
email: string;
|
|
109
|
-
password: string;
|
|
110
|
-
}
|
|
111
|
-
interface FirestoreQuery {
|
|
112
|
-
field: string;
|
|
113
|
-
operator: WhereFilterOp; // '==' | '<' | '<=' | '>' | '>=' | 'array-contains' | 'in' | 'array-contains-any';
|
|
114
|
-
value: unknown;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
interface CollectionUnsubscribeObject {
|
|
118
|
-
[key: string]: Unsubscribe;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
interface CollectionDataObject {
|
|
122
|
-
[key: string]: object;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
interface UserDataObject {
|
|
126
|
-
uid: string | null;
|
|
127
|
-
email: string;
|
|
128
|
-
}
|