@edgedev/firebase 1.1.0 → 1.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 +249 -0
- package/index.ts +54 -43
- 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
|
|
2
|
+
import { reactive } from "vue";
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
getFirestore,
|
|
@@ -207,43 +207,47 @@ export const getStaticData = async (
|
|
|
207
207
|
return { data, next: nextLast };
|
|
208
208
|
};
|
|
209
209
|
|
|
210
|
+
// Class for wrapping a getSaticData to handle pagination
|
|
210
211
|
export class SearchStaticData {
|
|
211
212
|
collectionPath = "";
|
|
212
213
|
queryList: FirestoreQuery[] = [];
|
|
213
214
|
orderList: FirestoreOrderBy[] = [];
|
|
214
215
|
max = 0;
|
|
215
216
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
217
|
+
results = reactive({
|
|
218
|
+
data: {},
|
|
219
|
+
pagination: [],
|
|
220
|
+
staticIsLastPage: true,
|
|
221
|
+
staticIsFirstPage: true,
|
|
222
|
+
staticCurrentPage: ""
|
|
223
|
+
});
|
|
221
224
|
|
|
222
225
|
prev = async (): Promise<void> => {
|
|
223
|
-
const findIndex = this.pagination.
|
|
224
|
-
(x) => x.key === this.staticCurrentPage
|
|
226
|
+
const findIndex = this.results.pagination.findIndex(
|
|
227
|
+
(x) => x.key === this.results.staticCurrentPage
|
|
225
228
|
);
|
|
226
229
|
let last = null;
|
|
227
230
|
if (findIndex === 1) {
|
|
228
|
-
this.staticCurrentPage
|
|
229
|
-
this.staticIsLastPage
|
|
230
|
-
this.staticIsFirstPage
|
|
231
|
+
this.results.staticCurrentPage = "";
|
|
232
|
+
this.results.staticIsLastPage = false;
|
|
233
|
+
this.results.staticIsFirstPage = true;
|
|
231
234
|
} else {
|
|
232
|
-
last = this.pagination
|
|
233
|
-
this.staticCurrentPage
|
|
235
|
+
last = this.results.pagination[findIndex - 2].next;
|
|
236
|
+
this.results.staticCurrentPage =
|
|
237
|
+
this.results.pagination[findIndex - 2].key;
|
|
234
238
|
}
|
|
235
239
|
await this.afterNextPrev(last);
|
|
236
240
|
};
|
|
237
241
|
|
|
238
242
|
next = async (): Promise<void> => {
|
|
239
|
-
const findIndex = this.pagination.
|
|
240
|
-
(x) => x.key === this.staticCurrentPage
|
|
243
|
+
const findIndex = this.results.pagination.findIndex(
|
|
244
|
+
(x) => x.key === this.results.staticCurrentPage
|
|
241
245
|
);
|
|
242
|
-
const last = this.pagination
|
|
243
|
-
if (this.pagination.
|
|
244
|
-
this.staticIsFirstPage
|
|
246
|
+
const last = this.results.pagination[findIndex].next;
|
|
247
|
+
if (this.results.pagination.length === 1) {
|
|
248
|
+
this.results.staticIsFirstPage = true;
|
|
245
249
|
} else {
|
|
246
|
-
this.staticIsFirstPage
|
|
250
|
+
this.results.staticIsFirstPage = false;
|
|
247
251
|
}
|
|
248
252
|
await this.afterNextPrev(last);
|
|
249
253
|
};
|
|
@@ -258,15 +262,15 @@ export class SearchStaticData {
|
|
|
258
262
|
);
|
|
259
263
|
|
|
260
264
|
if (last && Object.keys(results.data).length === 0) {
|
|
261
|
-
this.staticIsLastPage
|
|
262
|
-
if (this.pagination.
|
|
265
|
+
this.results.staticIsLastPage = true;
|
|
266
|
+
if (this.results.pagination.length === 1) {
|
|
263
267
|
last = null;
|
|
264
|
-
this.staticCurrentPage
|
|
265
|
-
this.staticIsFirstPage
|
|
268
|
+
this.results.staticCurrentPage = "";
|
|
269
|
+
this.results.staticIsFirstPage = true;
|
|
266
270
|
} else {
|
|
267
|
-
last = this.pagination
|
|
268
|
-
this.staticCurrentPage
|
|
269
|
-
this.pagination
|
|
271
|
+
last = this.results.pagination[this.results.pagination.length - 2].next;
|
|
272
|
+
this.results.staticCurrentPage =
|
|
273
|
+
this.results.pagination[this.results.pagination.length - 2].key;
|
|
270
274
|
}
|
|
271
275
|
results = await getStaticData(
|
|
272
276
|
"users",
|
|
@@ -276,20 +280,20 @@ export class SearchStaticData {
|
|
|
276
280
|
last
|
|
277
281
|
);
|
|
278
282
|
} else {
|
|
279
|
-
this.staticIsLastPage
|
|
280
|
-
if (this.pagination.
|
|
281
|
-
this.staticIsFirstPage
|
|
283
|
+
this.results.staticIsLastPage = false;
|
|
284
|
+
if (this.results.pagination.length === 1) {
|
|
285
|
+
this.results.staticIsFirstPage = false;
|
|
282
286
|
}
|
|
283
287
|
}
|
|
284
|
-
this.data
|
|
285
|
-
this.staticCurrentPage
|
|
286
|
-
if (!this.staticIsLastPage
|
|
288
|
+
this.results.data = results.data;
|
|
289
|
+
this.results.staticCurrentPage = results.next.id;
|
|
290
|
+
if (!this.results.staticIsLastPage) {
|
|
287
291
|
if (results.next) {
|
|
288
|
-
const findItem = this.pagination.
|
|
292
|
+
const findItem = this.results.pagination.find(
|
|
289
293
|
(x) => x.key === results.next.id
|
|
290
294
|
);
|
|
291
295
|
if (!findItem) {
|
|
292
|
-
this.pagination.
|
|
296
|
+
this.results.pagination.push({
|
|
293
297
|
key: results.next.id,
|
|
294
298
|
next: results.next
|
|
295
299
|
});
|
|
@@ -308,12 +312,12 @@ export class SearchStaticData {
|
|
|
308
312
|
this.queryList = queryList;
|
|
309
313
|
this.orderList = orderList;
|
|
310
314
|
this.max = max;
|
|
311
|
-
this.staticIsLastPage
|
|
312
|
-
this.staticIsFirstPage
|
|
313
|
-
this.staticCurrentPage
|
|
314
|
-
this.pagination
|
|
315
|
-
this.pagination
|
|
316
|
-
this.data
|
|
315
|
+
this.results.staticIsLastPage = true;
|
|
316
|
+
this.results.staticIsFirstPage = true;
|
|
317
|
+
this.results.staticCurrentPage = "";
|
|
318
|
+
this.results.pagination = [];
|
|
319
|
+
this.results.pagination = [];
|
|
320
|
+
this.results.data = {};
|
|
317
321
|
const results = await getStaticData(
|
|
318
322
|
collectionPath,
|
|
319
323
|
queryList,
|
|
@@ -321,9 +325,16 @@ export class SearchStaticData {
|
|
|
321
325
|
max
|
|
322
326
|
);
|
|
323
327
|
if (Object.keys(results.data).length > 0) {
|
|
324
|
-
this.
|
|
325
|
-
this.
|
|
326
|
-
this.
|
|
328
|
+
this.results.staticIsLastPage = false;
|
|
329
|
+
this.results.data = results.data;
|
|
330
|
+
this.results.staticCurrentPage = results.next.id;
|
|
331
|
+
this.results.pagination.push({
|
|
332
|
+
key: results.next.id,
|
|
333
|
+
next: results.next
|
|
334
|
+
});
|
|
335
|
+
} else {
|
|
336
|
+
this.results.staticIsLastPage = true;
|
|
337
|
+
this.results.staticIsFirstPage = true;
|
|
327
338
|
}
|
|
328
339
|
};
|
|
329
340
|
}
|