@nu-art/firebase-backend 0.401.9 → 0.500.6

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.
Files changed (56) hide show
  1. package/ModuleBE_Firebase.d.ts +15 -1
  2. package/ModuleBE_Firebase.js +39 -3
  3. package/auth/firebase-session.d.ts +12 -8
  4. package/auth/firebase-session.js +53 -15
  5. package/firestore/DocWrapper.d.ts +30 -0
  6. package/{firestore-v3/DocWrapperV3.js → firestore/DocWrapper.js} +30 -32
  7. package/firestore/FirestoreCollection.d.ts +141 -59
  8. package/firestore/FirestoreCollection.js +419 -147
  9. package/firestore/FirestoreInterface.d.ts +2 -3
  10. package/firestore/FirestoreInterface.js +1 -5
  11. package/firestore/FirestoreWrapperBE.d.ts +5 -6
  12. package/firestore/FirestoreWrapperBE.js +119 -9
  13. package/firestore/MongoCollection.d.ts +81 -0
  14. package/firestore/MongoCollection.js +426 -0
  15. package/firestore/MongoInterface.d.ts +18 -0
  16. package/firestore/MongoInterface.js +132 -0
  17. package/firestore/MongoWrapperBE.d.ts +18 -0
  18. package/firestore/MongoWrapperBE.js +95 -0
  19. package/firestore/consts.d.ts +23 -0
  20. package/firestore/consts.js +34 -0
  21. package/firestore/types.d.ts +6 -1
  22. package/firestore/types.js +0 -24
  23. package/{functions-v2 → functions}/ModuleBE_BaseFunction.d.ts +6 -3
  24. package/{functions-v2 → functions}/ModuleBE_BaseFunction.js +1 -0
  25. package/{functions-v2/ModuleBE_ExpressFunction_V2.d.ts → functions/ModuleBE_ExpressFunction_Class.d.ts} +5 -3
  26. package/{functions-v2/ModuleBE_ExpressFunction_V2.js → functions/ModuleBE_ExpressFunction_Class.js} +7 -3
  27. package/{functions-v2 → functions}/ModuleBE_FirebaseDBListener.d.ts +2 -1
  28. package/{functions-v2 → functions}/ModuleBE_FirebaseDBListener.js +1 -0
  29. package/{functions-v2 → functions}/ModuleBE_FirebaseScheduler.js +1 -0
  30. package/{functions-v2 → functions}/ModuleBE_FirestoreListener.d.ts +5 -1
  31. package/{functions-v2 → functions}/ModuleBE_FirestoreListener.js +1 -0
  32. package/{functions-v2 → functions}/ModuleBE_PubSubFunction.d.ts +5 -2
  33. package/{functions-v2 → functions}/ModuleBE_PubSubFunction.js +1 -0
  34. package/{functions-v2 → functions}/ModuleBE_StorageListener.js +1 -0
  35. package/index.d.ts +16 -13
  36. package/index.js +16 -13
  37. package/package.json +10 -7
  38. package/firestore/FirestoreTransaction.d.ts +0 -30
  39. package/firestore/FirestoreTransaction.js +0 -153
  40. package/firestore-v3/DocWrapperV3.d.ts +0 -32
  41. package/firestore-v3/FirestoreCollectionV3.d.ts +0 -154
  42. package/firestore-v3/FirestoreCollectionV3.js +0 -470
  43. package/firestore-v3/FirestoreInterfaceV3.d.ts +0 -10
  44. package/firestore-v3/FirestoreInterfaceV3.js +0 -107
  45. package/firestore-v3/FirestoreWrapperBEV3.d.ts +0 -16
  46. package/firestore-v3/FirestoreWrapperBEV3.js +0 -154
  47. package/firestore-v3/consts.d.ts +0 -13
  48. package/firestore-v3/consts.js +0 -18
  49. package/firestore-v3/types.d.ts +0 -6
  50. package/firestore-v3/types.js +0 -1
  51. package/functions/firebase-function.d.ts +0 -38
  52. package/functions/firebase-function.js +0 -53
  53. package/v1.d.ts +0 -21
  54. package/v1.js +0 -38
  55. /package/{functions-v2 → functions}/ModuleBE_FirebaseScheduler.d.ts +0 -0
  56. /package/{functions-v2 → functions}/ModuleBE_StorageListener.d.ts +0 -0
@@ -1,10 +1,20 @@
1
1
  /**
2
2
  * Created by tacb0ss on 25/08/2018.
3
3
  */
4
- import { Module } from '@nu-art/ts-common';
4
+ import { Module, TypedMap } from '@nu-art/ts-common';
5
5
  import { FirebaseSession_Admin } from './auth/FirebaseSession_Admin.js';
6
+ export type FirestoreMongoConfig = {
7
+ firestoreUid: string;
8
+ firestoreLocation: string;
9
+ };
10
+ export type MongoConnectionConfig = {
11
+ mongoUrl?: string;
12
+ firestoreMongo?: FirestoreMongoConfig;
13
+ params?: TypedMap<string | number>;
14
+ };
6
15
  type ConfigType = {
7
16
  isEmulator?: boolean;
17
+ mongo?: MongoConnectionConfig;
8
18
  };
9
19
  export declare const FIREBASE_DEFAULT_PROJECT_ID = "local";
10
20
  export declare class ModuleBE_Firebase_Class extends Module<ConfigType> {
@@ -13,6 +23,10 @@ export declare class ModuleBE_Firebase_Class extends Module<ConfigType> {
13
23
  protected init(): void;
14
24
  __resetForTests(): Promise<void>;
15
25
  createAdminSession(authKey?: string): FirebaseSession_Admin;
26
+ resolveMongoUrl(authKey?: string): string;
27
+ private resolveMongoConfig;
28
+ private static readonly firestoreMongoDefaults;
29
+ private static buildFirestoreMongoUrl;
16
30
  createModuleStateFirebaseRef<T>(module: Module, _relativePath: string): import("./index.js").FirebaseRef<T>;
17
31
  }
18
32
  export declare const ModuleBE_Firebase: ModuleBE_Firebase_Class;
@@ -18,8 +18,9 @@
18
18
  /**
19
19
  * Created by tacb0ss on 25/08/2018.
20
20
  */
21
- import { Module } from '@nu-art/ts-common';
21
+ import { composeUrl, ImplementationMissingException, merge, Module } from '@nu-art/ts-common';
22
22
  import { FirebaseSession_Admin } from './auth/FirebaseSession_Admin.js';
23
+ import { FirebaseSession } from './auth/firebase-session.js';
23
24
  // import {FirebaseSession_UserPassword} from "./auth/FirebaseSession_UserPassword.js";
24
25
  import { readFileSync } from 'fs';
25
26
  import { ModuleBE_Auth } from '@nu-art/google-services-backend';
@@ -31,6 +32,7 @@ export class ModuleBE_Firebase_Class extends Module {
31
32
  super('firebase');
32
33
  }
33
34
  init() {
35
+ FirebaseSession.setMongoUrlResolver((authKey) => this.resolveMongoUrl(authKey));
34
36
  }
35
37
  async __resetForTests() {
36
38
  this.logWarning('__resetForTests');
@@ -42,10 +44,9 @@ export class ModuleBE_Firebase_Class extends Module {
42
44
  const session = this.adminSessions[authKey];
43
45
  if (session)
44
46
  return session;
45
- // try to fetch the service account from the auth serviceAccount by the authKey
46
47
  let serviceAccount;
47
48
  try {
48
- serviceAccount = ModuleBE_Auth.getAuthConfig(authKey);
49
+ serviceAccount = ModuleBE_Auth.getCredentials(authKey);
49
50
  }
50
51
  catch (e) {
51
52
  if (authKey !== FIREBASE_DEFAULT_PROJECT_ID)
@@ -58,6 +59,41 @@ export class ModuleBE_Firebase_Class extends Module {
58
59
  this.logInfo(`Creating Firebase session for auth key: ${authKey}`, serviceAccount);
59
60
  return this.adminSessions[authKey] = new FirebaseSession_Admin(authKey, serviceAccount).connect();
60
61
  }
62
+ resolveMongoUrl(authKey) {
63
+ const mongoConfig = this.resolveMongoConfig(authKey);
64
+ if (mongoConfig?.mongoUrl)
65
+ return composeUrl(mongoConfig.mongoUrl, mongoConfig.params);
66
+ if (mongoConfig?.firestoreMongo)
67
+ return ModuleBE_Firebase_Class.buildFirestoreMongoUrl(mongoConfig.firestoreMongo, mongoConfig.params);
68
+ throw new ImplementationMissingException(`No MongoDB connection configured for session '${authKey ?? 'default'}'. ` +
69
+ 'Set mongo.mongoUrl or mongo.firestoreMongo in ModuleBE_Firebase config (default), or in auth config (keyed).');
70
+ }
71
+ resolveMongoConfig(authKey) {
72
+ if (!authKey || authKey === FIREBASE_DEFAULT_PROJECT_ID)
73
+ return this.config?.mongo;
74
+ try {
75
+ const authConfig = ModuleBE_Auth.getAuthConfig(authKey);
76
+ if (typeof authConfig === 'object' && 'mongo' in authConfig)
77
+ return authConfig.mongo;
78
+ }
79
+ catch (_e) {
80
+ }
81
+ return undefined;
82
+ }
83
+ static firestoreMongoDefaults = {
84
+ loadBalanced: 'true',
85
+ tls: 'true',
86
+ retryWrites: 'false',
87
+ connectTimeoutMS: 30000,
88
+ socketTimeoutMS: 60000,
89
+ authMechanism: 'MONGODB-OIDC',
90
+ authMechanismProperties: 'ENVIRONMENT:gcp,TOKEN_RESOURCE:FIRESTORE',
91
+ };
92
+ static buildFirestoreMongoUrl(config, overrides) {
93
+ const { firestoreUid, firestoreLocation } = config;
94
+ const baseUrl = `mongodb://${firestoreUid}.${firestoreLocation}.firestore.goog:443/default`;
95
+ return composeUrl(baseUrl, merge(ModuleBE_Firebase_Class.firestoreMongoDefaults, overrides));
96
+ }
61
97
  createModuleStateFirebaseRef(module, _relativePath) {
62
98
  let relativePath = _relativePath;
63
99
  if (relativePath.startsWith('/'))
@@ -6,9 +6,10 @@ import { DatabaseWrapperBE } from '../database/DatabaseWrapperBE.js';
6
6
  import { StorageWrapperBE } from '../storage/StorageWrapperBE.js';
7
7
  import { PushMessagesWrapperBE } from '../push/PushMessagesWrapperBE.js';
8
8
  import { App } from 'firebase-admin/app';
9
- import { FirestoreWrapperBEV3 } from '../firestore-v3/FirestoreWrapperBEV3.js';
10
- import { FirebaseConfig } from '@nu-art/firebase-shared';
11
9
  import { FirestoreWrapperBE } from '../firestore/FirestoreWrapperBE.js';
10
+ import { MongoWrapperBE } from '../firestore/MongoWrapperBE.js';
11
+ import { FirebaseConfig } from '@nu-art/firebase-shared';
12
+ import { MongoClient } from 'mongodb';
12
13
  /**
13
14
  * Represents the credentials of a Firebase user.
14
15
  */
@@ -22,13 +23,17 @@ export type Firebase_UserCredential = {
22
23
  /**
23
24
  * An abstract class that serves as a base for Firebase session classes.
24
25
  */
26
+ export type MongoUrlResolver = (authKey?: string) => string;
25
27
  export declare abstract class FirebaseSession<Config> extends Logger {
28
+ private static mongoUrlResolver?;
29
+ static setMongoUrlResolver(resolver: MongoUrlResolver): void;
26
30
  app: App;
27
31
  protected databases: TypedMap<DatabaseWrapperBE>;
28
32
  protected storage?: StorageWrapperBE;
29
- protected firestoresV3: TypedMap<FirestoreWrapperBEV3>;
30
- protected messaging?: PushMessagesWrapperBE;
31
33
  protected firestores: TypedMap<FirestoreWrapperBE>;
34
+ protected mongos: TypedMap<MongoWrapperBE>;
35
+ protected mongoClient?: MongoClient;
36
+ protected messaging?: PushMessagesWrapperBE;
32
37
  protected config: Config;
33
38
  protected firebaseAppName: string;
34
39
  private readonly admin;
@@ -49,11 +54,10 @@ export declare abstract class FirebaseSession<Config> extends Logger {
49
54
  * Returns an instance of the StorageWrapperBE object, which provides access to a cloud storage service.
50
55
  */
51
56
  getStorage(): StorageWrapperBE;
52
- /**
53
- * Returns an instance of the FirestoreWrapperBE object, which provides access to the Firestore database service.
54
- */
55
57
  getFirestore(dbName?: string): FirestoreWrapperBE;
56
- getFirestoreV3(dbName?: string): FirestoreWrapperBEV3;
58
+ setMongoClient(client: MongoClient): void;
59
+ reconnectMongo(): Promise<void>;
60
+ getMongo(dbName: string): MongoWrapperBE;
57
61
  /**
58
62
  * Returns an instance of the PushMessagesWrapperBE object, which provides access to push messaging services.
59
63
  */
@@ -18,23 +18,25 @@
18
18
  /**
19
19
  * Created by tacb0ss on 25/08/2018.
20
20
  */
21
- import { Logger } from '@nu-art/ts-common';
21
+ import { ImplementationMissingException, Logger } from '@nu-art/ts-common';
22
22
  import { DatabaseWrapperBE } from '../database/DatabaseWrapperBE.js';
23
23
  import { StorageWrapperBE } from '../storage/StorageWrapperBE.js';
24
24
  import { PushMessagesWrapperBE } from '../push/PushMessagesWrapperBE.js';
25
- import { FirestoreWrapperBEV3 } from '../firestore-v3/FirestoreWrapperBEV3.js';
26
25
  import { FirestoreWrapperBE } from '../firestore/FirestoreWrapperBE.js';
27
- // export type FirebaseApp = admin.app.App | firebase.app.App
28
- /**
29
- * An abstract class that serves as a base for Firebase session classes.
30
- */
26
+ import { MongoWrapperBE } from '../firestore/MongoWrapperBE.js';
27
+ import { MongoClient } from 'mongodb';
31
28
  export class FirebaseSession extends Logger {
29
+ static mongoUrlResolver;
30
+ static setMongoUrlResolver(resolver) {
31
+ FirebaseSession.mongoUrlResolver = resolver;
32
+ }
32
33
  app;
33
34
  databases = {};
34
35
  storage;
35
- firestoresV3 = {};
36
- messaging;
37
36
  firestores = {};
37
+ mongos = {};
38
+ mongoClient;
39
+ messaging;
38
40
  config;
39
41
  firebaseAppName;
40
42
  admin;
@@ -69,18 +71,54 @@ export class FirebaseSession extends Logger {
69
71
  return this.storage;
70
72
  return this.storage = new StorageWrapperBE(this);
71
73
  }
72
- /**
73
- * Returns an instance of the FirestoreWrapperBE object, which provides access to the Firestore database service.
74
- */
75
74
  getFirestore(dbName) {
76
75
  if (!this.firestores[dbName ?? 'default'])
77
76
  this.firestores[dbName ?? 'default'] = new FirestoreWrapperBE(this, dbName);
78
77
  return this.firestores[dbName ?? 'default'];
79
78
  }
80
- getFirestoreV3(dbName) {
81
- if (!this.firestoresV3[dbName ?? 'default'])
82
- this.firestoresV3[dbName ?? 'default'] = new FirestoreWrapperBEV3(this, dbName);
83
- return this.firestoresV3[dbName ?? 'default'];
79
+ setMongoClient(client) {
80
+ this.mongoClient = client;
81
+ }
82
+ async reconnectMongo() {
83
+ if (!this.mongoClient)
84
+ return;
85
+ this.logWarning('Reconnecting MongoDB — closing existing client');
86
+ try {
87
+ await this.mongoClient.close();
88
+ }
89
+ catch (e) {
90
+ this.logWarning(`Error closing MongoDB client during reconnect: ${e}`);
91
+ }
92
+ this.mongoClient = undefined;
93
+ for (const key of Object.keys(this.mongos))
94
+ delete this.mongos[key];
95
+ }
96
+ getMongo(dbName) {
97
+ if (!this.mongoClient) {
98
+ if (!FirebaseSession.mongoUrlResolver)
99
+ throw new ImplementationMissingException('MongoUrlResolver not set — call FirebaseSession.setMongoUrlResolver() during module init');
100
+ const url = FirebaseSession.mongoUrlResolver(this.firebaseAppName);
101
+ this.logInfo(`Creating MongoClient: ${url}`);
102
+ this.mongoClient = new MongoClient(url, { monitorCommands: true });
103
+ this.mongoClient.on('commandStarted', (event) => {
104
+ this.logDebug(`MongoDB >>> [${event.commandName}] reqId=${event.requestId} ns=${event.databaseName}.${event.command[event.commandName]}`);
105
+ });
106
+ this.mongoClient.on('commandFailed', (event) => {
107
+ this.logError(`MongoDB commandFailed [${event.commandName}] reqId=${event.requestId}: ${event.failure.message}`);
108
+ });
109
+ this.mongoClient.on('commandSucceeded', (event) => {
110
+ const reply = event.reply;
111
+ if (event.commandName === 'find' && !reply?.cursor) {
112
+ this.logError(`MongoDB <<< [${event.commandName}] reqId=${event.requestId} MISSING CURSOR — reply=${JSON.stringify(reply)}`);
113
+ }
114
+ else {
115
+ this.logDebug(`MongoDB <<< [${event.commandName}] reqId=${event.requestId} ok=${reply?.ok} ${event.duration}ms`);
116
+ }
117
+ });
118
+ }
119
+ if (!this.mongos[dbName])
120
+ this.mongos[dbName] = new MongoWrapperBE(this.mongoClient, dbName);
121
+ return this.mongos[dbName];
84
122
  }
85
123
  /**
86
124
  * Returns an instance of the PushMessagesWrapperBE object, which provides access to push messaging services.
@@ -0,0 +1,30 @@
1
+ import { DB_Prototype } from '@nu-art/db-api-shared';
2
+ import { TS_Object, UniqueId } from '@nu-art/ts-common';
3
+ import { FirestoreType_DocumentReference } from './types.js';
4
+ import { FirestoreCollection } from './FirestoreCollection.js';
5
+ import type { firestore as Fa } from 'firebase-admin';
6
+ export type UpdateObject<DBType extends TS_Object> = {
7
+ _id: UniqueId;
8
+ } & Fa.UpdateData<DBType>;
9
+ export declare class DocWrapper<Proto extends DB_Prototype> {
10
+ readonly ref: FirestoreType_DocumentReference<Proto['dbType']>;
11
+ readonly collection: FirestoreCollection<Proto>;
12
+ data?: Proto['dbType'];
13
+ protected constructor(collection: FirestoreCollection<Proto>, ref: FirestoreType_DocumentReference<Proto['dbType']>, data?: Proto['dbType']);
14
+ fromCache: () => Proto["dbType"] | undefined;
15
+ cleanCache: () => DocWrapper<Proto>;
16
+ private assertId;
17
+ get: () => Promise<Proto["dbType"] | undefined>;
18
+ prepareForCreate: (preDBItem: Proto["uiType"], upgrade?: boolean) => Promise<Proto["dbType"]>;
19
+ create: (preDBItem: Proto["uiType"]) => Promise<Proto["dbType"]>;
20
+ prepareForSet: (updatedDBItem: Proto["dbType"], dbItem?: Proto["dbType"], upgrade?: boolean) => Promise<Proto["dbType"]>;
21
+ set: (item: Proto["uiType"] | Proto["dbType"]) => Promise<Proto["dbType"]>;
22
+ private postWriteProcessing;
23
+ prepareForUpdate(updateData: UpdateObject<Proto['dbType']>): Promise<UpdateObject<Proto["dbType"]>>;
24
+ /**
25
+ * Recursively replaces any undefined or null fields in DB item with firestore.FieldValue.delete()
26
+ */
27
+ private updateDeletedFields;
28
+ update: (updateData: UpdateObject<Proto["dbType"]>) => Promise<Proto["dbType"] | undefined>;
29
+ delete: () => Promise<Proto["dbType"] | undefined>;
30
+ }
@@ -1,10 +1,10 @@
1
1
  import { _keys, currentTimeMillis, exists, MUSTNeverHappenException } from '@nu-art/ts-common';
2
- import { assertUniqueId } from './FirestoreCollectionV3.js';
2
+ import { assertUniqueId } from './FirestoreCollection.js';
3
3
  import { HttpCodes } from '@nu-art/ts-common/core/exceptions/http-codes';
4
- import { addDeletedToTransaction } from './consts.js';
4
+ import { addDeletedToTransaction, getActiveTransaction } from './consts.js';
5
5
  import admin from 'firebase-admin';
6
6
  const { FieldValue } = admin.firestore;
7
- export class DocWrapperV3 {
7
+ export class DocWrapper {
8
8
  ref;
9
9
  collection;
10
10
  data;
@@ -25,12 +25,13 @@ export class DocWrapperV3 {
25
25
  if (item._id !== this.ref.id)
26
26
  throw new MUSTNeverHappenException(`Composed _id does not match doc ref id! \n expected: ${this.ref.id} \n actual: ${item._id} \n`);
27
27
  }
28
- get = async (transaction) => {
28
+ get = async () => {
29
+ const transaction = getActiveTransaction();
29
30
  if (transaction)
30
31
  return this.data ?? (this.data = (await transaction.get(this.ref)).data());
31
32
  return this.data ?? (this.data = (await this.ref.get()).data());
32
33
  };
33
- prepareForCreate = async (preDBItem, transaction, upgrade = true) => {
34
+ prepareForCreate = async (preDBItem, upgrade = true) => {
34
35
  const now = currentTimeMillis();
35
36
  this.assertId(preDBItem);
36
37
  preDBItem.__updated = preDBItem.__created = now;
@@ -38,24 +39,25 @@ export class DocWrapperV3 {
38
39
  preDBItem._v = this.collection.getVersion();
39
40
  await this.collection.hooks?.upgradeInstances([preDBItem]);
40
41
  }
41
- await this.collection.hooks?.preWriteProcessing?.(preDBItem, undefined, transaction);
42
+ await this.collection.hooks?.preWriteProcessing?.(preDBItem, undefined);
42
43
  this.collection.validateItem(preDBItem);
43
44
  return preDBItem;
44
45
  };
45
- create = async (preDBItem, transaction) => {
46
+ create = async (preDBItem) => {
46
47
  const dbItem = await this.prepareForCreate(preDBItem);
48
+ const transaction = getActiveTransaction();
47
49
  if (transaction) {
48
50
  transaction.create(this.ref, dbItem);
49
51
  this.data = dbItem;
50
52
  }
51
53
  else
52
54
  await this.ref.create(dbItem);
53
- this.postWriteProcessing({ updated: dbItem }, 'create', transaction);
55
+ this.postWriteProcessing({ updated: dbItem }, 'create');
54
56
  return dbItem;
55
57
  };
56
- prepareForSet = async (updatedDBItem, dbItem, transaction, upgrade = true) => {
58
+ prepareForSet = async (updatedDBItem, dbItem, upgrade = true) => {
57
59
  if (!dbItem)
58
- return this.prepareForCreate(updatedDBItem, transaction);
60
+ return this.prepareForCreate(updatedDBItem);
59
61
  this.assertId(updatedDBItem);
60
62
  updatedDBItem.__created = dbItem.__created;
61
63
  this.collection.dbDef.lockKeys?.forEach(lockedKey => {
@@ -67,24 +69,25 @@ export class DocWrapperV3 {
67
69
  await this.collection.hooks?.upgradeInstances([updatedDBItem]);
68
70
  }
69
71
  updatedDBItem._v = this.collection.getVersion();
70
- await this.collection.hooks?.preWriteProcessing?.(updatedDBItem, dbItem, transaction);
72
+ await this.collection.hooks?.preWriteProcessing?.(updatedDBItem, dbItem);
71
73
  this.collection.validateItem(updatedDBItem);
72
74
  return updatedDBItem;
73
75
  };
74
- set = async (item, transaction) => {
76
+ set = async (item) => {
77
+ const transaction = getActiveTransaction();
75
78
  if (!transaction)
76
- return this.collection.runTransaction((transaction) => this.set(item, transaction));
77
- const currDBItem = await this.get(transaction);
79
+ return this.collection.runTransaction(() => this.set(item));
80
+ const currDBItem = await this.get();
78
81
  if ((currDBItem?.__updated || 0) > (item.__updated || currentTimeMillis()))
79
82
  throw HttpCodes._4XX.ENTITY_IS_OUTDATED('Item is outdated', `${this.collection.collection.path}/${currDBItem?._id} is outdated`);
80
- const newDBItem = await this.prepareForSet(item, currDBItem, transaction, false);
81
- // Will always get here with a transaction!
83
+ const newDBItem = await this.prepareForSet(item, currDBItem, false);
82
84
  transaction.set(this.ref, newDBItem);
83
85
  this.data = currDBItem;
84
- this.postWriteProcessing({ updated: newDBItem, before: currDBItem }, 'set', transaction);
86
+ this.postWriteProcessing({ updated: newDBItem, before: currDBItem }, 'set');
85
87
  return newDBItem;
86
88
  };
87
- postWriteProcessing(data, actionType, transaction) {
89
+ postWriteProcessing(data, actionType) {
90
+ const transaction = getActiveTransaction();
88
91
  const toExecute = () => this.collection.hooks?.postWriteProcessing?.(data, actionType);
89
92
  if (transaction)
90
93
  // @ts-ignore
@@ -92,21 +95,16 @@ export class DocWrapperV3 {
92
95
  else
93
96
  toExecute();
94
97
  }
95
- async prepareForUpdate(updateData, transaction) {
98
+ async prepareForUpdate(updateData) {
96
99
  delete updateData.__created;
97
100
  delete updateData._v;
98
101
  updateData.__updated = currentTimeMillis();
99
- // this.collection.dbDef.lockKeys?.forEach(lockedKey => {
100
- // (updateData as Partial<Proto['dbType']>)[lockedKey] = undefined;
101
- // });
102
102
  this.updateDeletedFields(updateData);
103
- await this.collection.validateUpdateData(updateData, transaction);
103
+ await this.collection.validateUpdateData(updateData);
104
104
  return updateData;
105
105
  }
106
106
  /**
107
107
  * Recursively replaces any undefined or null fields in DB item with firestore.FieldValue.delete()
108
- * @private
109
- * @param updateData
110
108
  */
111
109
  updateDeletedFields(updateData) {
112
110
  if (typeof updateData !== 'object' || updateData === null)
@@ -128,21 +126,21 @@ export class DocWrapperV3 {
128
126
  this.postWriteProcessing({ updated: dbItem }, 'update');
129
127
  return dbItem;
130
128
  };
131
- delete = async (transaction) => {
129
+ delete = async () => {
130
+ const transaction = getActiveTransaction();
132
131
  if (!transaction)
133
- return this.collection.runTransaction(transaction => this.delete(transaction));
134
- const dbItem = await this.get(transaction);
132
+ return this.collection.runTransaction(() => this.delete());
133
+ const dbItem = await this.get();
135
134
  if (!dbItem)
136
135
  return;
137
- addDeletedToTransaction(transaction, {
136
+ addDeletedToTransaction({
138
137
  dbKey: this.collection.dbDef.entityName,
139
138
  ids: [dbItem._id]
140
139
  });
141
- await this.collection.hooks?.canDeleteItems([dbItem], transaction);
142
- // Will always get here with a transaction!
140
+ await this.collection.hooks?.canDeleteItems([dbItem]);
143
141
  transaction.delete(this.ref);
144
142
  this.cleanCache();
145
- this.postWriteProcessing({ deleted: dbItem }, 'delete', transaction);
143
+ this.postWriteProcessing({ deleted: dbItem }, 'delete');
146
144
  return dbItem;
147
145
  };
148
146
  }
@@ -1,71 +1,153 @@
1
- import { Subset, TS_Object } from '@nu-art/ts-common';
2
- import { FirestoreType_Collection, FirestoreType_DocumentSnapshot } from './types.js';
3
- import { Clause_Select, Clause_Where, FilterKeys, FirestoreQuery } from '@nu-art/firebase-shared';
1
+ import { Database, DB_Prototype } from '@nu-art/db-api-shared';
2
+ import { CustomException, InvalidResult, Logger, TS_Object, UniqueId } from '@nu-art/ts-common';
3
+ import { FirestoreType_Collection, FirestoreType_DocumentReference } from './types.js';
4
+ import { Clause_Where, FirestoreQuery, MultiWriteOperation } from '@nu-art/firebase-shared';
4
5
  import { FirestoreWrapperBE } from './FirestoreWrapperBE.js';
5
- import { FirestoreTransaction } from './FirestoreTransaction.js';
6
- import { Transaction } from 'firebase-admin/firestore';
6
+ import { firestore } from 'firebase-admin';
7
+ import { DocWrapper, UpdateObject } from './DocWrapper.js';
8
+ import UpdateData = firestore.UpdateData;
9
+ export type PostWriteProcessingData<DBType extends TS_Object> = {
10
+ before?: DBType | DBType[];
11
+ updated?: DBType | DBType[];
12
+ deleted?: DBType | DBType[] | null;
13
+ };
14
+ export type CollectionActionType = 'create' | 'set' | 'update' | 'delete';
15
+ export type FirestoreCollectionHooks<DBType extends TS_Object> = {
16
+ canDeleteItems: (dbItems: DBType[]) => Promise<void>;
17
+ preWriteProcessing?: (dbInstance: DBType, originalDbInstance?: DBType) => Promise<void>;
18
+ manipulateQuery?: (query: FirestoreQuery<DBType>) => FirestoreQuery<DBType>;
19
+ postWriteProcessing?: (data: PostWriteProcessingData<DBType>, actionType: CollectionActionType) => Promise<void>;
20
+ upgradeInstances: (instances: DBType[]) => Promise<any>;
21
+ };
22
+ export type MultiWriteItem<Op extends MultiWriteOperation, DBType extends TS_Object> = Op extends 'delete' ? undefined : Op extends 'update' ? UpdateObject<DBType> : DBType;
23
+ type MultiWriteType = 'bulk' | 'batch';
7
24
  /**
8
- * FirestoreCollection is a class for handling Firestore collections. It takes in the name, FirestoreWrapperBE instance, and uniqueKeys as parameters.
25
+ * # <ins>FirestoreBulkException</ins>
26
+ * @category Exceptions
9
27
  */
10
- export declare class FirestoreCollection<Type extends TS_Object> {
11
- readonly name: string;
28
+ export declare class FirestoreBulkException extends CustomException {
29
+ causes?: Error[];
30
+ constructor(causes?: Error[]);
31
+ }
32
+ /**
33
+ * FirestoreCollection is a class for handling Firestore collections.
34
+ */
35
+ export declare class FirestoreCollection<Proto extends DB_Prototype> extends Logger {
12
36
  readonly wrapper: FirestoreWrapperBE;
13
37
  readonly collection: FirestoreType_Collection;
38
+ readonly dbDef: Database<Proto>;
39
+ readonly uniqueKeys: Proto['uniqueKeys'][] | string[];
40
+ private readonly validator;
41
+ readonly hooks?: FirestoreCollectionHooks<Proto['dbType']>;
42
+ constructor(wrapper: FirestoreWrapperBE, _dbDef: Database<Proto>, hooks?: FirestoreCollectionHooks<Proto['dbType']>);
43
+ doc: Readonly<{
44
+ _: (ref: FirestoreType_DocumentReference<Proto["dbType"]>, data?: Proto["dbType"]) => DocWrapper<Proto>;
45
+ unique: (id: Proto["uniqueParam"]) => DocWrapper<Proto>;
46
+ item: (item: Proto["uiType"]) => DocWrapper<Proto>;
47
+ all: (_ids: (Proto["uniqueParam"])[]) => DocWrapper<Proto>[];
48
+ allItems: (preDBItems: Proto["uiType"][]) => DocWrapper<Proto>[];
49
+ query: (query: FirestoreQuery<Proto["dbType"]>) => Promise<DocWrapper<Proto>[]>;
50
+ unManipulatedQuery: (query: FirestoreQuery<Proto["dbType"]>) => Promise<DocWrapper<Proto>[]>;
51
+ }>;
52
+ private getAll;
53
+ private _customQuery;
54
+ query: Readonly<{
55
+ unique: (_id: Proto["uniqueParam"]) => Promise<Proto["dbType"] | undefined>;
56
+ uniqueAssert: (_id: Proto["uniqueParam"]) => Promise<Proto["dbType"]>;
57
+ uniqueWhere: (where: Clause_Where<Proto["dbType"]>) => Promise<Proto["dbType"]>;
58
+ uniqueCustom: (query: FirestoreQuery<Proto["dbType"]>) => Promise<Proto["dbType"]>;
59
+ all: (_ids: (Proto["uniqueParam"])[]) => Promise<(Proto["dbType"] | undefined)[]>;
60
+ custom: (query: FirestoreQuery<Proto["dbType"]>) => Promise<Proto["dbType"][]>;
61
+ where: (where: Clause_Where<Proto["dbType"]>) => Promise<Proto["dbType"][]>;
62
+ unManipulatedQuery: (query: FirestoreQuery<Proto["dbType"]>) => Promise<Proto["dbType"][]>;
63
+ }>;
64
+ uniqueGetOrCreate: (where: Clause_Where<Proto["dbType"]>, toCreate: () => Promise<Proto["uiType"]>) => Promise<Proto["dbType"] | Proto["uiType"]>;
65
+ protected _createAll: (preDBItems: Proto["uiType"][], multiWriteType?: MultiWriteType) => Promise<Proto["dbType"][]>;
66
+ create: Readonly<{
67
+ item: (preDBItem: Proto["uiType"]) => Promise<Proto["dbType"]>;
68
+ all: (preDBItems: Proto["uiType"][], multiWriteType?: MultiWriteType) => Promise<Proto["dbType"][]>;
69
+ }>;
70
+ private _setAll;
71
+ set: Readonly<{
72
+ item: (preDBItem: Proto["uiType"]) => Promise<Proto["dbType"]>;
73
+ all: (items: (Proto["uiType"] | Proto["dbType"])[]) => Promise<Awaited<Proto["dbType"]>[]>;
74
+ /**
75
+ * Multi is a non atomic operation
76
+ */
77
+ multi: (items: (Proto["uiType"] | Proto["dbType"])[], multiWriteType?: MultiWriteType) => Promise<Awaited<Proto["dbType"]>[]>;
78
+ }>;
79
+ private upgradeInstances;
80
+ protected _updateAll: (updateData: UpdateObject<Proto["dbType"]>[], multiWriteType?: MultiWriteType) => Promise<Proto["dbType"][]>;
81
+ validateUpdateData(updateData: UpdateData<Proto['dbType']>): Promise<void>;
82
+ protected _deleteQuery: (query: FirestoreQuery<Proto["dbType"]>, multiWriteType?: MultiWriteType) => Promise<NonNullable<Proto["dbType"]>[]>;
83
+ protected _deleteUnManipulatedQuery: (query: FirestoreQuery<Proto["dbType"]>, multiWriteType?: MultiWriteType) => Promise<NonNullable<Proto["dbType"]>[]>;
84
+ protected _deleteAll: (docsToBeDeleted: DocWrapper<Proto>[], multiWriteType?: MultiWriteType) => Promise<Proto["dbType"][]>;
85
+ private deleteCollection;
86
+ delete: Readonly<{
87
+ unique: (id: Proto["uniqueParam"]) => Promise<Proto["dbType"] | undefined>;
88
+ item: (item: Proto["uiType"]) => Promise<Proto["dbType"] | undefined>;
89
+ all: (ids: (Proto["uniqueParam"])[]) => Promise<Proto["dbType"][]>;
90
+ allDocs: (docs: DocWrapper<Proto>[]) => Promise<Proto["dbType"][]>;
91
+ allItems: (items: Proto["uiType"][]) => Promise<Proto["dbType"][]>;
92
+ query: (query: FirestoreQuery<Proto["dbType"]>) => Promise<Proto["dbType"][]>;
93
+ unManipulatedQuery: (query: FirestoreQuery<Proto["dbType"]>) => Promise<Proto["dbType"][]>;
94
+ where: (where: Clause_Where<Proto["dbType"]>) => Promise<Proto["dbType"][]>;
95
+ /**
96
+ * Multi is a non atomic operation — doesn't use transactions. Use 'all' variants for transaction.
97
+ */
98
+ multi: {
99
+ all: (ids: UniqueId[], multiWriteType?: MultiWriteType) => Promise<Proto["dbType"][]>;
100
+ items: (items: Proto["uiType"][], multiWriteType?: MultiWriteType) => Promise<Proto["dbType"][]>;
101
+ allDocs: (docs: DocWrapper<Proto>[], multiWriteType?: MultiWriteType) => Promise<Proto["dbType"][]>;
102
+ query: (query: FirestoreQuery<Proto["dbType"]>, multiWriteType?: MultiWriteType) => Promise<NonNullable<Proto["dbType"]>[]>;
103
+ };
104
+ yes: {
105
+ iam: {
106
+ sure: {
107
+ iwant: {
108
+ todelete: {
109
+ the: {
110
+ collection: {
111
+ delete: () => Promise<void>;
112
+ };
113
+ };
114
+ };
115
+ };
116
+ };
117
+ };
118
+ };
119
+ }>;
14
120
  /**
15
- * External unique as in there must never ever be two that answer the same query
16
- */
17
- readonly externalUniqueFilter: ((object: Subset<Type>) => Clause_Where<Type>);
18
- /**
19
- * @param name
20
- * @param wrapper
21
- * @param uniqueKeys
121
+ * @param writer Type of BulkWriter - can be Bulk writer or Batch writer
122
+ * @param doc
123
+ * @param operation create/update/set/delete
124
+ * @param item - mandatory for everything but delete
22
125
  */
23
- constructor(name: string, wrapper: FirestoreWrapperBE, uniqueKeys?: FilterKeys<Type>);
126
+ private addToMultiWrite;
127
+ private multiWrite;
128
+ private bulkWrite;
24
129
  /**
25
- Executes a Firestore query on the collection.
26
- @param ourQuery - The query to execute.
27
- @returns A Promise that resolves to an array of FirestoreType_DocumentSnapshot objects.
28
- @private
130
+ * @param docs docs to write to
131
+ * @param operation create/update/set/delete
132
+ * @param items mandatory for everything but delete
29
133
  */
30
- private _query;
134
+ private batchWrite;
31
135
  /**
32
-
33
- Executes a unique Firestore query on the collection.
34
- @param ourQuery - The query to execute.
35
- @returns A Promise that resolves to a single FirestoreType_DocumentSnapshot object, or undefined if no match is found.
36
- @private
136
+ * Runs the processor within a Firestore transaction scope. If already inside a transaction
137
+ * (detected via MemKey), the existing transaction is reused. Otherwise a new one is created.
37
138
  */
38
- private _queryUnique;
39
- /**
40
- Executes a unique Firestore query on the collection and returns the matching object.
41
- @param ourQuery - The query to execute.
42
- @returns A Promise that resolves to the matching object, or undefined if no match is found.
43
- */
44
- queryUnique(ourQuery: FirestoreQuery<Type>): Promise<Type | undefined>;
45
- insert(instance: Type, _id?: string): Promise<Type>;
46
- insertAll(instances: Type[]): Promise<Awaited<Type>[]>;
47
- query(ourQuery: FirestoreQuery<Type>): Promise<Type[]>;
48
- upsert(instance: Type): Promise<Type>;
49
- upsertAll(instances: Type[]): Promise<Type[]>;
50
- patch(instance: Subset<Type>): Promise<Type>;
51
- deleteItem(instance: Type): Promise<Type | undefined>;
52
- deleteUnique(query: FirestoreQuery<Type>): Promise<Type | undefined>;
53
- delete(query: FirestoreQuery<Type>): Promise<Type[]>;
54
- private deleteBatch;
55
- deleteAll(): Promise<Type[]>;
56
- getAll(select?: Clause_Select<Type>): Promise<Type[]>;
57
- runInTransaction<ReturnType>(processor: (transaction: FirestoreTransaction) => Promise<ReturnType>): Promise<ReturnType>;
58
- createDocumentReference(_id?: string): FirebaseFirestore.DocumentReference<FirebaseFirestore.DocumentData, FirebaseFirestore.DocumentData>;
59
- getUniqueFilter: () => (object: Subset<Type>) => Clause_Where<Type>;
60
- newQueryUnique(ourQuery: FirestoreQuery<Type>): Promise<DocWrapper<Type> | undefined>;
61
- newQuery(ourQuery: FirestoreQuery<Type>): Promise<DocWrapper<Type>[]>;
62
- }
63
- export declare class DocWrapper<T extends TS_Object> {
64
- wrapper: FirestoreWrapperBE;
65
- doc: FirestoreType_DocumentSnapshot<T>;
66
- constructor(wrapper: FirestoreWrapperBE, doc: FirestoreType_DocumentSnapshot<T>);
67
- runInTransaction<R>(processor: (transaction: Transaction) => Promise<R>): Promise<R>;
68
- delete: (transaction?: Transaction) => Promise<T>;
69
- get: () => T;
70
- set: (instance: T, transaction?: Transaction) => Promise<T>;
139
+ runTransaction: <ReturnType>(processor: () => Promise<ReturnType>) => Promise<ReturnType>;
140
+ runTransactionInChunks: <T = any, R = any>(items: T[], processor: (chunk: typeof items) => Promise<R[]>, chunkSize?: number) => Promise<R[]>;
141
+ getVersion: () => any;
142
+ needsUpgrade: (version?: string) => boolean;
143
+ validateItem(dbItem: Proto['dbType']): void;
144
+ protected onValidationError(instance: Proto['dbType'], results: InvalidResult<Proto['dbType']>): void;
145
+ private assertNoDuplicatedIds;
146
+ composeDbObjectUniqueId: (item: Proto["uiType"]) => string;
71
147
  }
148
+ /**
149
+ * If the collection has unique keys, assert they exist, and use them to generate the _id.
150
+ * In the case an _id already exists, verify it is not different from the uniqueKeys-generated _id.
151
+ */
152
+ export declare const assertUniqueId: <Proto extends DB_Prototype>(item: Proto["dbType"], keys: Proto["uniqueKeys"]) => Proto["dbType"]["_id"];
153
+ export {};