@data-weave/backend-firestore 0.7.0 → 0.7.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.
@@ -24,6 +24,7 @@ export declare class FirestoreDataManager<T extends FirestoreTypes.DocumentData,
24
24
  private readonly converter;
25
25
  private readonly opts?;
26
26
  private mergedConverter;
27
+ private metadataConverter;
27
28
  private collection;
28
29
  private collectionQuery;
29
30
  private managerOptions;
@@ -35,6 +36,15 @@ export declare class FirestoreDataManager<T extends FirestoreTypes.DocumentData,
35
36
  read(id: string, options?: FirestoreReadOptions): Promise<WithMetadata<T> | undefined>;
36
37
  create(data: WithFieldValue<WithoutId<T>>, options?: FirebaseCreateOptions): Promise<IdentifiableReference<WithMetadata<T>>>;
37
38
  private _update;
39
+ /**
40
+ * Update only the internal metadata fields of a document.
41
+ *
42
+ * Unlike {@link _update}, this bypasses the user-provided converter so it never re-serializes
43
+ * (and therefore never clobbers) user data. This matters because the user converter rebuilds the
44
+ * full document shape, which turns absent fields into `undefined` and can overwrite existing map
45
+ * fields with empty objects when Firestore strips the undefined values.
46
+ */
47
+ private _updateMetadata;
38
48
  update(id: string, data: WithoutId<Partial<WithFieldValue<T>>>, options?: FirestoreWriteOptions): Promise<void>;
39
49
  upsert(id: string, data: WithFieldValue<WithoutId<T>>, options?: FirestoreWriteOptions): Promise<void>;
40
50
  count(params?: QueryParams<SerializedT>): Promise<number>;
@@ -18,6 +18,7 @@ class FirestoreDataManager {
18
18
  converter;
19
19
  opts;
20
20
  mergedConverter;
21
+ metadataConverter;
21
22
  collection;
22
23
  collectionQuery;
23
24
  managerOptions;
@@ -29,8 +30,9 @@ class FirestoreDataManager {
29
30
  this.collectionPath = collectionPath;
30
31
  this.converter = converter;
31
32
  this.opts = opts;
33
+ this.metadataConverter = new FirestoreMetadata_1.FirestoreMetadataConverter();
32
34
  // @ts-expect-error - Force merge FirestoreDataConverter and InternalFirestoreDataConverter
33
- this.mergedConverter = new utils_1.MergeConverters(this.converter, new FirestoreMetadata_1.FirestoreMetadataConverter());
35
+ this.mergedConverter = new utils_1.MergeConverters(this.converter, this.metadataConverter);
34
36
  this.managerOptions = this.validateOptions({ ...defaultFirebaseDataManagerOptions, ...this.opts });
35
37
  this.refCache = this.managerOptions.refCache || new datamanager_1.MapCache(100);
36
38
  this.listCache = this.managerOptions.listCache || new datamanager_1.MapCache(100);
@@ -44,6 +46,7 @@ class FirestoreDataManager {
44
46
  this.referenceOptions = {
45
47
  readMode: this.managerOptions.readMode,
46
48
  snapshotOptions: this.managerOptions.snapshotOptions,
49
+ filterDeleted: this.managerOptions.deleteMode === 'soft',
47
50
  };
48
51
  }
49
52
  validateOptions(options) {
@@ -55,7 +58,11 @@ class FirestoreDataManager {
55
58
  const snapshot = await options.transaction.get(this.firestore.doc(this.collection, id));
56
59
  if (!(0, utils_1.checkIfReferenceExists)(snapshot))
57
60
  return undefined;
58
- return snapshot.data(this.referenceOptions.snapshotOptions);
61
+ const data = snapshot.data(this.referenceOptions.snapshotOptions);
62
+ if (this.managerOptions.deleteMode === 'soft' && data?.deleted === true) {
63
+ return undefined;
64
+ }
65
+ return data;
59
66
  }
60
67
  return await ref.resolve();
61
68
  }
@@ -104,6 +111,25 @@ class FirestoreDataManager {
104
111
  }
105
112
  return this.firestore.updateDoc(ref, serializedData);
106
113
  }
114
+ /**
115
+ * Update only the internal metadata fields of a document.
116
+ *
117
+ * Unlike {@link _update}, this bypasses the user-provided converter so it never re-serializes
118
+ * (and therefore never clobbers) user data. This matters because the user converter rebuilds the
119
+ * full document shape, which turns absent fields into `undefined` and can overwrite existing map
120
+ * fields with empty objects when Firestore strips the undefined values.
121
+ */
122
+ async _updateMetadata(id, metadata, options) {
123
+ const serializedData = this.metadataConverter.toFirestore({
124
+ ...metadata,
125
+ [FirestoreMetadata_1.FIRESTORE_KEYS.UPDATED_AT]: this.firestore.serverTimestamp(),
126
+ });
127
+ const ref = this.firestore.doc(this.collection, id);
128
+ if (options?.transaction) {
129
+ return options.transaction.update(ref, serializedData);
130
+ }
131
+ return this.firestore.updateDoc(ref, serializedData);
132
+ }
107
133
  async update(id, data, options) {
108
134
  await this._update(id, data, options);
109
135
  }
@@ -178,10 +204,7 @@ class FirestoreDataManager {
178
204
  }
179
205
  async delete(id, options) {
180
206
  if (this.managerOptions.deleteMode === 'soft') {
181
- await this._update(id, {
182
- ...{},
183
- [FirestoreMetadata_1.FIRESTORE_KEYS.DELETED]: true,
184
- }, options);
207
+ await this._updateMetadata(id, { [FirestoreMetadata_1.FIRESTORE_KEYS.DELETED]: true }, options);
185
208
  return;
186
209
  }
187
210
  if (options?.transaction) {
@@ -16,8 +16,8 @@ export type FirestoreSerializedMetadata = {
16
16
  [FIRESTORE_INTERAL_KEYS.UPDATED_AT]: FirestoreTypes.Timestamp;
17
17
  };
18
18
  export declare class FirestoreMetadataConverter implements InternalFirestoreDataConverter<Metadata, FirestoreSerializedMetadata> {
19
- toFirestore(data: WithFieldValue<WithoutId<Metadata>>): {
20
- __deleted: FirestoreTypes.FieldValue | FirestoreTypes.WithFieldValue<boolean>;
19
+ toFirestore(data: Partial<WithFieldValue<WithoutId<Metadata>>>): {
20
+ __deleted: boolean;
21
21
  __createdAt: FirestoreTypes.Timestamp;
22
22
  __updatedAt: FirestoreTypes.Timestamp;
23
23
  };
@@ -16,6 +16,7 @@ var FIRESTORE_KEYS;
16
16
  class FirestoreMetadataConverter {
17
17
  toFirestore(data) {
18
18
  return {
19
+ // Cast: value may be absent on partial metadata writes; Firestore strips undefined fields
19
20
  [FIRESTORE_INTERAL_KEYS.DELETED]: data[FIRESTORE_KEYS.DELETED],
20
21
  // Cast: Firestore handles conversion from Date to Timestamp internally
21
22
  [FIRESTORE_INTERAL_KEYS.CREATED_AT]: data[FIRESTORE_KEYS.CREATED_AT],
@@ -4,6 +4,12 @@ export type { FirestoreReferenceContext } from './errors';
4
4
  export interface FirestoreReferenceOptions<T> extends LiveReferenceOptions<T> {
5
5
  readMode?: FirestoreReadMode;
6
6
  snapshotOptions?: FirestoreTypes.SnapshotOptions;
7
+ /**
8
+ * When true, documents flagged as soft-deleted (`__deleted === true`) are treated as
9
+ * non-existent and resolve to `undefined`. Set by the data manager when running in
10
+ * `deleteMode: 'soft'` so single-document reads stay consistent with list queries.
11
+ */
12
+ filterDeleted?: boolean;
7
13
  }
8
14
  export declare class FirestoreReference<T extends DocumentData, S extends DocumentData> extends LiveReference<T> {
9
15
  private readonly firestore;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FirestoreReference = void 0;
4
4
  const datamanager_1 = require("@data-weave/datamanager");
5
5
  const errors_1 = require("./errors");
6
+ const FirestoreMetadata_1 = require("./FirestoreMetadata");
6
7
  const utils_1 = require("./utils");
7
8
  class FirestoreReference extends datamanager_1.LiveReference {
8
9
  firestore;
@@ -68,7 +69,12 @@ class FirestoreReference extends datamanager_1.LiveReference {
68
69
  parseDocumentSnapshot(docSnapshot) {
69
70
  if (!(0, utils_1.checkIfReferenceExists)(docSnapshot))
70
71
  throw new Error(`Document does not exist ${this.docRef.path}`);
71
- return docSnapshot.data(this.options?.snapshotOptions);
72
+ const data = docSnapshot.data(this.options?.snapshotOptions);
73
+ const deletedFlag = data?.[FirestoreMetadata_1.FIRESTORE_KEYS.DELETED];
74
+ if (this.options?.filterDeleted && deletedFlag === true) {
75
+ return undefined;
76
+ }
77
+ return data;
72
78
  }
73
79
  }
74
80
  exports.FirestoreReference = FirestoreReference;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@data-weave/backend-firestore",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "author": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -23,7 +23,7 @@
23
23
  "registry": "https://registry.npmjs.org/"
24
24
  },
25
25
  "devDependencies": {
26
- "@data-weave/datamanager": "~0.7.0",
26
+ "@data-weave/datamanager": "~0.7.2",
27
27
  "@firebase/firestore": "^4.7.8",
28
28
  "@firebase/functions-types": "^0.6.3",
29
29
  "@google-cloud/firestore": "^7.11.1",