@sneat/api 0.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.
@@ -0,0 +1,157 @@
1
+ import { Injector, runInInjectionContext } from '@angular/core';
2
+ import {
3
+ doc,
4
+ getDoc,
5
+ CollectionReference,
6
+ DocumentReference,
7
+ DocumentSnapshot,
8
+ query,
9
+ where,
10
+ onSnapshot,
11
+ limit,
12
+ } from '@angular/fire/firestore';
13
+ import { IIdAndOptionalBriefAndOptionalDbo } from '@sneat/core';
14
+ import { QuerySnapshot, QueryOrderByConstraint } from 'firebase/firestore';
15
+ import { WhereFilterOp } from '@firebase/firestore-types';
16
+ import { INavContext } from '@sneat/core';
17
+ import { from, map, Observable, Subject } from 'rxjs';
18
+
19
+ export interface IFilter {
20
+ readonly field: string;
21
+ readonly operator: WhereFilterOp;
22
+ readonly value: unknown;
23
+ }
24
+
25
+ export interface IQueryArgs {
26
+ readonly limit?: number;
27
+ readonly filter?: readonly IFilter[];
28
+ readonly orderBy?: readonly QueryOrderByConstraint[];
29
+ }
30
+
31
+ export class SneatFirestoreService<Brief, Dbo extends Brief> {
32
+ constructor(
33
+ private readonly injector: Injector,
34
+ // private readonly afs: AngularFirestore,
35
+ private readonly dto2brief: (id: string, dto: Dbo) => Brief = (
36
+ id: string,
37
+ dto: Dbo,
38
+ ) => ({
39
+ ...(dto as unknown as Brief),
40
+ id,
41
+ }),
42
+ ) {
43
+ if (!dto2brief) {
44
+ throw new Error('dto2brief is required');
45
+ }
46
+ }
47
+
48
+ watchByID<Dbo2 extends Dbo>(
49
+ collection: CollectionReference<Dbo2>,
50
+ id: string,
51
+ ): Observable<IIdAndOptionalBriefAndOptionalDbo<Brief, Dbo2>> {
52
+ const docRef = runInInjectionContext(this.injector, () =>
53
+ doc(collection, id),
54
+ );
55
+ return this.watchByDocRef(docRef);
56
+ }
57
+
58
+ watchByDocRef<Dbo2 extends Dbo>(
59
+ docRef: DocumentReference<Dbo2>,
60
+ ): Observable<IIdAndOptionalBriefAndOptionalDbo<Brief, Dbo2>> {
61
+ return runInInjectionContext(this.injector, () => {
62
+ const subj = new Subject<DocumentSnapshot<Dbo2>>();
63
+ // const snapshots = docSnapshots<Dbo2>(docRef);
64
+ onSnapshot(
65
+ docRef,
66
+ (snapshot) => subj.next(snapshot),
67
+ (err) => subj.error(err),
68
+ () => subj.complete(),
69
+ );
70
+ // const snapshots = from(getDoc<Dbo2>(docRef));
71
+ return subj.asObservable().pipe(
72
+ // tap((snapshot) =>
73
+ // console.log(
74
+ // `SneatFirestoreService.watchByDocRef(${docRef.path}): snapshot:`,
75
+ // snapshot,
76
+ // ),
77
+ // ),
78
+ map((changes) =>
79
+ docSnapshotToDto<Brief, Dbo2>(docRef.id, this.dto2brief, changes),
80
+ ),
81
+ );
82
+ });
83
+ }
84
+
85
+ getByDocRef<Dbo2 extends Dbo>(
86
+ docRef: DocumentReference<Dbo2>,
87
+ ): Observable<INavContext<Brief, Dbo2>> {
88
+ return from(getDoc(docRef)).pipe(
89
+ map((changes) =>
90
+ docSnapshotToDto<Brief, Dbo2>(docRef.id, this.dto2brief, changes),
91
+ ),
92
+ );
93
+ }
94
+
95
+ watchSnapshotsByFilter<Dbo2 extends Dbo>(
96
+ collectionRef: CollectionReference<Dbo2>,
97
+ queryArgs?: IQueryArgs,
98
+ ): Observable<QuerySnapshot<Dbo2>> {
99
+ const operator = (f: IFilter) =>
100
+ f.field.endsWith('IDs') ? 'array-contains' : f.operator;
101
+ const q = query(
102
+ collectionRef,
103
+ ...(queryArgs?.filter || []).map((f) =>
104
+ where(f.field, operator(f), f.value),
105
+ ),
106
+ ...(queryArgs?.orderBy || []),
107
+ ...(queryArgs?.limit ? [limit(queryArgs.limit)] : []),
108
+ );
109
+ const subj = new Subject<QuerySnapshot<Dbo2>>();
110
+ onSnapshot(q, subj);
111
+ return subj;
112
+ }
113
+
114
+ watchByFilter<Dbo2 extends Dbo>(
115
+ collectionRef: CollectionReference<Dbo2>,
116
+ queryArgs?: IQueryArgs,
117
+ ): Observable<INavContext<Brief, Dbo2>[]> {
118
+ return this.watchSnapshotsByFilter(collectionRef, queryArgs).pipe(
119
+ map((querySnapshot) => {
120
+ return querySnapshot.docs.map(this.docSnapshotToContext.bind(this));
121
+ }),
122
+ );
123
+ }
124
+
125
+ docSnapshotsToContext<Dbo2 extends Dbo>(
126
+ docSnapshots: DocumentSnapshot<Dbo2>[],
127
+ ): INavContext<Brief, Dbo2>[] {
128
+ return docSnapshots.map((doc) => {
129
+ return this.docSnapshotToContext(doc);
130
+ });
131
+ }
132
+
133
+ docSnapshotToContext<Dbo2 extends Dbo>(
134
+ doc: DocumentSnapshot<Dbo2>,
135
+ ): INavContext<Brief, Dbo2> {
136
+ const { id } = doc;
137
+ const dto: Dbo2 | undefined = doc.data();
138
+ const brief = dto && this.dto2brief(id, dto);
139
+ return {
140
+ id,
141
+ dto,
142
+ brief,
143
+ } as unknown as INavContext<Brief, Dbo2>; // TODO: try to remove this cast
144
+ }
145
+ }
146
+
147
+ export function docSnapshotToDto<Brief, Dbo extends Brief>(
148
+ id: string,
149
+ dto2brief: (id: string, dto: Dbo) => Brief,
150
+ docSnapshot: DocumentSnapshot<Dbo>,
151
+ ): INavContext<Brief, Dbo> {
152
+ if (!docSnapshot.exists) {
153
+ return { id, brief: null, dbo: null };
154
+ }
155
+ const dto: Dbo | undefined = docSnapshot.data();
156
+ return { id, dbo: dto, brief: dto ? dto2brief(id, dto) : undefined };
157
+ }
@@ -0,0 +1,3 @@
1
+ import { setupTestEnvironment } from '@sneat/core/testing';
2
+
3
+ setupTestEnvironment();
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "../../tsconfig.angular.json",
3
+ "files": [],
4
+ "include": [],
5
+ "references": [
6
+ {
7
+ "path": "./tsconfig.lib.json"
8
+ },
9
+ {
10
+ "path": "./tsconfig.spec.json"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "extends": "../../tsconfig.lib.base.json",
3
+ "exclude": [
4
+ "vite.config.ts",
5
+ "vite.config.mts",
6
+ "vitest.config.ts",
7
+ "vitest.config.mts",
8
+ "src/**/*.test.ts",
9
+ "src/**/*.spec.ts",
10
+ "src/**/*.test.tsx",
11
+ "src/**/*.spec.tsx",
12
+ "src/**/*.test.js",
13
+ "src/**/*.spec.js",
14
+ "src/**/*.test.jsx",
15
+ "src/**/*.spec.jsx",
16
+ "src/test-setup.ts",
17
+ "src/lib/testing/**/*"
18
+ ]
19
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "./tsconfig.lib.json",
3
+ "compilerOptions": {
4
+ "declarationMap": false
5
+ },
6
+ "angularCompilerOptions": {}
7
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "../../dist/out-tsc",
5
+ "types": [
6
+ "vitest/globals",
7
+ "vitest/importMeta",
8
+ "vite/client",
9
+ "node",
10
+ "vitest"
11
+ ]
12
+ },
13
+ "include": [
14
+ "vite.config.ts",
15
+ "vite.config.mts",
16
+ "vitest.config.ts",
17
+ "vitest.config.mts",
18
+ "src/**/*.test.ts",
19
+ "src/**/*.spec.ts",
20
+ "src/**/*.test.tsx",
21
+ "src/**/*.spec.tsx",
22
+ "src/**/*.test.js",
23
+ "src/**/*.spec.js",
24
+ "src/**/*.test.jsx",
25
+ "src/**/*.spec.jsx",
26
+ "src/**/*.d.ts"
27
+ ],
28
+ "files": [
29
+ "src/test-setup.ts"
30
+ ]
31
+ }
@@ -0,0 +1,10 @@
1
+ /// <reference types='vitest' />
2
+ import { defineConfig } from 'vitest/config';
3
+ import { createBaseViteConfig } from '../../vite.config.base';
4
+
5
+ export default defineConfig(() =>
6
+ createBaseViteConfig({
7
+ dirname: __dirname,
8
+ name: 'api',
9
+ }),
10
+ );