@pi-r/gcp 0.9.3 → 0.10.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/client/index.js CHANGED
@@ -54,26 +54,43 @@ function getApp(credential, isDB) {
54
54
  const { getStorage } = require('@firebase/storage');
55
55
  return getStorage(app);
56
56
  }
57
- {
58
- const { getApps, initializeApp, applicationDefault } = require('firebase-admin/app');
59
- let app;
60
- for (const item of getApps()) {
57
+ const adminApp = require('firebase-admin/app');
58
+ const clientApp = (0, types_1.isString)(credential.admin) || (0, types_1.isPlainObject)(credential.admin);
59
+ let app;
60
+ if (!clientApp) {
61
+ for (const item of adminApp.getApps()) {
61
62
  if (!isDB && storageBucket && item.name === storageBucket || isDB && databaseURL && item.name === databaseURL) {
62
63
  app = item;
63
64
  break;
64
65
  }
65
66
  }
66
- if (!app) {
67
- app = isDB ? ADMIN_DB[databaseURL || "[DEFAULT]"] ||= initializeApp({ credential: applicationDefault(), databaseURL }, databaseURL) : ADMIN_STORAGE[storageBucket || "[DEFAULT]"] ||= initializeApp({ credential: applicationDefault(), storageBucket }, storageBucket);
68
- }
69
67
  credential.admin = true;
70
- if (isDB) {
71
- const { getDatabase } = require('firebase-admin/database');
72
- return getDatabase(app);
68
+ }
69
+ if (!app) {
70
+ let key = (isDB ? databaseURL : storageBucket) || "[DEFAULT]";
71
+ if (clientApp) {
72
+ key += JSON.stringify(credential.admin);
73
+ }
74
+ app = isDB ? ADMIN_DB[key] : ADMIN_STORAGE[key];
75
+ if (!app) {
76
+ let lib, credentialApp;
77
+ if (clientApp) {
78
+ lib = require('firebase-admin');
79
+ credentialApp = adminApp.cert(credential.admin);
80
+ }
81
+ else {
82
+ lib = adminApp;
83
+ credentialApp = adminApp.applicationDefault();
84
+ }
85
+ app = isDB ? ADMIN_DB[key] = lib.initializeApp({ credential: credentialApp, databaseURL }, key) : ADMIN_STORAGE[key] = lib.initializeApp({ credential: credentialApp, storageBucket }, key);
73
86
  }
74
- const { getStorage } = require('firebase-admin/storage');
75
- return getStorage(app);
76
87
  }
88
+ if (isDB) {
89
+ const { getDatabase } = require('firebase-admin/database');
90
+ return getDatabase(app);
91
+ }
92
+ const { getStorage } = require('firebase-admin/storage');
93
+ return getStorage(app);
77
94
  }
78
95
  async function hasBucket(client) {
79
96
  const [exists] = await client.exists().catch((err) => isErrorConflict(err) ? [true] : [false]);
@@ -121,9 +138,10 @@ async function updatePredefinedAcl(acl, values, metadata = {}) {
121
138
  async function setUniformBucketLevel(bucket, enabled) {
122
139
  return bucket.setMetadata({ iamConfiguration: { uniformBucketLevelAccess: { enabled } } });
123
140
  }
141
+ const keyLimit = (limit) => limit > 0 ? `_${limit}` : '_0';
124
142
  const asFieldPath = (params) => Array.isArray(params) && params.every(item => typeof item === 'string') ? new firestore_1.FieldPath(...params) : params;
125
- const getFirebasePackageName = (credential) => credential.apiKey && credential.authDomain ? 'firebase' : 'firebase-admin';
126
- const isErrorConflict = (err) => (err instanceof Error) && err.code === 409;
143
+ const getFirebasePackageName = (credential) => credential.apiKey && credential.authDomain && credential ? 'firebase' : 'firebase-admin';
144
+ const isErrorConflict = (err) => err instanceof Error && err.code === 409;
127
145
  const hasGoogleAuth = (credential) => (0, types_1.isString)(credential.keyFilename || credential.keyFile || process.env.GOOGLE_APPLICATION_CREDENTIALS) || (0, types_1.isObject)(credential.credentials) || !!credential.authClient;
128
146
  function isFirebaseApp(credential, store, admin) {
129
147
  return !!(credential.apiKey && credential.authDomain && (store || credential.databaseURL) || credential.product === "firebase" && process.env.GOOGLE_APPLICATION_CREDENTIALS) && (!admin || !credential.admin);
@@ -136,12 +154,11 @@ function validateDatabase(credential, data) {
136
154
  }
137
155
  function createStorageClient(credential, bucket) {
138
156
  if (isFirebaseApp(credential, true)) {
139
- let packageName = '';
157
+ const packageName = getFirebasePackageName(credential);
140
158
  try {
141
159
  if (bucket) {
142
160
  credential.storageBucket = bucket;
143
161
  }
144
- packageName = getFirebasePackageName(credential);
145
162
  return getApp(credential, false);
146
163
  }
147
164
  catch (err) {
@@ -153,7 +170,7 @@ function createStorageClient(credential, bucket) {
153
170
  return new storage_1.Storage(credential);
154
171
  }
155
172
  function createDatabaseClient(credential, data) {
156
- let packageName = '';
173
+ let packageName;
157
174
  try {
158
175
  if (data?.product === "firebase" || isFirebaseApp(credential)) {
159
176
  packageName = getFirebasePackageName(credential);
@@ -413,14 +430,12 @@ async function executeBatchQuery(credential, batch, sessionKey) {
413
430
  const clients = {};
414
431
  const caching = length > 0 && this.hasCache(batch[0].service, sessionKey);
415
432
  const cacheValue = { value: this.valueOfKey(credential, 'cache'), sessionKey };
416
- const authItems = [];
417
433
  const createClient = (data, type) => {
418
434
  const client = clients[type];
419
435
  if (client) {
420
436
  return client;
421
437
  }
422
438
  const item = length === 1 ? credential : { ...credential };
423
- authItems.push(item);
424
439
  return (clients[type] = createDatabaseClient.call(this, item, data));
425
440
  };
426
441
  const closeClient = () => {
@@ -454,7 +469,7 @@ async function executeBatchQuery(credential, batch, sessionKey) {
454
469
  };
455
470
  for (let i = 0; i < length; ++i) {
456
471
  const item = batch[i];
457
- const { service, product, name, database, table, id, query, limit = 0, update, ignoreCache } = item;
472
+ const { service, product, name, database, table, id, query, limit = 0, flags = 0, update, ignoreCache } = item;
458
473
  const useCache = caching && ignoreCache !== true;
459
474
  cacheValue.exclusiveOf = Array.isArray(ignoreCache) ? ignoreCache : undefined;
460
475
  let rows, queryString = '';
@@ -526,7 +541,7 @@ async function executeBatchQuery(credential, batch, sessionKey) {
526
541
  }
527
542
  const { updateType, options = {} } = item;
528
543
  if (useCache) {
529
- queryString = name + '_' + database + (table ? '_' + table + (columns ? Cloud.asString(columns, true) : '') : '') + '_' + Cloud.asString(query, true) + (limit > 0 ? '_' + limit : '');
544
+ queryString = name + '_' + database + (table ? '_' + table + (columns ? Cloud.asString(columns, true) : '') : '') + '_' + Cloud.asString(query, true) + keyLimit(limit);
530
545
  if (!update && (rows = this.getCacheResult(service, credential, queryString, cacheValue, ignoreCache))) {
531
546
  result[i] = rows;
532
547
  continue;
@@ -672,7 +687,7 @@ async function executeBatchQuery(credential, batch, sessionKey) {
672
687
  throw (0, util_1.formatError)(item, "Missing database query");
673
688
  }
674
689
  if (useCache) {
675
- queryString = (keys ? Cloud.asString(keys, true) : (kind ? (name ? '_' + name : '') + '_' + Cloud.asString(kind, true) : '') + '_' + Cloud.asString(query, true)) + (limit > 0 ? '_' + limit : 0) + (options ? Cloud.asString(options, true) : '');
690
+ queryString = (keys ? Cloud.asString(keys, true) : (kind ? (name ? '_' + name : '') + '_' + Cloud.asString(kind, true) : '') + '_' + Cloud.asString(query, true) + keyLimit(limit)) + (options ? Cloud.asString(options, true) : '');
676
691
  if (!update && (rows = this.getCacheResult(service, credential, queryString, cacheValue, ignoreCache))) {
677
692
  result[i] = rows;
678
693
  continue;
@@ -742,7 +757,16 @@ async function executeBatchQuery(credential, batch, sessionKey) {
742
757
  closeClient();
743
758
  throw (0, util_1.formatError)(item, "Missing database table");
744
759
  }
745
- if (id) {
760
+ const db = createClient(item, 3);
761
+ const group = !table.includes('/') && (flags & 1) === 1;
762
+ if (item.aggregateSpec) {
763
+ const col = group ? db.collectionGroup(table) : db.collection(table);
764
+ const data = (await col.aggregate(item.aggregateSpec).get()).data();
765
+ if (data) {
766
+ rows = [data];
767
+ }
768
+ }
769
+ else if ((0, types_1.isString)(id) || Array.isArray(id)) {
746
770
  const options = Array.isArray(id) ? item.options : null;
747
771
  if (useCache) {
748
772
  queryString = table + '_' + id + (options ? Cloud.asString(options, true) : '');
@@ -751,7 +775,6 @@ async function executeBatchQuery(credential, batch, sessionKey) {
751
775
  continue;
752
776
  }
753
777
  }
754
- const db = createClient(item, 3);
755
778
  if (Array.isArray(id)) {
756
779
  const items = id.map(ref => db.doc(table + '/' + ref));
757
780
  if (options) {
@@ -762,7 +785,7 @@ async function executeBatchQuery(credential, batch, sessionKey) {
762
785
  else {
763
786
  const doc = db.collection(table).doc(id);
764
787
  if ((0, types_1.isPlainObject)(update)) {
765
- const pattern = /^__(delete|serverTimestamp|increment|vector|(?:array)(?:Union|Remove))(?:<(.+)>)?__$/s;
788
+ const pattern = /^__(delete|serverTimestamp|increment|vector|array(?:Union|Remove))(?:<(.+)>)?__$/s;
766
789
  for (const attr in update) {
767
790
  const match = pattern.exec(attr);
768
791
  if (match) {
@@ -832,81 +855,115 @@ async function executeBatchQuery(credential, batch, sessionKey) {
832
855
  }
833
856
  }
834
857
  }
835
- else if (Array.isArray(query)) {
836
- const db = createClient(item, 3).collection(table);
837
- if (item.params && query.every(value => typeof value === 'number')) {
838
- const options = item.options;
839
- if (useCache && (rows = this.getCacheResult(service, credential, queryString = table + '_' + Cloud.asString(item.params, true) + '_' + Cloud.asString(query, true) + (options ? Cloud.asString(options, true) : ''), cacheValue, ignoreCache))) {
840
- result[i] = rows;
841
- continue;
842
- }
843
- rows = (await db.findNearest(asFieldPath(item.params), firestore_1.FieldValue.vector(query), options).get()).docs.map(doc => doc.data());
844
- }
845
- else {
846
- const orderBy = item.orderBy;
847
- if (useCache && (rows = this.getCacheResult(service, credential, queryString = table + '_' + Cloud.asString(query, true) + '_' + Cloud.asString(orderBy, true) + (limit > 0 ? '_' + limit : ''), cacheValue, ignoreCache))) {
848
- result[i] = rows;
849
- continue;
858
+ else {
859
+ const options = item.options || {};
860
+ if (options.vectorField || item.params || Array.isArray(query)) {
861
+ if (options.vectorField) {
862
+ if (Array.isArray(query)) {
863
+ options.queryVector ||= firestore_1.FieldValue.vector(query);
864
+ }
865
+ if (limit > 0) {
866
+ options.limit = limit;
867
+ }
868
+ rows = (await db.collection(table).findNearest(options).get()).docs.map(doc => doc.data());
850
869
  }
851
- let target = db;
852
- for (const args of query) {
853
- if ((0, types_1.isArray)(args)) {
854
- const method = args[0];
855
- const params = args.slice(1);
856
- switch (method) {
857
- case 'whereAnd':
858
- case 'whereOr':
859
- if (!params.every(p => (0, types_1.isArray)(p))) {
860
- throw (0, types_1.errorMessage)(method, 'Conditionals are not in array format', Cloud.asString(params));
861
- }
862
- target = target.where(firestore_1.Filter[method === 'whereAnd' ? 'and' : 'or'](...params.map((p) => firestore_1.Filter.where(p[0], p[1], p[2]))));
863
- break;
864
- case 'findNearest':
865
- params[0] = asFieldPath(params[0]);
866
- target = target.findNearest.apply(target, params).query;
867
- break;
868
- case 'where':
869
- case 'orderBy':
870
- params[0] = asFieldPath(params[0]);
871
- target = target[method].apply(target, params);
872
- case 'select':
873
- params.forEach((field, index) => {
874
- params[index] = asFieldPath(field);
875
- });
876
- target = target.select.apply(target, params);
877
- break;
878
- case 'endAt':
879
- case 'endBefore':
880
- case 'limit':
881
- case 'limitToLast':
882
- case 'offset':
883
- case 'startAfter':
884
- case 'startAt':
885
- case 'withConverter':
886
- target = target[method].apply(target, params);
887
- break;
870
+ else if (item.params && (options.queryVector || Array.isArray(query) && query.every(value => typeof value === 'number'))) {
871
+ if (!options.queryVector) {
872
+ if (useCache && (rows = this.getCacheResult(service, credential, queryString = table + '_' + Cloud.asString(item.params, true) + '_' + Cloud.asString(query, true) + (options ? Cloud.asString(options, true) : '') + keyLimit(limit), cacheValue, ignoreCache))) {
873
+ result[i] = rows;
874
+ continue;
888
875
  }
876
+ options.queryVector = firestore_1.FieldValue.vector(query);
889
877
  }
878
+ options.vectorField = asFieldPath(item.params);
879
+ if (limit > 0) {
880
+ options.limit = limit;
881
+ }
882
+ rows = (await db.collection(table).findNearest(options).get()).docs.map(doc => doc.data());
890
883
  }
891
- if (Array.isArray(orderBy)) {
892
- for (const order of orderBy) {
893
- if ((0, types_1.isArray)(order)) {
894
- target = target.orderBy(asFieldPath(order[0]), order[1]);
884
+ else {
885
+ const orderBy = item.orderBy;
886
+ if (useCache && (rows = this.getCacheResult(service, credential, queryString = table + '_' + Cloud.asString(query, true) + '_' + Cloud.asString(orderBy, true) + `_${group ? '1' : '0'}_${typeof id === 'number' ? '1' : '0'}` + keyLimit(limit), cacheValue, ignoreCache))) {
887
+ result[i] = rows;
888
+ continue;
889
+ }
890
+ const targets = [];
891
+ if (group) {
892
+ const col = db.collectionGroup(table);
893
+ if (typeof id === 'number') {
894
+ for await (const partition of col.getPartitions(id)) {
895
+ targets.push(partition.toQuery());
896
+ }
895
897
  }
896
- else if ((0, types_1.isString)(order)) {
897
- target = target.orderBy(order);
898
+ else {
899
+ targets.push(col);
898
900
  }
899
901
  }
902
+ else {
903
+ targets.push(db.collection(table));
904
+ }
905
+ rows = [];
906
+ for (let target of targets) {
907
+ for (const args of query) {
908
+ if ((0, types_1.isArray)(args)) {
909
+ const method = args[0];
910
+ const params = args.slice(1);
911
+ switch (method) {
912
+ case 'whereAnd':
913
+ case 'whereOr':
914
+ if (!params.every(p => (0, types_1.isArray)(p))) {
915
+ throw (0, types_1.errorMessage)(method, 'Conditionals are not in array format', Cloud.asString(params));
916
+ }
917
+ target = target.where(firestore_1.Filter[method === 'whereAnd' ? 'and' : 'or'](...params.map((p) => firestore_1.Filter.where(p[0], p[1], p[2]))));
918
+ break;
919
+ case 'findNearest':
920
+ params[0] = asFieldPath(params[0]);
921
+ target = target.findNearest.apply(target, params).query;
922
+ break;
923
+ case 'where':
924
+ case 'orderBy':
925
+ params[0] = asFieldPath(params[0]);
926
+ target = target[method].apply(target, params);
927
+ case 'select':
928
+ params.forEach((field, index) => {
929
+ params[index] = asFieldPath(field);
930
+ });
931
+ target = target.select.apply(target, params);
932
+ break;
933
+ case 'endAt':
934
+ case 'endBefore':
935
+ case 'limit':
936
+ case 'limitToLast':
937
+ case 'offset':
938
+ case 'startAfter':
939
+ case 'startAt':
940
+ case 'withConverter':
941
+ target = target[method].apply(target, params);
942
+ break;
943
+ }
944
+ }
945
+ }
946
+ if (Array.isArray(orderBy)) {
947
+ for (const order of orderBy) {
948
+ if ((0, types_1.isArray)(order)) {
949
+ target = target.orderBy(asFieldPath(order[0]), order[1]);
950
+ }
951
+ else if ((0, types_1.isString)(order)) {
952
+ target = target.orderBy(order);
953
+ }
954
+ }
955
+ }
956
+ if (limit > 0) {
957
+ target = target.limit(limit);
958
+ }
959
+ rows = rows.concat((await target.get()).docs.map(doc => doc.data()));
960
+ }
900
961
  }
901
- if (limit > 0) {
902
- target = target.limit(limit);
903
- }
904
- rows = (await target.get()).docs.map(doc => doc.data());
905
962
  }
906
- }
907
- else {
908
- closeClient();
909
- throw (0, util_1.formatError)(item, "Missing database query");
963
+ else {
964
+ closeClient();
965
+ throw (0, util_1.formatError)(item, "Missing database query");
966
+ }
910
967
  }
911
968
  }
912
969
  else {
@@ -1041,6 +1098,7 @@ function createBucketRequest(credential) {
1041
1098
  case 'location':
1042
1099
  case 'locationType':
1043
1100
  case 'logging':
1101
+ case 'generation':
1044
1102
  case 'metageneration':
1045
1103
  case 'name':
1046
1104
  case 'objectRetention':
@@ -1048,6 +1106,8 @@ function createBucketRequest(credential) {
1048
1106
  case 'projectNumber':
1049
1107
  case 'retentionPolicy':
1050
1108
  case 'rpo':
1109
+ case 'softDeleteTime':
1110
+ case 'hardDeleteTime':
1051
1111
  case 'softDeletePolicy':
1052
1112
  case 'storageClass':
1053
1113
  case 'timeCreated':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-r/gcp",
3
- "version": "0.9.3",
3
+ "version": "0.10.0",
4
4
  "description": "Google (GCP) cloud functions for E-mc.",
5
5
  "main": "client/index.js",
6
6
  "publishConfig": {
@@ -19,10 +19,10 @@
19
19
  "license": "MIT",
20
20
  "homepage": "https://github.com/anpham6/pi-r#readme",
21
21
  "dependencies": {
22
- "@e-mc/cloud": "^0.11.4",
23
- "@e-mc/module": "^0.11.4",
24
- "@e-mc/types": "^0.11.4",
22
+ "@e-mc/cloud": "^0.12.0",
23
+ "@e-mc/module": "^0.12.0",
24
+ "@e-mc/types": "^0.12.0",
25
25
  "@google-cloud/firestore": "^7.11.0",
26
- "@google-cloud/storage": "^7.15.2"
26
+ "@google-cloud/storage": "^7.16.0"
27
27
  }
28
28
  }
package/types/index.d.ts CHANGED
@@ -2,8 +2,10 @@ import type { CloudDatabase, CloudStorage } from '@e-mc/types/lib/cloud';
2
2
 
3
3
  import type { StorageOptions } from '@google-cloud/storage';
4
4
  import type { PathType } from '@google-cloud/datastore';
5
+ import type { AggregateSpec } from '@google-cloud/firestore';
5
6
  import type { entity } from '@google-cloud/datastore/build/src/entity';
6
7
  import type { FirebaseOptions as IFirebaseOptions } from '@firebase/app';
8
+ import type { ServiceAccount } from 'firebase-admin/app';
7
9
 
8
10
  // @ts-ignore
9
11
  import type { GoogleAuthOptions } from 'google-auth-library';
@@ -17,19 +19,21 @@ export interface GCPDatabaseQuery<T = PlainObject> extends Omit<CloudDatabase<un
17
19
  service: "gcp" | "gcloud";
18
20
  product?: "firestore" | "firebase" | "bigquery" | "bigtable" | "datastore" | "spanner";
19
21
  database?: string;
20
- id?: string | string[];
22
+ id?: string | string[] | number;
21
23
  updateType?: 0 | 1 | 2 | 3;
22
24
  columns?: string[];
23
25
  keys?: DatastoreKey | DatastoreKey[];
24
26
  kind?: string | string[];
25
27
  orderBy?: unknown[][];
28
+ aggregateSpec?: AggregateSpec;
29
+ flags?: number;
26
30
  }
27
31
 
28
32
  export type GCPDatabaseCredential = GoogleAuthOptions;
29
33
 
30
34
  export interface FirebaseOptions extends IFirebaseOptions {
31
35
  product?: "firebase";
32
- admin?: boolean;
36
+ admin?: boolean | string | ServiceAccount;
33
37
  }
34
38
 
35
39
  export type DatastoreKey = string | PathType[] | entity.KeyOptions;
package/upload/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  const path = require("node:path");
3
3
  const fs = require("node:fs");
4
4
  const stream = require("node:stream");
5
+ const storage_1 = require("@google-cloud/storage");
5
6
  const Cloud = require("@e-mc/cloud");
6
7
  const types_1 = require("@e-mc/types");
7
8
  const util_1 = require("@e-mc/cloud/util");
@@ -61,9 +62,9 @@ function upload(credential, service = 'gcp') {
61
62
  this.formatMessage(64, service, ["Bucket configured" + ` (${feature})`, bucket], message, { ...Cloud.LOG_CLOUD_COMMAND });
62
63
  };
63
64
  if (typeof retentionPolicy === 'number') {
64
- bucketClient.setRetentionPeriod(retentionPolicy)
65
+ (retentionPolicy === 0 ? bucketClient.removeRetentionPeriod() : bucketClient.setRetentionPeriod(retentionPolicy))
65
66
  .then(([response]) => {
66
- commandMessage('Retention Policy', response.retentionPolicy?.effectiveTime);
67
+ commandMessage('Retention Policy', response.retentionPolicy?.[retentionPolicy === 0 ? 'retentionPeriod' : 'effectiveTime']);
67
68
  })
68
69
  .catch(addLog);
69
70
  }
@@ -298,11 +299,10 @@ function upload(credential, service = 'gcp') {
298
299
  params.predefinedAcl = acl;
299
300
  }
300
301
  }
301
- const file = bucketClient.file(destination);
302
+ const file = bucketClient.file(destination, params);
302
303
  let uploadClient;
303
304
  if (Path[i]) {
304
- const { TransferManager } = require('@google-cloud/storage');
305
- uploadClient = new TransferManager(bucketClient).uploadFileInChunks(Path[i], { uploadName: destination, chunkSizeBytes, headers, concurrencyLimit: data.upload.chunkLimit || concurrencyLimit, maxQueueSize });
305
+ uploadClient = new storage_1.TransferManager(bucketClient).uploadFileInChunks(Path[i], { uploadName: destination, chunkSizeBytes, headers, concurrencyLimit: data.upload.chunkLimit || concurrencyLimit, maxQueueSize });
306
306
  }
307
307
  else {
308
308
  params.resumable ??= false;
@@ -316,8 +316,11 @@ function upload(credential, service = 'gcp') {
316
316
  .on('error', reject);
317
317
  });
318
318
  }
319
+ else {
320
+ uploadClient = file.save(Body[i], params);
321
+ }
319
322
  }
320
- (uploadClient || file.save(Body[i], params))
323
+ uploadClient
321
324
  .then(async () => {
322
325
  const url = getURL(file.name);
323
326
  this.formatMessage(64, service, "Upload success", url, { ...Cloud.LOG_CLOUD_UPLOAD });