emberflow 1.4.3 → 1.4.5

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/lib/index.js CHANGED
@@ -56,6 +56,7 @@ const firestore_1 = require("firebase-functions/v2/firestore");
56
56
  const https_1 = require("firebase-functions/v2/https");
57
57
  const functions_1 = require("./utils/functions");
58
58
  var FieldValue = firebase_admin_1.firestore.FieldValue;
59
+ const transaction_1 = require("./utils/transaction");
59
60
  exports.functionsConfig = {};
60
61
  exports.SUBMIT_FORM_TOPIC_NAME = "submit-form-queue";
61
62
  exports.VIEW_LOGICS_TOPIC_NAME = "view-logics-queue";
@@ -242,92 +243,104 @@ async function onFormSubmit(event) {
242
243
  await formRef.update({ "@status": "validation-error", "@messages": validationResult });
243
244
  return;
244
245
  }
245
- const document = (await exports.db.doc(docPath).get()).data() || {};
246
- const formModifiedFields = (0, index_utils_1.getFormModifiedFields)(form, document);
247
- let user;
248
- if (!isServiceAccount) {
249
- user = (await exports.db.doc(`users/${userId}`).get()).data();
250
- if (!user) {
251
- const message = "No user data found";
252
- console.warn(message);
253
- await formRef.update({ "@status": "error", "@messages": message });
254
- return;
246
+ let runStatus = { status: "running", logicResults: [] };
247
+ let errorMessage = "";
248
+ const actionRef = exports._mockable.initActionRef(formId);
249
+ await exports.db.runTransaction(async (txn) => {
250
+ const docRef = exports.db.doc(docPath);
251
+ const document = (await txn.get(docRef)).data() || {};
252
+ const formModifiedFields = (0, index_utils_1.getFormModifiedFields)(form, document);
253
+ let user;
254
+ if (!isServiceAccount) {
255
+ const userRef = exports.db.doc(`users/${userId}`);
256
+ user = (await txn.get(userRef)).data();
257
+ if (!user) {
258
+ const message = "No user data found";
259
+ console.warn(message);
260
+ await formRef.update({ "@status": "error", "@messages": message });
261
+ return;
262
+ }
255
263
  }
256
- }
257
- else {
258
- user = { "@id": userId };
259
- }
260
- console.info("Validating Security");
261
- const securityFn = (0, index_utils_1.getSecurityFn)(entity);
262
- if (securityFn) {
263
- const securityFnStart = performance.now();
264
- const securityResult = await securityFn(entity, docPath, document, actionType, formModifiedFields, user);
265
- const securityFnEnd = performance.now();
266
- const securityLogicResult = {
267
- name: "securityFn",
268
- status: "finished",
269
- documents: [],
270
- execTime: securityFnEnd - securityFnStart,
271
- };
272
- logicResults.push(securityLogicResult);
273
- if (securityResult.status === "rejected") {
274
- console.log(`Security check failed: ${securityResult.message}`);
275
- await formRef.update({ "@status": "security-error", "@messages": securityResult.message });
276
- return;
264
+ else {
265
+ user = { "@id": userId };
277
266
  }
278
- }
279
- // Check for delay
280
- console.info("Checking for delay");
281
- const delay = form["@delay"];
282
- if (delay) {
283
- const delayStart = performance.now();
284
- const cancelled = await (0, index_utils_1.delayFormSubmissionAndCheckIfCancelled)(delay, formRef);
285
- const delayEnd = performance.now();
286
- const delayLogicResult = {
287
- name: "delayFormSubmission",
267
+ console.info("Validating Security");
268
+ const securityFn = (0, index_utils_1.getSecurityFn)(entity);
269
+ if (securityFn) {
270
+ const securityFnStart = performance.now();
271
+ const securityResult = await securityFn(entity, docPath, document, actionType, formModifiedFields, user);
272
+ const securityFnEnd = performance.now();
273
+ const securityLogicResult = {
274
+ name: "securityFn",
275
+ status: "finished",
276
+ documents: [],
277
+ execTime: securityFnEnd - securityFnStart,
278
+ };
279
+ logicResults.push(securityLogicResult);
280
+ if (securityResult.status === "rejected") {
281
+ console.log(`Security check failed: ${securityResult.message}`);
282
+ await formRef.update({ "@status": "security-error", "@messages": securityResult.message });
283
+ return;
284
+ }
285
+ }
286
+ // Check for delay
287
+ console.info("Checking for delay");
288
+ const delay = form["@delay"];
289
+ if (delay) {
290
+ const delayStart = performance.now();
291
+ const cancelled = await (0, index_utils_1.delayFormSubmissionAndCheckIfCancelled)(delay, formRef);
292
+ const delayEnd = performance.now();
293
+ const delayLogicResult = {
294
+ name: "delayFormSubmission",
295
+ status: "finished",
296
+ documents: [],
297
+ execTime: delayEnd - delayStart,
298
+ };
299
+ logicResults.push(delayLogicResult);
300
+ if (cancelled) {
301
+ await formRef.update({ "@status": "cancelled" });
302
+ return;
303
+ }
304
+ }
305
+ await formRef.update({ "@status": "processing" });
306
+ const status = "processing";
307
+ const timeCreated = exports._mockable.createNowTimestamp();
308
+ console.info("Creating Action");
309
+ const eventContext = {
310
+ id: event.id,
311
+ uid: userId,
312
+ formId,
313
+ docId: entityId,
314
+ docPath,
315
+ entity,
316
+ };
317
+ const action = {
318
+ eventContext,
319
+ actionType,
320
+ document,
321
+ status,
322
+ timeCreated,
323
+ modifiedFields: formModifiedFields,
324
+ user,
325
+ };
326
+ await formRef.update({ "@status": "submitted" });
327
+ console.info("Running Business Logics");
328
+ const businessLogicStart = performance.now();
329
+ runStatus = await (0, index_utils_1.runBusinessLogics)((0, transaction_1.extractTransactionGetOnly)(txn), action);
330
+ const businessLogicEnd = performance.now();
331
+ const businessLogicLogicResult = {
332
+ name: "runBusinessLogics",
288
333
  status: "finished",
289
334
  documents: [],
290
- execTime: delayEnd - delayStart,
335
+ execTime: businessLogicEnd - businessLogicStart,
291
336
  };
292
- logicResults.push(delayLogicResult);
293
- if (cancelled) {
294
- await formRef.update({ "@status": "cancelled" });
295
- return;
296
- }
297
- }
298
- await formRef.update({ "@status": "processing" });
299
- const status = "processing";
300
- const timeCreated = exports._mockable.createNowTimestamp();
301
- console.info("Creating Action");
302
- const eventContext = {
303
- id: event.id,
304
- uid: userId,
305
- formId,
306
- docId: entityId,
307
- docPath,
308
- entity,
309
- };
310
- const action = {
311
- eventContext,
312
- actionType,
313
- document,
314
- status,
315
- timeCreated,
316
- modifiedFields: formModifiedFields,
317
- user,
318
- };
319
- const actionRef = exports._mockable.initActionRef(formId);
320
- await actionRef.set(action);
321
- await formRef.update({ "@status": "submitted" });
322
- console.info("Running Business Logics");
323
- let errorMessage = "";
324
- const businessLogicStart = performance.now();
325
- const runStatus = await (0, index_utils_1.runBusinessLogics)(actionRef, action, async (actionRef, logicResults, page) => {
337
+ logicResults.push(businessLogicLogicResult);
338
+ await actionRef.set(action);
326
339
  async function saveLogicResults() {
327
- for (let i = 0; i < logicResults.length; i++) {
328
- const _a = logicResults[i], { documents } = _a, logicResult = __rest(_a, ["documents"]);
340
+ for (let i = 0; i < runStatus.logicResults.length; i++) {
341
+ const _a = runStatus.logicResults[i], { documents } = _a, logicResult = __rest(_a, ["documents"]);
329
342
  const logicResultsRef = actionRef.collection("logicResults")
330
- .doc(`${actionRef.id}-${page}-${i}`);
343
+ .doc(`${actionRef.id}-${i}`);
331
344
  await logicResultsRef.set(logicResult);
332
345
  const documentsRef = logicResultsRef.collection("documents");
333
346
  for (let j = 0; j < documents.length; j++) {
@@ -337,228 +350,42 @@ async function onFormSubmit(event) {
337
350
  }
338
351
  await saveLogicResults();
339
352
  function updateErrorMessage() {
340
- const errorLogicResults = logicResults.filter((result) => result.status === "error");
353
+ const errorLogicResults = runStatus.logicResults.filter((result) => result.status === "error");
341
354
  if (errorLogicResults.length > 0) {
342
355
  errorMessage = errorMessage + errorLogicResults.map((result) => result.message).join("\n");
343
356
  }
344
357
  }
345
358
  updateErrorMessage();
346
- async function writeJournalEntriesFirst() {
347
- // Gather all logicResultDoc with journalEntries
348
- const logicResultDocsWithJournalEntries = logicResults
349
- .map((result) => result.documents).flat()
350
- .filter((logicResultDoc) => logicResultDoc.journalEntries &&
351
- logicResultDoc.action !== "delete");
352
- if (logicResultDocsWithJournalEntries.length === 0) {
353
- console.info("No journal entries to write");
354
- return;
355
- }
356
- const dstPathLogicDocsWithJournalEntriesMap = await (0, index_utils_1.expandConsolidateAndGroupByDstPath)(logicResultDocsWithJournalEntries);
357
- for (const [dstPath, logicDocs] of dstPathLogicDocsWithJournalEntriesMap) {
358
- const docId = dstPath.split("/").pop();
359
- if (!docId) {
360
- console.error("Dst path has no docId");
361
- continue;
362
- }
363
- for (const logicDoc of logicDocs) {
364
- const { doc, instructions, journalEntries, action, skipRunViewLogics, } = logicDoc;
365
- if (!journalEntries) {
366
- continue;
367
- }
368
- const accounts = new Set(journalEntries.map((entry) => entry.ledgerEntries).flat()
369
- .map((entry) => entry.account));
370
- if (Object.keys(doc || {}).some((key) => accounts.has(key))) {
371
- console.error("Doc cannot have keys that are the same as account names");
372
- continue;
373
- }
374
- if (Object.keys(instructions || {}).some((key) => accounts.has(key))) {
375
- console.error("Instructions cannot have keys that are the same as account names");
376
- continue;
377
- }
378
- for (let i = 0; i < journalEntries.length; i++) {
379
- const { ledgerEntries, recordEntry, equation, date, } = journalEntries[i];
380
- const consolidatedPerAccount = ledgerEntries
381
- .reduce((acc, entry) => {
382
- const { account } = entry;
383
- if (acc[account]) {
384
- acc[account].debit += entry.debit;
385
- acc[account].credit += entry.credit;
386
- }
387
- else {
388
- acc[account] = {
389
- debit: entry.debit,
390
- credit: entry.credit,
391
- };
392
- }
393
- return acc;
394
- }, {});
395
- // loop through keys of consolidatedPerAccount
396
- const totalCreditDebit = Object.entries(consolidatedPerAccount)
397
- .reduce((acc, [account, { debit, credit }]) => {
398
- return {
399
- debit: acc.debit + debit,
400
- credit: acc.credit + credit,
401
- };
402
- }, { debit: 0, credit: 0 });
403
- if (totalCreditDebit.debit !== totalCreditDebit.credit) {
404
- console.error("Debit and credit should be equal");
405
- continue;
406
- }
407
- await exports.db.runTransaction(async (transaction) => {
408
- const docRef = exports.db.doc(dstPath);
409
- const currData = (await transaction.get(docRef)).data();
410
- let instructionsDbValues;
411
- if (instructions) {
412
- instructionsDbValues = await (0, distribution_1.convertInstructionsToDbValues)(instructions);
413
- }
414
- const finalDoc = Object.assign(Object.assign({}, (doc ? doc : {})), (instructionsDbValues ? instructionsDbValues : {}));
415
- if (currData) {
416
- transaction.update(docRef, Object.assign(Object.assign({}, finalDoc), { "@forDeletionLater": true }));
417
- }
418
- else {
419
- transaction.set(docRef, Object.assign(Object.assign({}, finalDoc), { "@forDeletionLater": true }));
420
- }
421
- const [leftSide, ..._] = equation.split("=");
422
- Object.entries(consolidatedPerAccount).forEach(([account, { debit, credit }]) => {
423
- const increment = leftSide.includes(account) ? debit - credit : credit - debit;
424
- if (increment === 0) {
425
- transaction.update(docRef, {
426
- "@forDeletionLater": FieldValue.delete(),
427
- });
428
- return;
429
- }
430
- const accountVal = ((currData === null || currData === void 0 ? void 0 : currData[account]) || 0) + increment;
431
- if (accountVal < 0) {
432
- throw new Error("Account value cannot be negative");
433
- }
434
- finalDoc[account] = accountVal;
435
- transaction.update(docRef, {
436
- [account]: accountVal,
437
- "@forDeletionLater": FieldValue.delete(),
438
- });
439
- });
440
- if (Object.keys(finalDoc).length > 0 && !skipRunViewLogics &&
441
- ["create", "merge"].includes(action)) {
442
- logicDoc.doc = finalDoc;
443
- await (0, view_logics_1.queueRunViewLogics)(logicDoc);
444
- }
445
- if (recordEntry) {
446
- for (let j = 0; j < ledgerEntries.length; j++) {
447
- const { account, debit, credit, description } = ledgerEntries[j];
448
- const journalEntryId = docId + i;
449
- const ledgerEntryId = journalEntryId + j;
450
- const ledgerEntryDoc = Object.assign({ journalEntryId,
451
- account,
452
- credit,
453
- debit,
454
- equation,
455
- date }, (description && { description }));
456
- const entryRef = docRef.collection("@ledgers").doc(ledgerEntryId);
457
- transaction.set(entryRef, ledgerEntryDoc);
458
- }
459
- }
460
- });
461
- }
462
- }
463
- }
464
- }
465
- await writeJournalEntriesFirst();
466
- async function distributeTransactionalLogicResults() {
467
- const transactionalResults = logicResults.filter((result) => result.transactional);
468
- if (transactionalResults.length === 0) {
469
- console.info("No transactional logic results to distribute");
470
- return;
471
- }
472
- // We always distribute transactional results first
473
- const transactionalDstPathLogicDocsMap = await (0, index_utils_1.expandConsolidateAndGroupByDstPath)(transactionalResults.map((result) => result.documents).flat().filter((doc) => !doc.journalEntries || doc.journalEntries && doc.action === "delete"));
474
- // Write to firestore in one transaction
475
- await exports.db.runTransaction(async (transaction) => {
476
- for (const [dstPath, logicDocs] of transactionalDstPathLogicDocsMap) {
477
- for (const logicDoc of logicDocs) {
478
- const docRef = exports.db.doc(dstPath);
479
- const { action } = logicDoc;
480
- if (action === "create") {
481
- transaction.set(docRef, logicDoc.doc);
482
- }
483
- else if (action === "merge") {
484
- transaction.update(docRef, logicDoc.doc);
485
- }
486
- else if (action === "delete") {
487
- transaction.delete(docRef);
488
- }
489
- if (logicDoc.instructions) {
490
- const { updateData, removeData } = await (0, distribution_1.convertInstructionsToDbValues)(logicDoc.instructions);
491
- if (Object.keys(updateData).length > 0) {
492
- transaction.update(docRef, updateData);
493
- }
494
- if (Object.keys(removeData).length > 0) {
495
- transaction.update(docRef, removeData);
496
- }
497
- }
498
- }
499
- }
500
- });
501
- }
502
- await distributeTransactionalLogicResults();
503
- async function distributeNonTransactionalLogicResults() {
504
- const nonTransactionalResults = logicResults.filter((result) => !result.transactional);
505
- console.info(`Group logic docs by priority: ${nonTransactionalResults.length}`);
506
- const { highPriorityDocs, normalPriorityDocs, lowPriorityDocs } = nonTransactionalResults
507
- .map((result) => result.documents)
508
- .flat()
509
- .filter((doc) => !doc.journalEntries || doc.journalEntries && doc.action === "delete")
510
- .reduce((acc, doc) => {
511
- if (doc.priority === "high") {
512
- acc.highPriorityDocs.push(doc);
513
- }
514
- else if (!doc.priority || doc.priority === "normal") {
515
- acc.normalPriorityDocs.push(doc);
516
- }
517
- else {
518
- acc.lowPriorityDocs.push(doc);
519
- }
520
- return acc;
521
- }, {
522
- highPriorityDocs: [],
523
- normalPriorityDocs: [],
524
- lowPriorityDocs: [],
525
- });
526
- console.info(`Consolidating and Distributing High Priority Logic Results: ${highPriorityDocs.length}`);
527
- const highPriorityDstPathLogicDocsMap = await (0, index_utils_1.expandConsolidateAndGroupByDstPath)(highPriorityDocs);
528
- const { docsByDocPath: highPriorityDocsByDocPath, otherDocsByDocPath: highPriorityOtherDocsByDocPath, } = (0, index_utils_1.groupDocsByTargetDocPath)(highPriorityDstPathLogicDocsMap, docPath);
529
- await (0, index_utils_1.distribute)(highPriorityDocsByDocPath);
530
- await (0, index_utils_1.distribute)(highPriorityOtherDocsByDocPath);
531
- if (page === 0) {
532
- await formRef.update({ "@status": "finished" });
533
- }
534
- console.info(`Consolidating and Distributing Normal Priority Logic Results: ${normalPriorityDocs.length}`);
535
- const normalPriorityDstPathLogicDocsMap = await (0, index_utils_1.expandConsolidateAndGroupByDstPath)(normalPriorityDocs);
536
- const { docsByDocPath: normalPriorityDocsByDocPath, otherDocsByDocPath: normalPriorityOtherDocsByDocPath, } = (0, index_utils_1.groupDocsByTargetDocPath)(normalPriorityDstPathLogicDocsMap, docPath);
537
- await (0, index_utils_1.distribute)(normalPriorityDocsByDocPath);
538
- await (0, index_utils_1.distributeLater)(normalPriorityOtherDocsByDocPath);
539
- console.info(`Consolidating and Distributing Low Priority Logic Results: ${lowPriorityDocs.length}`);
540
- const lowPriorityDstPathLogicDocsMap = await (0, index_utils_1.expandConsolidateAndGroupByDstPath)(lowPriorityDocs);
541
- const { docsByDocPath: lowPriorityDocsByDocPath, otherDocsByDocPath: lowPriorityOtherDocsByDocPath, } = (0, index_utils_1.groupDocsByTargetDocPath)(lowPriorityDstPathLogicDocsMap, docPath);
542
- await (0, index_utils_1.distributeLater)(lowPriorityDocsByDocPath);
543
- await (0, index_utils_1.distributeLater)(lowPriorityOtherDocsByDocPath);
544
- }
545
- await distributeNonTransactionalLogicResults();
359
+ const distributeTransactionalLogicResultsStart = performance.now();
360
+ await distributeFnTransactional(txn, runStatus.logicResults);
361
+ const distributeTransactionalLogicResultsEnd = performance.now();
362
+ const distributeTransactionalLogicResults = {
363
+ name: "distributeTransactionalLogicResults",
364
+ status: "finished",
365
+ documents: [],
366
+ execTime: distributeTransactionalLogicResultsEnd - distributeTransactionalLogicResultsStart,
367
+ };
368
+ logicResults.push(distributeTransactionalLogicResults);
546
369
  });
547
- const businessLogicEnd = performance.now();
548
- const businessLogicLogicResult = {
549
- name: "runBusinessLogics",
370
+ const distributeNonTransactionalLogicResultsStart = performance.now();
371
+ await distributeNonTransactionalLogicResults(runStatus.logicResults, docPath);
372
+ const distributeNonTransactionalLogicResultsEnd = performance.now();
373
+ const distributeNonTransactionalPerfLogicResults = {
374
+ name: "distributeNonTransactionalLogicResults",
550
375
  status: "finished",
551
376
  documents: [],
552
- execTime: businessLogicEnd - businessLogicStart,
377
+ execTime: distributeNonTransactionalLogicResultsEnd - distributeNonTransactionalLogicResultsStart,
553
378
  };
554
- logicResults.push(businessLogicLogicResult);
555
- if (runStatus === "cancel-then-retry") {
556
- await formRef.update({ "@status": "cancelled", "@messages": "cancel-then-retry received " +
557
- "from business logic" });
558
- return;
559
- }
379
+ logicResults.push(distributeNonTransactionalPerfLogicResults);
380
+ await formRef.update({ "@status": "finished" });
560
381
  const end = performance.now();
561
382
  const execTime = end - start;
383
+ if (errorMessage) {
384
+ await actionRef.update({ status: "finished-with-error", message: errorMessage, execTime: execTime });
385
+ }
386
+ else {
387
+ await actionRef.update({ status: "finished", execTime: execTime });
388
+ }
562
389
  const onFormSubmitLogicResult = {
563
390
  name: "onFormSubmit",
564
391
  status: "finished",
@@ -566,12 +393,7 @@ async function onFormSubmit(event) {
566
393
  execTime,
567
394
  };
568
395
  logicResults.push(onFormSubmitLogicResult);
569
- if (errorMessage) {
570
- await actionRef.update({ status: "finished-with-error", message: errorMessage, execTime: execTime });
571
- }
572
- else {
573
- await actionRef.update({ status: "finished", execTime: execTime });
574
- }
396
+ await index_utils_1._mockable.createMetricExecution(logicResults);
575
397
  console.info("Finished");
576
398
  }
577
399
  catch (error) {
@@ -587,9 +409,202 @@ async function onFormSubmit(event) {
587
409
  logicResults.push(onFormSubmitLogicResult);
588
410
  await formRef.update({ "@status": "error", "@messages": error, "execTime": execTime });
589
411
  }
590
- await index_utils_1._mockable.createMetricExecution(logicResults);
591
412
  }
592
413
  exports.onFormSubmit = onFormSubmit;
414
+ async function distributeNonTransactionalLogicResults(logicResults, docPath) {
415
+ const nonTransactionalResults = logicResults.filter((result) => !result.transactional);
416
+ console.info(`Group logic docs by priority: ${nonTransactionalResults.length}`);
417
+ const { highPriorityDocs, normalPriorityDocs, lowPriorityDocs } = nonTransactionalResults
418
+ .map((result) => result.documents)
419
+ .flat()
420
+ .filter((doc) => !doc.journalEntries || doc.journalEntries && doc.action === "delete")
421
+ .reduce((acc, doc) => {
422
+ if (doc.priority === "high") {
423
+ acc.highPriorityDocs.push(doc);
424
+ }
425
+ else if (!doc.priority || doc.priority === "normal") {
426
+ acc.normalPriorityDocs.push(doc);
427
+ }
428
+ else {
429
+ acc.lowPriorityDocs.push(doc);
430
+ }
431
+ return acc;
432
+ }, {
433
+ highPriorityDocs: [],
434
+ normalPriorityDocs: [],
435
+ lowPriorityDocs: [],
436
+ });
437
+ console.info(`Consolidating and Distributing High Priority Logic Results: ${highPriorityDocs.length}`);
438
+ const highPriorityDstPathLogicDocsMap = await (0, index_utils_1.expandConsolidateAndGroupByDstPath)(highPriorityDocs);
439
+ const { docsByDocPath: highPriorityDocsByDocPath, otherDocsByDocPath: highPriorityOtherDocsByDocPath, } = (0, index_utils_1.groupDocsByTargetDocPath)(highPriorityDstPathLogicDocsMap, docPath);
440
+ await (0, index_utils_1.distributeFnNonTransactional)(highPriorityDocsByDocPath);
441
+ await (0, index_utils_1.distributeFnNonTransactional)(highPriorityOtherDocsByDocPath);
442
+ console.info(`Consolidating and Distributing Normal Priority Logic Results: ${normalPriorityDocs.length}`);
443
+ const normalPriorityDstPathLogicDocsMap = await (0, index_utils_1.expandConsolidateAndGroupByDstPath)(normalPriorityDocs);
444
+ const { docsByDocPath: normalPriorityDocsByDocPath, otherDocsByDocPath: normalPriorityOtherDocsByDocPath, } = (0, index_utils_1.groupDocsByTargetDocPath)(normalPriorityDstPathLogicDocsMap, docPath);
445
+ await (0, index_utils_1.distributeFnNonTransactional)(normalPriorityDocsByDocPath);
446
+ await (0, index_utils_1.distributeLater)(normalPriorityOtherDocsByDocPath);
447
+ console.info(`Consolidating and Distributing Low Priority Logic Results: ${lowPriorityDocs.length}`);
448
+ const lowPriorityDstPathLogicDocsMap = await (0, index_utils_1.expandConsolidateAndGroupByDstPath)(lowPriorityDocs);
449
+ const { docsByDocPath: lowPriorityDocsByDocPath, otherDocsByDocPath: lowPriorityOtherDocsByDocPath, } = (0, index_utils_1.groupDocsByTargetDocPath)(lowPriorityDstPathLogicDocsMap, docPath);
450
+ await (0, index_utils_1.distributeLater)(lowPriorityDocsByDocPath);
451
+ await (0, index_utils_1.distributeLater)(lowPriorityOtherDocsByDocPath);
452
+ }
453
+ async function distributeFnTransactional(txn, logicResults) {
454
+ async function writeJournalEntriesFirst() {
455
+ // Gather all logicResultDoc with journalEntries
456
+ const logicResultDocsWithJournalEntries = logicResults
457
+ .map((result) => result.documents).flat()
458
+ .filter((logicResultDoc) => logicResultDoc.journalEntries &&
459
+ logicResultDoc.action !== "delete");
460
+ if (logicResultDocsWithJournalEntries.length === 0) {
461
+ console.info("No journal entries to write");
462
+ return;
463
+ }
464
+ const dstPathLogicDocsWithJournalEntriesMap = await (0, index_utils_1.expandConsolidateAndGroupByDstPath)(logicResultDocsWithJournalEntries);
465
+ for (const [dstPath, logicDocs] of dstPathLogicDocsWithJournalEntriesMap) {
466
+ const docId = dstPath.split("/").pop();
467
+ if (!docId) {
468
+ console.error("Dst path has no docId");
469
+ continue;
470
+ }
471
+ for (const logicDoc of logicDocs) {
472
+ const { doc, instructions, journalEntries, action, skipRunViewLogics, } = logicDoc;
473
+ if (!journalEntries) {
474
+ continue;
475
+ }
476
+ const accounts = new Set(journalEntries.map((entry) => entry.ledgerEntries).flat()
477
+ .map((entry) => entry.account));
478
+ if (Object.keys(doc || {}).some((key) => accounts.has(key))) {
479
+ console.error("Doc cannot have keys that are the same as account names");
480
+ continue;
481
+ }
482
+ if (Object.keys(instructions || {}).some((key) => accounts.has(key))) {
483
+ console.error("Instructions cannot have keys that are the same as account names");
484
+ continue;
485
+ }
486
+ for (let i = 0; i < journalEntries.length; i++) {
487
+ const { ledgerEntries, recordEntry, equation, date, } = journalEntries[i];
488
+ const consolidatedPerAccount = ledgerEntries
489
+ .reduce((acc, entry) => {
490
+ const { account } = entry;
491
+ if (acc[account]) {
492
+ acc[account].debit += entry.debit;
493
+ acc[account].credit += entry.credit;
494
+ }
495
+ else {
496
+ acc[account] = {
497
+ debit: entry.debit,
498
+ credit: entry.credit,
499
+ };
500
+ }
501
+ return acc;
502
+ }, {});
503
+ // loop through keys of consolidatedPerAccount
504
+ const totalCreditDebit = Object.entries(consolidatedPerAccount)
505
+ .reduce((acc, [account, { debit, credit }]) => {
506
+ return {
507
+ debit: acc.debit + debit,
508
+ credit: acc.credit + credit,
509
+ };
510
+ }, { debit: 0, credit: 0 });
511
+ if (totalCreditDebit.debit !== totalCreditDebit.credit) {
512
+ console.error("Debit and credit should be equal");
513
+ continue;
514
+ }
515
+ const docRef = exports.db.doc(dstPath);
516
+ const currData = (await txn.get(docRef)).data();
517
+ let instructionsDbValues;
518
+ if (instructions) {
519
+ instructionsDbValues = await (0, distribution_1.convertInstructionsToDbValues)(txn, instructions);
520
+ }
521
+ const finalDoc = Object.assign(Object.assign({}, (doc ? doc : {})), (instructionsDbValues ? instructionsDbValues : {}));
522
+ if (currData) {
523
+ txn.update(docRef, Object.assign(Object.assign({}, finalDoc), { "@forDeletionLater": true }));
524
+ }
525
+ else {
526
+ txn.set(docRef, Object.assign(Object.assign({}, finalDoc), { "@forDeletionLater": true }));
527
+ }
528
+ const [leftSide, ..._] = equation.split("=");
529
+ Object.entries(consolidatedPerAccount).forEach(([account, { debit, credit }]) => {
530
+ const increment = leftSide.includes(account) ? debit - credit : credit - debit;
531
+ if (increment === 0) {
532
+ txn.update(docRef, {
533
+ "@forDeletionLater": FieldValue.delete(),
534
+ });
535
+ return;
536
+ }
537
+ const accountVal = ((currData === null || currData === void 0 ? void 0 : currData[account]) || 0) + increment;
538
+ if (accountVal < 0) {
539
+ throw new Error("Account value cannot be negative");
540
+ }
541
+ finalDoc[account] = accountVal;
542
+ txn.update(docRef, {
543
+ [account]: accountVal,
544
+ "@forDeletionLater": FieldValue.delete(),
545
+ });
546
+ });
547
+ if (Object.keys(finalDoc).length > 0 && !skipRunViewLogics &&
548
+ ["create", "merge"].includes(action)) {
549
+ logicDoc.doc = finalDoc;
550
+ await (0, view_logics_1.queueRunViewLogics)(logicDoc);
551
+ }
552
+ if (recordEntry) {
553
+ for (let j = 0; j < ledgerEntries.length; j++) {
554
+ const { account, debit, credit, description } = ledgerEntries[j];
555
+ const journalEntryId = docId + i;
556
+ const ledgerEntryId = journalEntryId + j;
557
+ const ledgerEntryDoc = Object.assign({ journalEntryId,
558
+ account,
559
+ credit,
560
+ debit,
561
+ equation,
562
+ date }, (description && { description }));
563
+ const entryRef = docRef.collection("@ledgers").doc(ledgerEntryId);
564
+ txn.set(entryRef, ledgerEntryDoc);
565
+ }
566
+ }
567
+ }
568
+ }
569
+ }
570
+ }
571
+ await writeJournalEntriesFirst();
572
+ async function distributeTransactionalLogicResults() {
573
+ const transactionalResults = logicResults.filter((result) => result.transactional);
574
+ if (transactionalResults.length === 0) {
575
+ console.info("No transactional logic results to distribute");
576
+ return;
577
+ }
578
+ // We always distribute transactional results first
579
+ const transactionalDstPathLogicDocsMap = await (0, index_utils_1.expandConsolidateAndGroupByDstPath)(transactionalResults.map((result) => result.documents).flat().filter((doc) => !doc.journalEntries || doc.journalEntries && doc.action === "delete"));
580
+ // Write to firestore in one transaction
581
+ for (const [dstPath, logicDocs] of transactionalDstPathLogicDocsMap) {
582
+ for (const logicDoc of logicDocs) {
583
+ const docRef = exports.db.doc(dstPath);
584
+ const { action } = logicDoc;
585
+ if (action === "create") {
586
+ txn.set(docRef, logicDoc.doc);
587
+ }
588
+ else if (action === "merge") {
589
+ txn.update(docRef, logicDoc.doc);
590
+ }
591
+ else if (action === "delete") {
592
+ txn.delete(docRef);
593
+ }
594
+ if (logicDoc.instructions) {
595
+ const { updateData, removeData } = await (0, distribution_1.convertInstructionsToDbValues)(txn, logicDoc.instructions);
596
+ if (Object.keys(updateData).length > 0) {
597
+ txn.update(docRef, updateData);
598
+ }
599
+ if (Object.keys(removeData).length > 0) {
600
+ txn.update(docRef, removeData);
601
+ }
602
+ }
603
+ }
604
+ }
605
+ }
606
+ await distributeTransactionalLogicResults();
607
+ }
593
608
  const onUserRegister = async (user) => {
594
609
  await exports.db.doc(`users/${user.uid}`).set({
595
610
  "@id": user.uid,