@data-weave/backend-firestore 0.4.27 → 0.6.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.
- package/dist/FirestoreAdminAdapter.d.ts +31 -0
- package/dist/FirestoreAdminAdapter.js +106 -0
- package/dist/FirestoreDataManager.d.ts +76 -0
- package/dist/FirestoreDataManager.js +243 -0
- package/{lib → dist}/FirestoreList.d.ts +3 -7
- package/dist/FirestoreList.js +95 -0
- package/{lib → dist}/FirestoreMetadata.d.ts +1 -0
- package/{lib → dist}/FirestoreMetadata.js +3 -3
- package/{lib → dist}/FirestoreReference.d.ts +3 -10
- package/dist/FirestoreReference.js +75 -0
- package/dist/errors.d.ts +29 -0
- package/dist/errors.js +38 -0
- package/{lib → dist}/firestoreTypes.d.ts +18 -11
- package/dist/firestoreTypes.js +29 -0
- package/{lib → dist}/index.d.ts +2 -0
- package/{lib → dist}/index.js +2 -0
- package/{lib → dist}/utils.d.ts +3 -2
- package/{lib → dist}/utils.js +49 -7
- package/package.json +56 -10
- package/lib/FirestoreDataManager.d.ts +0 -46
- package/lib/FirestoreDataManager.js +0 -227
- package/lib/FirestoreList.js +0 -96
- package/lib/FirestoreReference.js +0 -85
- package/lib/firestoreTypes.js +0 -127
- package/lib/version.d.ts +0 -2
- package/lib/version.js +0 -3
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type AggregateResult, type AggregateSpec, FieldValues, Firestore, FirestoreApp, FirestoreTypes } from './firestoreTypes';
|
|
2
|
+
export declare class FirestoreAdminAdapter extends Firestore {
|
|
3
|
+
readonly firestore: FirestoreApp;
|
|
4
|
+
readonly fieldValues: FieldValues;
|
|
5
|
+
app: FirestoreApp;
|
|
6
|
+
constructor(firestore: FirestoreApp, fieldValues: FieldValues);
|
|
7
|
+
collection(reference: any, path: string): any;
|
|
8
|
+
getDocs(reference: any): any;
|
|
9
|
+
getAggregateFromServer<Spec extends AggregateSpec>(query: any, spec: Spec): Promise<AggregateResult<Spec>>;
|
|
10
|
+
getDoc(reference: any, path?: string): any;
|
|
11
|
+
serverTimestamp(): FirestoreTypes.FieldValue;
|
|
12
|
+
increment(n: number): FirestoreTypes.FieldValue;
|
|
13
|
+
query(reference: any, filter: any): any;
|
|
14
|
+
where(field: string | FirestoreTypes.FieldPath, op: string, value: unknown): any;
|
|
15
|
+
limit(limit: number): {
|
|
16
|
+
type: string;
|
|
17
|
+
limit: number;
|
|
18
|
+
};
|
|
19
|
+
orderBy(orderBy: string, direction: FirestoreTypes.OrderByDirection): {
|
|
20
|
+
type: string;
|
|
21
|
+
field: string;
|
|
22
|
+
direction: FirestoreTypes.OrderByDirection;
|
|
23
|
+
};
|
|
24
|
+
setDoc(reference: any, data: any, options?: FirestoreTypes.SetOptions): any;
|
|
25
|
+
updateDoc(reference: any, data: any): any;
|
|
26
|
+
deleteDoc(reference: any): any;
|
|
27
|
+
doc(reference: any, path?: string): any;
|
|
28
|
+
onSnapshot(reference: any, onNext: (snapshot: any) => void, onError?: (error: Error) => void): any;
|
|
29
|
+
runTransaction<T>(firestore: FirestoreApp, transaction: (transaction: FirestoreTypes.Transaction) => Promise<T>, options?: FirestoreTypes.TransactionOptions): Promise<T>;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=FirestoreAdminAdapter.d.ts.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FirestoreAdminAdapter = void 0;
|
|
4
|
+
const firestore_1 = require("firebase-admin/firestore");
|
|
5
|
+
const firestoreTypes_1 = require("./firestoreTypes");
|
|
6
|
+
/*
|
|
7
|
+
* FirestoreAdminAdapter creates an interface between modular and namespaced
|
|
8
|
+
* firestore libraries.
|
|
9
|
+
* - admin sdk doesn't support modular imports
|
|
10
|
+
*/
|
|
11
|
+
// TODO: add types
|
|
12
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
13
|
+
class FirestoreAdminAdapter extends firestoreTypes_1.Firestore {
|
|
14
|
+
firestore;
|
|
15
|
+
fieldValues;
|
|
16
|
+
app;
|
|
17
|
+
constructor(firestore, fieldValues) {
|
|
18
|
+
super();
|
|
19
|
+
this.firestore = firestore;
|
|
20
|
+
this.fieldValues = fieldValues;
|
|
21
|
+
this.app = firestore;
|
|
22
|
+
this.fieldValues = fieldValues;
|
|
23
|
+
}
|
|
24
|
+
collection(reference, path) {
|
|
25
|
+
return reference.collection(path);
|
|
26
|
+
}
|
|
27
|
+
getDocs(reference) {
|
|
28
|
+
return reference.get();
|
|
29
|
+
}
|
|
30
|
+
async getAggregateFromServer(query, spec) {
|
|
31
|
+
const adminSpec = Object.fromEntries(Object.keys(spec).map(alias => {
|
|
32
|
+
const fieldSpec = spec[alias];
|
|
33
|
+
if (fieldSpec.type === 'count') {
|
|
34
|
+
return [alias, firestore_1.AggregateField.count()];
|
|
35
|
+
}
|
|
36
|
+
return [alias, firestore_1.AggregateField[fieldSpec.type](fieldSpec.field)];
|
|
37
|
+
}));
|
|
38
|
+
const snapshot = await query.aggregate(adminSpec).get();
|
|
39
|
+
return snapshot.data();
|
|
40
|
+
}
|
|
41
|
+
getDoc(reference, path) {
|
|
42
|
+
return reference.get(path);
|
|
43
|
+
}
|
|
44
|
+
serverTimestamp() {
|
|
45
|
+
return this.fieldValues.serverTimestamp();
|
|
46
|
+
}
|
|
47
|
+
increment(n) {
|
|
48
|
+
return this.fieldValues.increment(n);
|
|
49
|
+
}
|
|
50
|
+
query(reference, filter) {
|
|
51
|
+
if (filter.type === 'where') {
|
|
52
|
+
return reference.where(filter.field, filter.op, filter.value);
|
|
53
|
+
}
|
|
54
|
+
if (filter.type === 'orderBy') {
|
|
55
|
+
return reference.orderBy(filter.field, filter.direction);
|
|
56
|
+
}
|
|
57
|
+
if (filter.type === 'limit') {
|
|
58
|
+
return reference.limit(filter.limit);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
where(field, op, value) {
|
|
62
|
+
return {
|
|
63
|
+
type: 'where',
|
|
64
|
+
field,
|
|
65
|
+
op,
|
|
66
|
+
value,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
limit(limit) {
|
|
70
|
+
return {
|
|
71
|
+
type: 'limit',
|
|
72
|
+
limit,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
orderBy(orderBy, direction) {
|
|
76
|
+
return {
|
|
77
|
+
type: 'orderBy',
|
|
78
|
+
field: orderBy,
|
|
79
|
+
direction,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
setDoc(reference, data, options) {
|
|
83
|
+
return reference.set(data, options);
|
|
84
|
+
}
|
|
85
|
+
updateDoc(reference, data) {
|
|
86
|
+
return reference.update(data);
|
|
87
|
+
}
|
|
88
|
+
deleteDoc(reference) {
|
|
89
|
+
return reference.delete();
|
|
90
|
+
}
|
|
91
|
+
doc(reference, path) {
|
|
92
|
+
if (!path)
|
|
93
|
+
return reference.doc();
|
|
94
|
+
return reference.doc(path);
|
|
95
|
+
}
|
|
96
|
+
onSnapshot(reference, onNext, onError) {
|
|
97
|
+
return reference.onSnapshot(onNext, onError);
|
|
98
|
+
}
|
|
99
|
+
runTransaction(firestore, transaction, options) {
|
|
100
|
+
// firestore.runTransaction is Admin SDK specific
|
|
101
|
+
return firestore.runTransaction(transaction, options);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
exports.FirestoreAdminAdapter = FirestoreAdminAdapter;
|
|
105
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
106
|
+
//# sourceMappingURL=FirestoreAdminAdapter.js.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Cache, DataManager, IdentifiableReference, List, ListPaginationParams, LiveList, LiveReference, NumericKeys, WithMetadata, WithoutId } from '@data-weave/datamanager';
|
|
2
|
+
import { FirestoreSerializedMetadata } from './FirestoreMetadata';
|
|
3
|
+
import { FilterBy, FirebaseCreateOptions, Firestore, FirestoreDataConverter, FirestoreReadMode, FirestoreReadOptions, FirestoreTypes, FirestoreWriteOptions, OrderBy, WithFieldValue } from './firestoreTypes';
|
|
4
|
+
export type FirebaseDataManagerDeleteMode = 'soft' | 'hard';
|
|
5
|
+
export interface FirebaseDataManagerOptions {
|
|
6
|
+
readonly idResolver?: () => string;
|
|
7
|
+
readonly deleteMode: FirebaseDataManagerDeleteMode;
|
|
8
|
+
readonly readMode: FirestoreReadMode;
|
|
9
|
+
readonly preventOverwriteOnCreate: boolean;
|
|
10
|
+
readonly snapshotOptions?: FirestoreTypes.SnapshotOptions;
|
|
11
|
+
readonly listCache?: Cache;
|
|
12
|
+
readonly refCache?: Cache;
|
|
13
|
+
readonly disableCache?: boolean;
|
|
14
|
+
readonly ReferenceProxy?: <T>(reference: LiveReference<T>) => LiveReference<T>;
|
|
15
|
+
readonly ListProxy?: <T>(list: LiveList<T>) => LiveList<T>;
|
|
16
|
+
}
|
|
17
|
+
export interface QueryParams<T> {
|
|
18
|
+
readonly filters?: Array<FilterBy<T & FirestoreSerializedMetadata>>;
|
|
19
|
+
readonly orderBy?: Array<OrderBy<T & FirestoreSerializedMetadata>>;
|
|
20
|
+
}
|
|
21
|
+
export declare class FirestoreDataManager<T extends FirestoreTypes.DocumentData, SerializedT extends FirestoreTypes.DocumentData = T> implements DataManager<T> {
|
|
22
|
+
private readonly firestore;
|
|
23
|
+
private readonly collectionPath;
|
|
24
|
+
private readonly converter;
|
|
25
|
+
private readonly opts?;
|
|
26
|
+
private mergedConverter;
|
|
27
|
+
private collection;
|
|
28
|
+
private collectionQuery;
|
|
29
|
+
private managerOptions;
|
|
30
|
+
private refCache;
|
|
31
|
+
private listCache;
|
|
32
|
+
private referenceOptions;
|
|
33
|
+
constructor(firestore: Firestore, collectionPath: string, converter: FirestoreDataConverter<T, SerializedT>, opts?: Partial<FirebaseDataManagerOptions> | undefined);
|
|
34
|
+
private validateOptions;
|
|
35
|
+
read(id: string, options?: FirestoreReadOptions): Promise<WithMetadata<T> | undefined>;
|
|
36
|
+
create(data: WithFieldValue<WithoutId<T>>, options?: FirebaseCreateOptions): Promise<IdentifiableReference<WithMetadata<T>>>;
|
|
37
|
+
private _update;
|
|
38
|
+
update(id: string, data: WithoutId<Partial<WithFieldValue<T>>>, options?: FirestoreWriteOptions): Promise<void>;
|
|
39
|
+
upsert(id: string, data: WithFieldValue<WithoutId<T>>, options?: FirestoreWriteOptions): Promise<void>;
|
|
40
|
+
count(params?: QueryParams<SerializedT>): Promise<number>;
|
|
41
|
+
/**
|
|
42
|
+
* Sum the values of a field in the collection
|
|
43
|
+
*
|
|
44
|
+
* NOTE: `field` is resolved against Firestore (serialized) field names.
|
|
45
|
+
* If user model fields differ from serialized fields, this can target a different field than expected.
|
|
46
|
+
*/
|
|
47
|
+
sum(field: NumericKeys<T>, params?: QueryParams<SerializedT>): Promise<number>;
|
|
48
|
+
/**
|
|
49
|
+
* Calculate the average value of a field in the collection.
|
|
50
|
+
*
|
|
51
|
+
* NOTE: `field` is resolved against Firestore (serialized) field names.
|
|
52
|
+
* If user model fields differ from serialized fields, this can target a different field than expected.
|
|
53
|
+
*/
|
|
54
|
+
average(field: NumericKeys<T>, params?: QueryParams<SerializedT>): Promise<number | null>;
|
|
55
|
+
/**
|
|
56
|
+
* Read the minimum value for a field in the collection.
|
|
57
|
+
*
|
|
58
|
+
* NOTE: `field` is resolved against Firestore (serialized) field names.
|
|
59
|
+
* If user model fields differ from serialized fields, this can target a different field than expected.
|
|
60
|
+
*/
|
|
61
|
+
min<K extends string & keyof T>(field: K, params?: QueryParams<SerializedT>): Promise<T[K] | null>;
|
|
62
|
+
/**
|
|
63
|
+
* Read the maximum value for a field in the collection.
|
|
64
|
+
*
|
|
65
|
+
* NOTE: `field` is resolved against Firestore (serialized) field names.
|
|
66
|
+
* If user model fields differ from serialized fields, this can target a different field than expected.
|
|
67
|
+
*/
|
|
68
|
+
max<K extends string & keyof T>(field: K, params?: QueryParams<SerializedT>): Promise<T[K] | null>;
|
|
69
|
+
exists(id: string): Promise<boolean>;
|
|
70
|
+
delete(id: string, options?: FirestoreWriteOptions): Promise<void>;
|
|
71
|
+
getRef(id: string): IdentifiableReference<WithMetadata<T>>;
|
|
72
|
+
private _getFilteredQuery;
|
|
73
|
+
getList(params?: QueryParams<SerializedT> & ListPaginationParams): List<WithMetadata<T>>;
|
|
74
|
+
private preventOverwriteOnCreate;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=FirestoreDataManager.d.ts.map
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FirestoreDataManager = void 0;
|
|
4
|
+
const datamanager_1 = require("@data-weave/datamanager");
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
const FirestoreList_1 = require("./FirestoreList");
|
|
7
|
+
const FirestoreMetadata_1 = require("./FirestoreMetadata");
|
|
8
|
+
const FirestoreReference_1 = require("./FirestoreReference");
|
|
9
|
+
const utils_1 = require("./utils");
|
|
10
|
+
const defaultFirebaseDataManagerOptions = {
|
|
11
|
+
deleteMode: 'soft',
|
|
12
|
+
preventOverwriteOnCreate: true,
|
|
13
|
+
readMode: 'static',
|
|
14
|
+
};
|
|
15
|
+
class FirestoreDataManager {
|
|
16
|
+
firestore;
|
|
17
|
+
collectionPath;
|
|
18
|
+
converter;
|
|
19
|
+
opts;
|
|
20
|
+
mergedConverter;
|
|
21
|
+
collection;
|
|
22
|
+
collectionQuery;
|
|
23
|
+
managerOptions;
|
|
24
|
+
refCache;
|
|
25
|
+
listCache;
|
|
26
|
+
referenceOptions;
|
|
27
|
+
constructor(firestore, collectionPath, converter, opts) {
|
|
28
|
+
this.firestore = firestore;
|
|
29
|
+
this.collectionPath = collectionPath;
|
|
30
|
+
this.converter = converter;
|
|
31
|
+
this.opts = opts;
|
|
32
|
+
// @ts-expect-error - Force merge FirestoreDataConverter and InternalFirestoreDataConverter
|
|
33
|
+
this.mergedConverter = new utils_1.MergeConverters(this.converter, new FirestoreMetadata_1.FirestoreMetadataConverter());
|
|
34
|
+
this.managerOptions = this.validateOptions({ ...defaultFirebaseDataManagerOptions, ...this.opts });
|
|
35
|
+
this.refCache = this.managerOptions.refCache || new datamanager_1.MapCache(100);
|
|
36
|
+
this.listCache = this.managerOptions.listCache || new datamanager_1.MapCache(100);
|
|
37
|
+
this.collection = this.firestore
|
|
38
|
+
.collection(this.firestore.app, this.collectionPath)
|
|
39
|
+
.withConverter(this.mergedConverter);
|
|
40
|
+
this.collectionQuery =
|
|
41
|
+
this.managerOptions.deleteMode === 'soft'
|
|
42
|
+
? (0, FirestoreMetadata_1.queryNotDeleted)(this.collection, this.firestore.query, this.firestore.where)
|
|
43
|
+
: this.collection;
|
|
44
|
+
this.referenceOptions = {
|
|
45
|
+
readMode: this.managerOptions.readMode,
|
|
46
|
+
snapshotOptions: this.managerOptions.snapshotOptions,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
validateOptions(options) {
|
|
50
|
+
return options;
|
|
51
|
+
}
|
|
52
|
+
async read(id, options) {
|
|
53
|
+
const ref = this.getRef(id);
|
|
54
|
+
if (options?.transaction) {
|
|
55
|
+
const snapshot = await options.transaction.get(this.firestore.doc(this.collection, id));
|
|
56
|
+
if (!(0, utils_1.checkIfReferenceExists)(snapshot))
|
|
57
|
+
return undefined;
|
|
58
|
+
return snapshot.data(this.referenceOptions.snapshotOptions);
|
|
59
|
+
}
|
|
60
|
+
return await ref.resolve();
|
|
61
|
+
}
|
|
62
|
+
async create(data, options) {
|
|
63
|
+
let id = undefined;
|
|
64
|
+
if (options?.id) {
|
|
65
|
+
id = options?.id;
|
|
66
|
+
}
|
|
67
|
+
else if (this.managerOptions?.idResolver) {
|
|
68
|
+
id = this.managerOptions.idResolver();
|
|
69
|
+
}
|
|
70
|
+
let docRef;
|
|
71
|
+
let docExists = false;
|
|
72
|
+
if (id) {
|
|
73
|
+
docRef = this.firestore.doc(this.collection, id);
|
|
74
|
+
docExists = await this.preventOverwriteOnCreate(docRef, options);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
docRef = this.firestore.doc(this.collection);
|
|
78
|
+
}
|
|
79
|
+
const docDataWithMetadata = {
|
|
80
|
+
...data,
|
|
81
|
+
[FirestoreMetadata_1.FIRESTORE_KEYS.CREATED_AT]: docExists ? undefined : this.firestore.serverTimestamp(),
|
|
82
|
+
[FirestoreMetadata_1.FIRESTORE_KEYS.UPDATED_AT]: this.firestore.serverTimestamp(),
|
|
83
|
+
[FirestoreMetadata_1.FIRESTORE_KEYS.DELETED]: false,
|
|
84
|
+
};
|
|
85
|
+
const firebaseOptions = { merge: options?.merge };
|
|
86
|
+
if (options?.transaction) {
|
|
87
|
+
options?.transaction.set(docRef, docDataWithMetadata, firebaseOptions);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
await this.firestore.setDoc(docRef, docDataWithMetadata, firebaseOptions);
|
|
91
|
+
}
|
|
92
|
+
return this.getRef(docRef.id);
|
|
93
|
+
}
|
|
94
|
+
async _update(id, data, options) {
|
|
95
|
+
const extendedData = {
|
|
96
|
+
...data,
|
|
97
|
+
[FirestoreMetadata_1.FIRESTORE_KEYS.UPDATED_AT]: this.firestore.serverTimestamp(),
|
|
98
|
+
};
|
|
99
|
+
// Firestore update method doesn't call converter like setDoc does, so we need to serialize the data manually.
|
|
100
|
+
const serializedData = this.mergedConverter.toFirestore(extendedData);
|
|
101
|
+
const ref = this.firestore.doc(this.collection, id);
|
|
102
|
+
if (options?.transaction) {
|
|
103
|
+
return options.transaction.update(ref, serializedData);
|
|
104
|
+
}
|
|
105
|
+
return this.firestore.updateDoc(ref, serializedData);
|
|
106
|
+
}
|
|
107
|
+
async update(id, data, options) {
|
|
108
|
+
await this._update(id, data, options);
|
|
109
|
+
}
|
|
110
|
+
async upsert(id, data, options) {
|
|
111
|
+
await this.create(data, { ...options, id, merge: true });
|
|
112
|
+
}
|
|
113
|
+
async count(params) {
|
|
114
|
+
const compoundQuery = this._getFilteredQuery(params);
|
|
115
|
+
const result = await this.firestore.getAggregateFromServer(compoundQuery, {
|
|
116
|
+
result: { type: 'count' },
|
|
117
|
+
});
|
|
118
|
+
return result.result ?? 0;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Sum the values of a field in the collection
|
|
122
|
+
*
|
|
123
|
+
* NOTE: `field` is resolved against Firestore (serialized) field names.
|
|
124
|
+
* If user model fields differ from serialized fields, this can target a different field than expected.
|
|
125
|
+
*/
|
|
126
|
+
async sum(field, params) {
|
|
127
|
+
const compoundQuery = this._getFilteredQuery(params);
|
|
128
|
+
const result = await this.firestore.getAggregateFromServer(compoundQuery, {
|
|
129
|
+
result: { type: 'sum', field },
|
|
130
|
+
});
|
|
131
|
+
return result.result ?? 0;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Calculate the average value of a field in the collection.
|
|
135
|
+
*
|
|
136
|
+
* NOTE: `field` is resolved against Firestore (serialized) field names.
|
|
137
|
+
* If user model fields differ from serialized fields, this can target a different field than expected.
|
|
138
|
+
*/
|
|
139
|
+
async average(field, params) {
|
|
140
|
+
const compoundQuery = this._getFilteredQuery(params);
|
|
141
|
+
const result = await this.firestore.getAggregateFromServer(compoundQuery, {
|
|
142
|
+
result: { type: 'average', field },
|
|
143
|
+
});
|
|
144
|
+
return result.result ?? null;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Read the minimum value for a field in the collection.
|
|
148
|
+
*
|
|
149
|
+
* NOTE: `field` is resolved against Firestore (serialized) field names.
|
|
150
|
+
* If user model fields differ from serialized fields, this can target a different field than expected.
|
|
151
|
+
*/
|
|
152
|
+
async min(field, params) {
|
|
153
|
+
const compoundQuery = this._getFilteredQuery(params);
|
|
154
|
+
const limitedQuery = this.firestore.query(this.firestore.query(compoundQuery, this.firestore.orderBy(field, 'asc')), this.firestore.limit(1));
|
|
155
|
+
const snapshot = await this.firestore.getDocs(limitedQuery);
|
|
156
|
+
if (snapshot.empty)
|
|
157
|
+
return null;
|
|
158
|
+
return snapshot.docs[0].get(field) ?? null;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Read the maximum value for a field in the collection.
|
|
162
|
+
*
|
|
163
|
+
* NOTE: `field` is resolved against Firestore (serialized) field names.
|
|
164
|
+
* If user model fields differ from serialized fields, this can target a different field than expected.
|
|
165
|
+
*/
|
|
166
|
+
async max(field, params) {
|
|
167
|
+
const compoundQuery = this._getFilteredQuery(params);
|
|
168
|
+
const limitedQuery = this.firestore.query(this.firestore.query(compoundQuery, this.firestore.orderBy(field, 'desc')), this.firestore.limit(1));
|
|
169
|
+
const snapshot = await this.firestore.getDocs(limitedQuery);
|
|
170
|
+
if (snapshot.empty)
|
|
171
|
+
return null;
|
|
172
|
+
return snapshot.docs[0].get(field) ?? null;
|
|
173
|
+
}
|
|
174
|
+
async exists(id) {
|
|
175
|
+
const ref = this.firestore.doc(this.collection, id);
|
|
176
|
+
const snapshot = await this.firestore.getDoc(ref);
|
|
177
|
+
return (0, utils_1.checkIfReferenceExists)(snapshot);
|
|
178
|
+
}
|
|
179
|
+
async delete(id, options) {
|
|
180
|
+
if (this.managerOptions.deleteMode === 'soft') {
|
|
181
|
+
await this._update(id, {
|
|
182
|
+
...{},
|
|
183
|
+
[FirestoreMetadata_1.FIRESTORE_KEYS.DELETED]: true,
|
|
184
|
+
}, options);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (options?.transaction) {
|
|
188
|
+
options.transaction.delete(this.firestore.doc(this.collection, id));
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
await this.firestore.deleteDoc(this.firestore.doc(this.collection, id));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
getRef(id) {
|
|
195
|
+
if (this.refCache.has(id) && !this.managerOptions.disableCache) {
|
|
196
|
+
return this.refCache.get(id);
|
|
197
|
+
}
|
|
198
|
+
const newRef = new FirestoreReference_1.FirestoreReference(this.firestore, this.firestore.doc(this.collection, id), this.referenceOptions);
|
|
199
|
+
const ref = this.managerOptions.ReferenceProxy ? this.managerOptions.ReferenceProxy(newRef) : newRef;
|
|
200
|
+
if (!this.managerOptions.disableCache) {
|
|
201
|
+
this.refCache.set(id, ref);
|
|
202
|
+
}
|
|
203
|
+
return ref;
|
|
204
|
+
}
|
|
205
|
+
_getFilteredQuery(params) {
|
|
206
|
+
let compoundQuery = this.collectionQuery;
|
|
207
|
+
params?.filters?.forEach(filter => {
|
|
208
|
+
compoundQuery = this.firestore.query(compoundQuery, this.firestore.where(filter[0], filter[1], filter[2]));
|
|
209
|
+
});
|
|
210
|
+
params?.orderBy?.forEach(orderBy => {
|
|
211
|
+
compoundQuery = this.firestore.query(compoundQuery, this.firestore.orderBy(orderBy[0], orderBy[1]));
|
|
212
|
+
});
|
|
213
|
+
return compoundQuery;
|
|
214
|
+
}
|
|
215
|
+
getList(params) {
|
|
216
|
+
const compoundQuery = this._getFilteredQuery(params);
|
|
217
|
+
const key = JSON.stringify(params || {});
|
|
218
|
+
if (this.listCache.has(key) && !this.managerOptions.disableCache) {
|
|
219
|
+
return this.listCache.get(key);
|
|
220
|
+
}
|
|
221
|
+
const newList = new FirestoreList_1.FirestoreList(this.firestore, compoundQuery, {
|
|
222
|
+
readMode: this.managerOptions.readMode,
|
|
223
|
+
...params,
|
|
224
|
+
});
|
|
225
|
+
const list = this.managerOptions.ListProxy ? this.managerOptions.ListProxy(newList) : newList;
|
|
226
|
+
if (!this.managerOptions.disableCache) {
|
|
227
|
+
this.listCache.set(key, list);
|
|
228
|
+
}
|
|
229
|
+
return list;
|
|
230
|
+
}
|
|
231
|
+
async preventOverwriteOnCreate(docRef, createOptions) {
|
|
232
|
+
if (!this.managerOptions.preventOverwriteOnCreate)
|
|
233
|
+
return false;
|
|
234
|
+
const doc = await this.firestore.getDoc(docRef);
|
|
235
|
+
const docExists = (0, utils_1.checkIfReferenceExists)(doc);
|
|
236
|
+
if (docExists && createOptions?.merge !== true) {
|
|
237
|
+
throw new errors_1.FirestoreDataManagerError(`Cannot create document at "${doc.ref.path}": document already exists. Use 'merge: true' or disable 'preventOverwriteOnCreate' to allow overwriting.`);
|
|
238
|
+
}
|
|
239
|
+
return docExists;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
exports.FirestoreDataManager = FirestoreDataManager;
|
|
243
|
+
//# sourceMappingURL=FirestoreDataManager.js.map
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import { ListPaginationParams, LiveList, LiveListOptions } from '@data-weave/datamanager';
|
|
2
2
|
import { DocumentData, Firestore, FirestoreReadMode, FirestoreTypes } from './firestoreTypes';
|
|
3
|
-
export
|
|
4
|
-
query: FirestoreTypes.Query<unknown>;
|
|
5
|
-
readMode?: FirestoreReadMode;
|
|
6
|
-
type: 'list';
|
|
7
|
-
}
|
|
3
|
+
export type { FirestoreListContext } from './errors';
|
|
8
4
|
export interface FirestoreListOptions<T> extends LiveListOptions<T> {
|
|
9
5
|
readMode?: FirestoreReadMode;
|
|
10
|
-
errorInterceptor?: (error: unknown, ctx: FirestoreListContext) => void;
|
|
11
6
|
}
|
|
12
7
|
export declare class FirestoreList<T extends DocumentData, S extends DocumentData> extends LiveList<T> {
|
|
13
8
|
private readonly firestore;
|
|
@@ -18,6 +13,7 @@ export declare class FirestoreList<T extends DocumentData, S extends DocumentDat
|
|
|
18
13
|
resolve(): Promise<T[]>;
|
|
19
14
|
protected handleInitialDataChange(values: FirestoreTypes.QueryDocumentSnapshot<T, S>[]): void;
|
|
20
15
|
protected handleSubsequentDataChanges(changes: FirestoreTypes.DocumentChange<T, S>[]): void;
|
|
21
|
-
|
|
16
|
+
unsubscribe(): void;
|
|
22
17
|
protected onError(error: unknown): void;
|
|
23
18
|
}
|
|
19
|
+
//# sourceMappingURL=FirestoreList.d.ts.map
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FirestoreList = void 0;
|
|
4
|
+
const datamanager_1 = require("@data-weave/datamanager");
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
class FirestoreList extends datamanager_1.LiveList {
|
|
7
|
+
firestore;
|
|
8
|
+
query;
|
|
9
|
+
options;
|
|
10
|
+
unsubscribeFromSnapshot;
|
|
11
|
+
constructor(firestore, query, options) {
|
|
12
|
+
super(options);
|
|
13
|
+
this.firestore = firestore;
|
|
14
|
+
this.query = query;
|
|
15
|
+
this.options = options;
|
|
16
|
+
}
|
|
17
|
+
async resolve() {
|
|
18
|
+
if (this.options?.readMode === 'realtime') {
|
|
19
|
+
let initialLoad = true;
|
|
20
|
+
return new Promise(resolve => {
|
|
21
|
+
// Unsubscribe from any existing snapshot listener
|
|
22
|
+
if (this.unsubscribeFromSnapshot) {
|
|
23
|
+
this.unsubscribeFromSnapshot();
|
|
24
|
+
}
|
|
25
|
+
this.unsubscribeFromSnapshot = this.firestore.onSnapshot(this.query, querySnapshot => {
|
|
26
|
+
try {
|
|
27
|
+
if (initialLoad) {
|
|
28
|
+
initialLoad = false;
|
|
29
|
+
this.handleInitialDataChange(querySnapshot.docs);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// handle every change as inital dataload
|
|
33
|
+
this.handleInitialDataChange(querySnapshot.docs);
|
|
34
|
+
// this.handleSubsequentDataChanges(querySnapshot.docChanges())
|
|
35
|
+
}
|
|
36
|
+
// TODO: When calling ".resolve()" with realtime listener,
|
|
37
|
+
// the snapshot data might be stale from cache.
|
|
38
|
+
resolve(this.values);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
this.onError(error);
|
|
42
|
+
resolve(this.values);
|
|
43
|
+
}
|
|
44
|
+
}, error => {
|
|
45
|
+
this.onError(error);
|
|
46
|
+
resolve(this.values);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
try {
|
|
52
|
+
const snapshot = await this.firestore.getDocs(this.query);
|
|
53
|
+
this.handleInitialDataChange(snapshot.docs);
|
|
54
|
+
return this.values;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
this.onError(error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return this.values;
|
|
61
|
+
}
|
|
62
|
+
handleInitialDataChange(values) {
|
|
63
|
+
const newValues = values.map(v => v.data());
|
|
64
|
+
this.onUpdateAll(newValues);
|
|
65
|
+
}
|
|
66
|
+
handleSubsequentDataChanges(changes) {
|
|
67
|
+
changes.forEach(change => {
|
|
68
|
+
if (change.type === 'added') {
|
|
69
|
+
this.onAddAtIndex(change.newIndex, change.doc.data());
|
|
70
|
+
}
|
|
71
|
+
else if (change.type === 'modified') {
|
|
72
|
+
this.onUpdateAtIndex(change.newIndex, change.doc.data());
|
|
73
|
+
}
|
|
74
|
+
else if (change.type === 'removed') {
|
|
75
|
+
this.onRemoveAtIndex(change.oldIndex);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
// TODO: handle onUpdate in the parent class - make sure it's only called once after all changes are processed
|
|
79
|
+
this.onUpdate();
|
|
80
|
+
}
|
|
81
|
+
unsubscribe() {
|
|
82
|
+
this.unsubscribeFromSnapshot?.();
|
|
83
|
+
this.setStale();
|
|
84
|
+
}
|
|
85
|
+
onError(error) {
|
|
86
|
+
const wrapped = new errors_1.FirestoreListError(error, {
|
|
87
|
+
query: this.query,
|
|
88
|
+
readMode: this.options?.readMode,
|
|
89
|
+
type: 'list',
|
|
90
|
+
});
|
|
91
|
+
super.onError(wrapped);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
exports.FirestoreList = FirestoreList;
|
|
95
|
+
//# sourceMappingURL=FirestoreList.js.map
|
|
@@ -30,3 +30,4 @@ export declare class FirestoreMetadataConverter implements InternalFirestoreData
|
|
|
30
30
|
}
|
|
31
31
|
export declare const queryNotDeleted: <T extends DocumentData, SerializedT extends DocumentData>(query: FirestoreTypes.Query<T, SerializedT>, firestoreQuery: FirestoreQuery<T, SerializedT>, firestoreWhere: FirestoreWhere) => FirestoreTypes.Query<T, SerializedT>;
|
|
32
32
|
export {};
|
|
33
|
+
//# sourceMappingURL=FirestoreMetadata.d.ts.map
|
|
@@ -23,13 +23,12 @@ class FirestoreMetadataConverter {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
fromFirestore(snapshot, options) {
|
|
26
|
-
var _a, _b, _c, _d;
|
|
27
26
|
const data = snapshot.data(options);
|
|
28
27
|
return {
|
|
29
28
|
id: snapshot.ref.id,
|
|
30
29
|
// TODO: add warning in case data type is not as expected
|
|
31
|
-
createdAt:
|
|
32
|
-
updatedAt:
|
|
30
|
+
createdAt: data[FIRESTORE_INTERAL_KEYS.CREATED_AT]?.toDate() ?? new Date(),
|
|
31
|
+
updatedAt: data[FIRESTORE_INTERAL_KEYS.UPDATED_AT]?.toDate() ?? new Date(),
|
|
33
32
|
deleted: data[FIRESTORE_INTERAL_KEYS.DELETED],
|
|
34
33
|
};
|
|
35
34
|
}
|
|
@@ -37,3 +36,4 @@ class FirestoreMetadataConverter {
|
|
|
37
36
|
exports.FirestoreMetadataConverter = FirestoreMetadataConverter;
|
|
38
37
|
const queryNotDeleted = (query, firestoreQuery, firestoreWhere) => firestoreQuery(query, firestoreWhere(FIRESTORE_INTERAL_KEYS.DELETED, '==', false));
|
|
39
38
|
exports.queryNotDeleted = queryNotDeleted;
|
|
39
|
+
//# sourceMappingURL=FirestoreMetadata.js.map
|
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
import { LiveReference, LiveReferenceOptions } from '@data-weave/datamanager';
|
|
2
2
|
import { DocumentData, Firestore, FirestoreReadMode, FirestoreTypes } from './firestoreTypes';
|
|
3
|
-
export
|
|
4
|
-
path: string;
|
|
5
|
-
id: string;
|
|
6
|
-
readMode?: FirestoreReadMode;
|
|
7
|
-
snapshotOptions?: FirestoreTypes.SnapshotOptions;
|
|
8
|
-
type: 'reference';
|
|
9
|
-
}
|
|
3
|
+
export type { FirestoreReferenceContext } from './errors';
|
|
10
4
|
export interface FirestoreReferenceOptions<T> extends LiveReferenceOptions<T> {
|
|
11
5
|
readMode?: FirestoreReadMode;
|
|
12
6
|
snapshotOptions?: FirestoreTypes.SnapshotOptions;
|
|
13
|
-
errorInterceptor?: (error: unknown, ctx: FirestoreReferenceContext) => void;
|
|
14
7
|
}
|
|
15
8
|
export declare class FirestoreReference<T extends DocumentData, S extends DocumentData> extends LiveReference<T> {
|
|
16
9
|
private readonly firestore;
|
|
@@ -19,9 +12,9 @@ export declare class FirestoreReference<T extends DocumentData, S extends Docume
|
|
|
19
12
|
private unsubscribeFromSnapshot;
|
|
20
13
|
constructor(firestore: Firestore, docRef: FirestoreTypes.DocumentReference<T, S>, options: FirestoreReferenceOptions<T>);
|
|
21
14
|
resolve(): Promise<T | undefined>;
|
|
22
|
-
get id(): string;
|
|
23
15
|
get path(): string;
|
|
24
16
|
protected onError(error: unknown): void;
|
|
25
|
-
|
|
17
|
+
unsubscribe(): void;
|
|
26
18
|
private parseDocumentSnapshot;
|
|
27
19
|
}
|
|
20
|
+
//# sourceMappingURL=FirestoreReference.d.ts.map
|