@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.
Files changed (2) hide show
  1. package/index.ts +365 -56
  2. 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
- // import { getAuth, signInWithEmailAndPassword } from "firebase/auth";
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
- // const auth = getAuth(app);
33
- // console.log(auth);
34
- const db = getFirestore(app);
35
-
36
- // export const signIn = async (credentials: Credentials): Promise<object> => {
37
- // const user = await signInWithEmailAndPassword(
38
- // auth,
39
- // credentials.email,
40
- // credentials.password
41
- // );
42
- // console.log(user);
43
- // return user;
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 = ref<UserDataObject>({ uid: "", email: "" });
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
- if (data[collectionPath] instanceof Function) {
57
- stopSnapshot(collectionPath);
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
- export const storeDoc = (collectionPath: string, item: object): void => {
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 current_time = new Date().getTime();
78
- cloneItem.last_updated = current_time;
79
- cloneItem.uid = user["uid"];
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 = current_time;
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][current_time] = cloneItem;
414
+ data[collectionPath][docRef.id] = cloneItem;
94
415
  }
95
- addDoc(collection(db, collectionPath), cloneItem);
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
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edgedev/firebase",
3
- "version": "1.0.26",
3
+ "version": "1.1.0",
4
4
  "description": "Composables and stores for firebase",
5
5
  "main": "index.ts",
6
6
  "author": "Seth Fischer",