@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.
- package/README.md +249 -0
- package/index.ts +57 -46
- 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,
|
|
@@ -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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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.
|
|
224
|
-
(x) => x.key === this.staticCurrentPage
|
|
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
|
|
229
|
-
this.staticIsLastPage
|
|
230
|
-
this.staticIsFirstPage
|
|
232
|
+
this.results.staticCurrentPage = "";
|
|
233
|
+
this.results.staticIsLastPage = false;
|
|
234
|
+
this.results.staticIsFirstPage = true;
|
|
231
235
|
} else {
|
|
232
|
-
last = this.pagination
|
|
233
|
-
this.staticCurrentPage
|
|
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.
|
|
240
|
-
(x) => x.key === this.staticCurrentPage
|
|
244
|
+
const findIndex = this.results.pagination.findIndex(
|
|
245
|
+
(x) => x.key === this.results.staticCurrentPage
|
|
241
246
|
);
|
|
242
|
-
const last = this.pagination
|
|
243
|
-
if (this.pagination.
|
|
244
|
-
this.staticIsFirstPage
|
|
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
|
|
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
|
|
262
|
-
if (this.pagination.
|
|
266
|
+
this.results.staticIsLastPage = true;
|
|
267
|
+
if (this.results.pagination.length === 1) {
|
|
263
268
|
last = null;
|
|
264
|
-
this.staticCurrentPage
|
|
265
|
-
this.staticIsFirstPage
|
|
269
|
+
this.results.staticCurrentPage = "";
|
|
270
|
+
this.results.staticIsFirstPage = true;
|
|
266
271
|
} else {
|
|
267
|
-
last = this.pagination
|
|
268
|
-
this.staticCurrentPage
|
|
269
|
-
this.pagination
|
|
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
|
|
280
|
-
if (this.pagination.
|
|
281
|
-
this.staticIsFirstPage
|
|
284
|
+
this.results.staticIsLastPage = false;
|
|
285
|
+
if (this.results.pagination.length === 1) {
|
|
286
|
+
this.results.staticIsFirstPage = false;
|
|
282
287
|
}
|
|
283
288
|
}
|
|
284
|
-
this.data
|
|
285
|
-
this.staticCurrentPage
|
|
286
|
-
if (!this.staticIsLastPage
|
|
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.
|
|
293
|
+
const findItem = this.results.pagination.find(
|
|
289
294
|
(x) => x.key === results.next.id
|
|
290
295
|
);
|
|
291
296
|
if (!findItem) {
|
|
292
|
-
this.pagination.
|
|
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
|
|
312
|
-
this.staticIsFirstPage
|
|
313
|
-
this.staticCurrentPage
|
|
314
|
-
this.pagination
|
|
315
|
-
this.pagination
|
|
316
|
-
this.data
|
|
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.
|
|
325
|
-
this.
|
|
326
|
-
this.
|
|
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
|
-
|
|
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)) {
|