@edgedev/firebase 1.1.0 → 1.1.2

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 (3) hide show
  1. package/README.md +249 -0
  2. package/index.ts +57 -46
  3. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,249 @@
1
+ # @edgedev/firebase
2
+
3
+ This a collection of vue3 composables of firebase functions
4
+
5
+ ### Table of Contents
6
+ **[Installation](#installation)**
7
+ **[Firebase Authentication](#firebase-authentication)**
8
+ **[Firestore Basic Document Interactions](#firestore-Basic-document-interactions)**
9
+ **[Firestore Snapshot Listeners](#firestore-snapshot-listeners)**
10
+ **[Firestore Static Collection Data](#firestore-static-collection-data)**
11
+
12
+ # Installation
13
+
14
+ pnpm install @edgedev/firebase
15
+
16
+ ```bash
17
+ pnpm install @edgedev/firebase
18
+ ```
19
+ If installing into a Nuxt 3 project, you can make this globally avaiable by adding a file (whatever.ts) to your "composables" folder with the code below.
20
+
21
+ ```typescript
22
+ import * as edgeFirebase from "@edgedev/firebase";
23
+ export { edgeFirebase };
24
+ ```
25
+
26
+ Also or Nuxt 3 SSR must be disabled, update the nuxt.config.ts file:
27
+ ```javascript
28
+ export default defineNuxtConfig({ ssr: false });
29
+ ```
30
+
31
+ If not in Nuxt 3 or there is no need for it to be global, put this in your components <script setup>
32
+
33
+ ```javascript
34
+ <script setup>
35
+ import * as edgeFirebase from "@edgedev/firebase";
36
+ </script>
37
+ ```
38
+ <a name="Firebase Authentication>
39
+ # Firebase Authentication
40
+ (currently only sign in with email and password is supported)
41
+
42
+ If "persistence" is true, login will be saved locally, they can close their browser and when they open they will be logged in automatically. If "persistence" is false login saved only for the session.
43
+ ```javascript
44
+ edgeFirebase.logIn(
45
+ {
46
+ email: "devs@edgemarketing.com",
47
+ password: "pasword"
48
+ },
49
+ true // : persistence
50
+ );
51
+ ```
52
+
53
+ #### User information is contained in: edgeFirebase.user
54
+ The user object is reactive and contains these items:
55
+ ```typescript
56
+ interface UserDataObject {
57
+ uid: string | null;
58
+ email: string;
59
+ loggedIn: boolean;
60
+ logInError: boolean;
61
+ logInErrorMessage: string;
62
+ }
63
+ ```
64
+ The reactive item **edgeFirebase.user.loggedIn** can be used in code or templates to determine if they user is logged in.
65
+
66
+ If there is an error logging in, **edgeFirebase.user.logInError** will be true and **edgeFirebase.user.logInErrorMessage** can be used to return that error to the user.
67
+
68
+ After logging in, **edgeFirebase.logOut** becomes available. Logging out will also automatically disconnect all FireStore listeners.
69
+
70
+ Here is a sample component using the login:
71
+ ```html
72
+ <template>
73
+ <div>
74
+ <div v-if="edgeFirebase.user.loggedIn">
75
+ <button @click="edgeFirebase.logOut">Logout</button><br />
76
+ <AppUsers v-if="edgeFirebase.user.loggedIn" />
77
+ </div>
78
+ <div v-else>
79
+ <input v-model="email" style="width: 400px" type="text" /><br />
80
+ <input v-model="password" style="width: 400px" type="text" /><br />
81
+ <button @click="login">Login</button><br />
82
+ <div v-if="edgeFirebase.user.logInError">
83
+ {{ edgeFirebase.user.logInErrorMessage }}
84
+ </div>
85
+ </div>
86
+ </div>
87
+ </template>
88
+ ```
89
+
90
+ ```javascript
91
+ <script setup>
92
+ const email = ref("");
93
+ const password = ref("");
94
+ const login = () => {
95
+ edgeFirebase.logIn(
96
+ {
97
+ email: email.value,
98
+ password: password.value
99
+ },
100
+ true
101
+ );
102
+ };
103
+ </script>
104
+ ```
105
+ # Firestore Basic Document Interactions
106
+ ### Adding/Update a Document.
107
+ Both adding and updating a document use the same function: **edgeFirebase.storeDoc(collectionPath, object)** for a document to be updated the object must contain the key **docId** and the value must match the ID of a document in the collection on are updating *(Note: All documents returned by edgeFirebase functions will already have docId insert in the document objects)*. If the object does not contain docId or the docId doesn't match a document in the collection, new document will be created.
108
+
109
+ ```javascript
110
+ <script setup>
111
+ const addUser = {name: "bob"};
112
+ edgeFirebase.storeDoc("users", addUser);
113
+ </script>
114
+ ```
115
+ Note: When a document is written to the collection several other keys are added that can be referenced: **doc_created_at**(timestamp of doc creation), **last_updated**(timestamp document last written), **uid**(the user id of the user that updated or created the document).
116
+
117
+ ### Getting a single Document.
118
+ If you want to query a single document from a collection use: **edgeFirebase.getDocData(collectionPath, docId)**
119
+ ```javascript
120
+ <script setup>
121
+ const docId = "DrJRpDXVsEEqZu0UB8NT";
122
+ const singleDoc = edgeFirebase.getDocData("users", docId);
123
+ </script>
124
+ ```
125
+
126
+ ### Deleting a Document.
127
+ To delete a document use: **edgeFirebase.removeDoc(collectionPath, docId)**
128
+ ```javascript
129
+ <script setup>
130
+ const docId = "DrJRpDXVsEEqZu0UB8NT";
131
+ const singleDoc = edgeFirebase.removeDoc("users", docId);
132
+ </script>
133
+ ```
134
+
135
+ # Firestore Snapshot Listeners
136
+ ### Starting a snapshot listener on a collection.
137
+ To start a snapshot listen on a collection use: **edgeFirebase.startSnapshot(collectionPath)**
138
+ ```javascript
139
+ <script setup>
140
+ edgeFirebase.startSnapshot("users");
141
+ </script>
142
+ ```
143
+ Once you have started a snapshot reactive data for that snapshot will be available with **edgeFirebase.data[collectionPath]**. Each document in the data object is keyed with the DocumentId from FireStore.
144
+ ```html
145
+ <template>
146
+ <div>
147
+ <div v-for="item in edgeFirebase.data.users" :key="item">
148
+ {{ item.name }}
149
+ </div>
150
+ </div>
151
+ </template>
152
+ ```
153
+ ### Snapshot listeners can also be queried, sorted, and limited.
154
+ #### Query and Sort are an array of objects, Limit is a number
155
+ (if passing more than one query on different keys, FireStore may make you create indexes)
156
+ ```typescript
157
+ interface FirestoreQuery {
158
+ field: string;
159
+ operator: WhereFilterOp; // '==' | '<' | '<=' | '>' | '>=' | 'array-contains' | 'in' | 'array-contains-any';
160
+ value: unknown;
161
+ }
162
+
163
+ interface FirestoreOrderBy {
164
+ field: string;
165
+ direction: "asc" | "desc";
166
+ }
167
+ ```
168
+ ##### Example with query, sort and limit:
169
+ ```javascript
170
+ <script setup>
171
+ const query = [{field: "name", operator: "==", value="Bob"}];
172
+ const sort = [{ field: "name", direction: "asc" }];
173
+ const limit = 10;
174
+ edgeFirebase.startSnapshot("users", query, sort, limit);
175
+ </setup>
176
+ ```
177
+ ### Stopping a snapshot listener
178
+ To stop listening to a collection use: **edgeFirebase.stopSnapshot(collectionPath)**
179
+ ```javascript
180
+ <script setup>
181
+ edgeFirebase.stopSnapshot("users");
182
+ </setup>
183
+ ```
184
+
185
+ # Firestore Static Collection Data
186
+ To get static data from a collection use the Object: **edgeFirebase.SearchStaticData()**. Static search is done from a class to handle pagination better.
187
+ ```javascript
188
+ const staticSearch = new edgeFirebase.SearchStaticData();
189
+ staticSearch.getData("users");
190
+ ```
191
+ After initialized like above... Data will be available from **staticSearch.results.data**
192
+
193
+ ### The static data object can also be queried, sorted, limited and paginated.
194
+ (if passing more than one query on different keys, FireStore may make you create indexes)
195
+ ```typescript
196
+ interface FirestoreQuery {
197
+ field: string;
198
+ operator: WhereFilterOp; // '==' | '<' | '<=' | '>' | '>=' | 'array-contains' | 'in' | 'array-contains-any';
199
+ value: unknown;
200
+ }
201
+
202
+ interface FirestoreOrderBy {
203
+ field: string;
204
+ direction: "asc" | "desc";
205
+ }
206
+ ```
207
+
208
+ ### Pagination
209
+
210
+ For pagination purposes there are 2 functions **staticSearch.next()** and **staticSearch.prev()**
211
+ for updating **staticSearch.results.data** the pagination data set. There are also two helper variables **staticSearch.results.staticIsFirstPage** (set to true if the data is at the first pagination data set) and **staticSearch.results.staticIsLastPage** (set to true if the data is on the last pagination data set). Note: Because of the way Firestore pagination works, you don't know you are at your last data set until you try and query for the next. If you are using using **staticSearch.results.staticIsLastPage** to disable a "Next" button for example it won't happen until the "second" click and in that scenario **staticSearch.results.data** will just remain at the last pagination data set, it won't break.
212
+
213
+ ### Example - Template and code with query, sort, limit, and pagination:
214
+ ```html
215
+ <template>
216
+ <div>
217
+ <div v-for="item in staticSearch.results.data" :key="item">
218
+ {{ item.name }}
219
+ </div>
220
+ <div>
221
+ <button
222
+ v-if="!staticSearch.results.staticIsFirstPage"
223
+ @click="staticSearch.prev()"
224
+ >
225
+ Previous
226
+ </button>
227
+ <button
228
+ v-if="!staticSearch.results.staticIsLastPage"
229
+ @click="staticSearch.next()"
230
+ >
231
+ Next
232
+ </button>
233
+ </div>
234
+ </div>
235
+ </template>
236
+ ```
237
+ ```javascript
238
+ <script setup>
239
+ const staticSearch = new edgeFirebase.SearchStaticData();
240
+
241
+ const query = [{field: "name", operator: "==", value="Bob"}];
242
+ const sort = [{ field: "name", direction: "asc" }];
243
+ const limit = 10;
244
+
245
+ staticSearch.getData("users", query, sort, limit);
246
+ </script>
247
+ ```
248
+ ## License
249
+ [ISC](https://choosealicense.com/licenses/isc/)
package/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { initializeApp } from "firebase/app";
2
- import { reactive, ref } from "vue";
2
+ import { reactive } from "vue";
3
3
 
4
4
  import {
5
5
  getFirestore,
@@ -20,7 +20,8 @@ import {
20
20
  limit,
21
21
  Query,
22
22
  startAfter,
23
- DocumentData
23
+ DocumentData,
24
+ setDoc
24
25
  } from "firebase/firestore";
25
26
 
26
27
  import {
@@ -207,43 +208,47 @@ export const getStaticData = async (
207
208
  return { data, next: nextLast };
208
209
  };
209
210
 
211
+ // Class for wrapping a getSaticData to handle pagination
210
212
  export class SearchStaticData {
211
213
  collectionPath = "";
212
214
  queryList: FirestoreQuery[] = [];
213
215
  orderList: FirestoreOrderBy[] = [];
214
216
  max = 0;
215
217
 
216
- data = ref({});
217
- pagination = ref([]);
218
- staticIsLastPage = ref<boolean>(true);
219
- staticIsFirstPage = ref<boolean>(true);
220
- staticCurrentPage = ref("");
218
+ results = reactive({
219
+ data: {},
220
+ pagination: [],
221
+ staticIsLastPage: true,
222
+ staticIsFirstPage: true,
223
+ staticCurrentPage: ""
224
+ });
221
225
 
222
226
  prev = async (): Promise<void> => {
223
- const findIndex = this.pagination.value.findIndex(
224
- (x) => x.key === this.staticCurrentPage.value
227
+ const findIndex = this.results.pagination.findIndex(
228
+ (x) => x.key === this.results.staticCurrentPage
225
229
  );
226
230
  let last = null;
227
231
  if (findIndex === 1) {
228
- this.staticCurrentPage.value = "";
229
- this.staticIsLastPage.value = false;
230
- this.staticIsFirstPage.value = true;
232
+ this.results.staticCurrentPage = "";
233
+ this.results.staticIsLastPage = false;
234
+ this.results.staticIsFirstPage = true;
231
235
  } else {
232
- last = this.pagination.value[findIndex - 2].next;
233
- this.staticCurrentPage.value = this.pagination.value[findIndex - 2].key;
236
+ last = this.results.pagination[findIndex - 2].next;
237
+ this.results.staticCurrentPage =
238
+ this.results.pagination[findIndex - 2].key;
234
239
  }
235
240
  await this.afterNextPrev(last);
236
241
  };
237
242
 
238
243
  next = async (): Promise<void> => {
239
- const findIndex = this.pagination.value.findIndex(
240
- (x) => x.key === this.staticCurrentPage.value
244
+ const findIndex = this.results.pagination.findIndex(
245
+ (x) => x.key === this.results.staticCurrentPage
241
246
  );
242
- const last = this.pagination.value[findIndex].next;
243
- if (this.pagination.value.length === 1) {
244
- this.staticIsFirstPage.value = true;
247
+ const last = this.results.pagination[findIndex].next;
248
+ if (this.results.pagination.length === 1) {
249
+ this.results.staticIsFirstPage = true;
245
250
  } else {
246
- this.staticIsFirstPage.value = false;
251
+ this.results.staticIsFirstPage = false;
247
252
  }
248
253
  await this.afterNextPrev(last);
249
254
  };
@@ -258,15 +263,15 @@ export class SearchStaticData {
258
263
  );
259
264
 
260
265
  if (last && Object.keys(results.data).length === 0) {
261
- this.staticIsLastPage.value = true;
262
- if (this.pagination.value.length === 1) {
266
+ this.results.staticIsLastPage = true;
267
+ if (this.results.pagination.length === 1) {
263
268
  last = null;
264
- this.staticCurrentPage.value = "";
265
- this.staticIsFirstPage.value = true;
269
+ this.results.staticCurrentPage = "";
270
+ this.results.staticIsFirstPage = true;
266
271
  } 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;
272
+ last = this.results.pagination[this.results.pagination.length - 2].next;
273
+ this.results.staticCurrentPage =
274
+ this.results.pagination[this.results.pagination.length - 2].key;
270
275
  }
271
276
  results = await getStaticData(
272
277
  "users",
@@ -276,20 +281,20 @@ export class SearchStaticData {
276
281
  last
277
282
  );
278
283
  } else {
279
- this.staticIsLastPage.value = false;
280
- if (this.pagination.value.length === 1) {
281
- this.staticIsFirstPage.value = false;
284
+ this.results.staticIsLastPage = false;
285
+ if (this.results.pagination.length === 1) {
286
+ this.results.staticIsFirstPage = false;
282
287
  }
283
288
  }
284
- this.data.value = results.data;
285
- this.staticCurrentPage.value = results.next.id;
286
- if (!this.staticIsLastPage.value) {
289
+ this.results.data = results.data;
290
+ this.results.staticCurrentPage = results.next.id;
291
+ if (!this.results.staticIsLastPage) {
287
292
  if (results.next) {
288
- const findItem = this.pagination.value.find(
293
+ const findItem = this.results.pagination.find(
289
294
  (x) => x.key === results.next.id
290
295
  );
291
296
  if (!findItem) {
292
- this.pagination.value.push({
297
+ this.results.pagination.push({
293
298
  key: results.next.id,
294
299
  next: results.next
295
300
  });
@@ -308,12 +313,12 @@ export class SearchStaticData {
308
313
  this.queryList = queryList;
309
314
  this.orderList = orderList;
310
315
  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 = {};
316
+ this.results.staticIsLastPage = true;
317
+ this.results.staticIsFirstPage = true;
318
+ this.results.staticCurrentPage = "";
319
+ this.results.pagination = [];
320
+ this.results.pagination = [];
321
+ this.results.data = {};
317
322
  const results = await getStaticData(
318
323
  collectionPath,
319
324
  queryList,
@@ -321,9 +326,16 @@ export class SearchStaticData {
321
326
  max
322
327
  );
323
328
  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 });
329
+ this.results.staticIsLastPage = false;
330
+ this.results.data = results.data;
331
+ this.results.staticCurrentPage = results.next.id;
332
+ this.results.pagination.push({
333
+ key: results.next.id,
334
+ next: results.next
335
+ });
336
+ } else {
337
+ this.results.staticIsLastPage = true;
338
+ this.results.staticIsFirstPage = true;
327
339
  }
328
340
  };
329
341
  }
@@ -406,8 +418,7 @@ export const storeDoc = async (
406
418
  if (Object.prototype.hasOwnProperty.call(data, collectionPath)) {
407
419
  data[collectionPath][docId] = cloneItem;
408
420
  }
409
- const docRef = doc(db, collectionPath, docId);
410
- updateDoc(docRef, cloneItem);
421
+ setDoc(doc(db, collectionPath, docId), cloneItem);
411
422
  } else {
412
423
  const docRef = await addDoc(collection(db, collectionPath), cloneItem);
413
424
  if (Object.prototype.hasOwnProperty.call(data, collectionPath)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edgedev/firebase",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Composables and stores for firebase",
5
5
  "main": "index.ts",
6
6
  "author": "Seth Fischer",