расширение распределенного счетчика firebase в облачных функциях

#javascript #firebase #google-cloud-firestore #google-cloud-functions #firebase-extensions

#javascript #firebase #google-облако-firestore #google-cloud-функции #firebase-расширения

Вопрос:

Я пытаюсь реализовать расширение распределенного счетчика Firebase в облачной функции. Идея состоит в том, чтобы определять, когда документ создан, затем добавляется счетчик внутри документа. Ниже приведена моя облачная функция:

 exports.bookmark_increment = functions.firestore.document('questions/{question_id}/bookmarks/{user_uid}')
  .onCreate(async (snap, context) => {

    // compiled client sample code for increment counter
  var sharded = function(t) { var e = {}; function r(n) { if (e[n]) return e[n].exports; var o = e[n] = { i: n, l: !1, exports: {} }; return t[n].call(o.exports, o, o.exports, r), o.l = !0, o.exports } return r.m = t, r.c = e, r.d = function(t, e, n) { r.o(t, e) || Object.defineProperty(t, e, { enumerable: !0, get: n }) }, r.r = function(t) { "undefined" != typeof Symbol amp;amp; Symbol.toStringTag amp;amp; Object.defineProperty(t, Symbol.toStringTag, { value: "Module" }), Object.defineProperty(t, "__esModule", { value: !0 }) }, r.t = function(t, e) { if (1 amp; e amp;amp; (t = r(t)), 8 amp; e) return t; if (4 amp; e amp;amp; "object" == typeof t amp;amp; t amp;amp; t.__esModule) return t; var n = Object.create(null); if (r.r(n), Object.defineProperty(n, "default", { enumerable: !0, value: t }), 2 amp; e amp;amp; "string" != typeof t) for (var o in t) r.d(n, o, function(e) { return t[e] }.bind(null, o)); return n }, r.n = function(t) { var e = t amp;amp; t.__esModule ? function() { return t.default } : function() { return t }; return r.d(e, "a", e), e }, r.o = function(t, e) { return Object.prototype.hasOwnProperty.call(t, e) }, r.p = "", r(r.s = 2) }([function(t, e) { var r = "undefined" != typeof crypto amp;amp; crypto.getRandomValues amp;amp; crypto.getRandomValues.bind(crypto) || "undefined" != typeof msCrypto amp;amp; "function" == typeof window.msCrypto.getRandomValues amp;amp; msCrypto.getRandomValues.bind(msCrypto); if (r) { var n = new Uint8Array(16); t.exports = function() { return r(n), n } } else { var o = new Array(16); t.exports = function() { for (var t, e = 0; e < 16; e  )0 == (3 amp; e) amp;amp; (t = 4294967296 * Math.random()), o[e] = t >>> ((3 amp; e) << 3) amp; 255; return o } } }, function(t, e) { for (var r = [], n = 0; n < 256;   n)r[n] = (n   256).toString(16).substr(1); t.exports = function(t, e) { var n = e || 0, o = r; return [o[t[n  ]], o[t[n  ]], o[t[n  ]], o[t[n  ]], "-", o[t[n  ]], o[t[n  ]], "-", o[t[n  ]], o[t[n  ]], "-", o[t[n  ]], o[t[n  ]], "-", o[t[n  ]], o[t[n  ]], o[t[n  ]], o[t[n  ]], o[t[n  ]], o[t[n  ]]].join("") } }, function(t, e, r) { "use strict"; var n = this amp;amp; this.__awaiter || function(t, e, r, n) { return new (r || (r = Promise))(function(o, i) { function s(t) { try { a(n.next(t)) } catch (t) { i(t) } } function u(t) { try { a(n.throw(t)) } catch (t) { i(t) } } function a(t) { t.done ? o(t.value) : new r(function(e) { e(t.value) }).then(s, u) } a((n = n.apply(t, e || [])).next()) }) }, o = this amp;amp; this.__generator || function(t, e) { var r, n, o, i, s = { label: 0, sent: function() { if (1 amp; o[0]) throw o[1]; return o[1] }, trys: [], ops: [] }; return i = { next: u(0), throw: u(1), return: u(2) }, "function" == typeof Symbol amp;amp; (i[Symbol.iterator] = function() { return this }), i; function u(i) { return function(u) { return function(i) { if (r) throw new TypeError("Generator is already executing."); for (; s;)try { if (r = 1, n amp;amp; (o = 2 amp; i[0] ? n.return : i[0] ? n.throw || ((o = n.return) amp;amp; o.call(n), 0) : n.next) amp;amp; !(o = o.call(n, i[1])).done) return o; switch (n = 0, o amp;amp; (i = [2 amp; i[0], o.value]), i[0]) { case 0: case 1: o = i; break; case 4: return s.label  , { value: i[1], done: !1 }; case 5: s.label  , n = i[1], i = [0]; continue; case 7: i = s.ops.pop(), s.trys.pop(); continue; default: if (!(o = (o = s.trys).length > 0 amp;amp; o[o.length - 1]) amp;amp; (6 === i[0] || 2 === i[0])) { s = 0; continue } if (3 === i[0] amp;amp; (!o || i[1] > o[0] amp;amp; i[1] < o[3])) { s.label = i[1]; break } if (6 === i[0] amp;amp; s.label < o[1]) { s.label = o[1], o = i; break } if (o amp;amp; s.label < o[2]) { s.label = o[2], s.ops.push(i); break } o[2] amp;amp; s.ops.pop(), s.trys.pop(); continue }i = e.call(t, s) } catch (t) { i = [6, t], n = 0 } finally { r = o = 0 } if (5 amp; i[0]) throw i[1]; return { value: i[0] ? i[1] : void 0, done: !0 } }([i, u]) } } }; Object.defineProperty(e, "__esModule", { value: !0 }); var i = r(3), s = "_counter_shards_", u = "FIRESTORE_COUNTER_SHARD_ID", a = function() { function t(t, e) { this.doc = t, this.field = e, this.db = null, this.shardId = "", this.shards = {}, this.notifyPromise = null, this.db = t.firestore, this.shardId = function(t) { var e = new RegExp("(?:^|; )"   encodeURIComponent(t)   "=([^;]*)").exec(document.cookie); if (e) return e[1]; var r = i.v4(), n = new Date; n.setTime(n.getTime()   2592e6); var o = "; expires="   n.toUTCString(); return document.cookie = encodeURIComponent(t)   "="   r   o   "; path=/", r }(u); var r = t.collection(s); this.shards[t.path] = 0, this.shards[r.doc(this.shardId).path] = 0, this.shards[r.doc("t"   this.shardId.substr(0, 4)).path] = 0, this.shards[r.doc("tt"   this.shardId.substr(0, 3)).path] = 0, this.shards[r.doc("ttt"   this.shardId.substr(0, 2)).path] = 0, this.shards[r.doc("tttt"   this.shardId.substr(0, 1)).path] = 0 } return t.prototype.get = function(t) { return n(this, void 0, void 0, function() { var e, r = this; return o(this, function(i) { switch (i.label) { case 0: return e = Object.keys(this.shards).map(function(e) { return n(r, void 0, void 0, function() { return o(this, function(r) { switch (r.label) { case 0: return [4, this.db.doc(e).get(t)]; case 1: return [2, r.sent().get(this.field) || 0] } }) }) }), [4, Promise.all(e)]; case 1: return [2, i.sent().reduce(function(t, e) { return t   e }, 0)] } }) }) }, t.prototype.onSnapshot = function(t) { var e = this; Object.keys(this.shards).forEach(function(r) { e.db.doc(r).onSnapshot(function(r) { e.shards[r.ref.path] = r.get(e.field) || 0, null === e.notifyPromise amp;amp; (e.notifyPromise = function(t) { return n(this, void 0, void 0, function() { var e = this; return o(this, function(r) { return [2, new Promise(function(r) { return n(e, void 0, void 0, function() { var e = this; return o(this, function(i) { return setTimeout(function() { return n(e, void 0, void 0, function() { var e; return o(this, function(n) { return e = t(), r(e), [2] }) }) }, 0), [2] }) }) })] }) }) }(function() { var r = Object.values(e.shards).reduce(function(t, e) { return t   e }, 0); t({ exists: !0, data: function() { return r } }), e.notifyPromise = null })) }) }) }, t.prototype.incrementBy = function(t) { var e = firebase.firestore.FieldValue.increment(t), r = this.field.split(".").reverse().reduce(function(t, e) { var r; return (r = {})[e] = t, r }, e); return this.doc.collection(s).doc(this.shardId).set(r, { merge: !0 }) }, t.prototype.shard = function() { return this.doc.collection(s).doc(this.shardId) }, t }(); e.Counter = a }, function(t, e, r) { var n = r(4), o = r(5), i = o; i.v1 = n, i.v4 = o, t.exports = i }, function(t, e, r) { var n, o, i = r(0), s = r(1), u = 0, a = 0; t.exports = function(t, e, r) { var c = e amp;amp; r || 0, f = e || [], d = (t = t || {}).node || n, l = void 0 !== t.clockseq ? t.clockseq : o; if (null == d || null == l) { var h = i(); null == d amp;amp; (d = n = [1 | h[0], h[1], h[2], h[3], h[4], h[5]]), null == l amp;amp; (l = o = 16383 amp; (h[6] << 8 | h[7])) } var p = void 0 !== t.msecs ? t.msecs : (new Date).getTime(), v = void 0 !== t.nsecs ? t.nsecs : a   1, y = p - u   (v - a) / 1e4; if (y < 0 amp;amp; void 0 === t.clockseq amp;amp; (l = l   1 amp; 16383), (y < 0 || p > u) amp;amp; void 0 === t.nsecs amp;amp; (v = 0), v >= 1e4) throw new Error("uuid.v1(): Can't create more than 10M uuids/sec"); u = p, a = v, o = l; var b = (1e4 * (268435455 amp; (p  = 122192928e5))   v) % 4294967296; f[c  ] = b >>> 24 amp; 255, f[c  ] = b >>> 16 amp; 255, f[c  ] = b >>> 8 amp; 255, f[c  ] = 255 amp; b; var m = p / 4294967296 * 1e4 amp; 268435455; f[c  ] = m >>> 8 amp; 255, f[c  ] = 255 amp; m, f[c  ] = m >>> 24 amp; 15 | 16, f[c  ] = m >>> 16 amp; 255, f[c  ] = l >>> 8 | 128, f[c  ] = 255 amp; l; for (var g = 0; g < 6;   g)f[c   g] = d[g]; return e || s(f) } }, function(t, e, r) { var n = r(0), o = r(1); t.exports = function(t, e, r) { var i = e amp;amp; r || 0; "string" == typeof t amp;amp; (e = "binary" === t ? new Array(16) : null, t = null); var s = (t = t || {}).random || (t.rng || n)(); if (s[6] = 15 amp; s[6] | 64, s[8] = 63 amp; s[8] | 128, e) for (var u = 0; u < 16;   u)e[i   u] = s[u]; return e || o(s) } }]);
    
    const question_ref = db.collection('questions').doc(context.params.question_id);
    
    // Initialize the sharded counter.
    var bookmarks_count = new sharded.Counter(question_ref, "bookmarks_count");

    bookmarks_count.incrementBy(1);
  });
  

Идея заключается в том, что пользователь добавляет вопрос в закладки. Это создает новый документ внутри вложенной коллекции bookmarks внутри соответствующего документа вопроса. Затем функция обнаружит создание нового документа и обновит счетчик закладок на единицу. Я пытаюсь использовать расширение firebase для этого. Но почему-то счетчик не работает, когда я развертываю функцию. Что я здесь сделал не так?

Комментарии:

1. Неясно, используете ли вы расширение ИЛИ свою собственную облачную функцию. Или вы интегрировали в свою облачную функцию некоторые части кода расширения? Не могли бы вы, пожалуйста, уточнить?

2. Я интегрирую расширение в эту облачную функцию, скомпилированный клиентский код, я просто скопировал его из описания расширения

3. Вы чего-то не понимаете await . Firestore возвращает promise, и вы не обработали его должным образом. const question_ref = await db.collection('questions').doc(context.params.question_id); — измените эту строку @YugueChen

4. а также вы должны вернуть обещание из функции firebase.

5. @SushanSapaliga только что попробовал ваше решение, ошибка все еще сохраняется

Ответ №1:

Я хотел бы отметить, что расширения Firebase находятся в бета-версии [1]. В документе распределенного счетчика [2] нет информации о Node.js ; в нем упоминается, что его скомпилированный уменьшенный JavaScript [3] и говорится, что пользователи могут использовать предоставленный образец клиента или ваш собственный клиентский код, чтобы указать путь к вашему документу и увеличить значения [4].

Итак, когда мы проверяем документ распределенных счетчиков [5] на node.js пользователи могут увеличивать и получать общее количество счетчиков на Node.js .

[1] https://firebase.google.com/docs/extensions

[2] https://firebase.google.com/products/extensions/firestore-counter

[3] https://github.com/firebase/extensions/blob/master/firestore-counter/clients/web/dist/sharded-counter.js

[4] https://github.com/firebase/extensions/tree/master/firestore-counter

[5] https://firebase.google.com/docs/firestore/solutions/counters#node.js

Ответ №2:

Если вы похожи на нас и заботитесь только об увеличении счетчиков внутри облачной функции и читаете их во внешнем интерфейсе, вот упрощенная реализация, которую мы используем (в Typescript). Расширение Firestore заботится об агрегации.

 import * as uuid from 'uuid';
import { firestore } from 'firebase-admin/lib/firestore';

export class DistributedCounter {
  static async incrementBy(doc: firestore.DocumentReference, field: string, val: number): Promise<void> {
    const shardId = uuid.v4();
    const increment: any = firestore.FieldValue.increment(val);
    const update: { [key: string]: any } = field
      .split('.')
      .reverse()
      .reduce((value, name) => ({ [name]: value }), increment);
    await doc.collection('_counter_shards_').doc(shardId).set(update, { merge: true });
  }
}