@prmichaelsen/firebase-admin-sdk-v8 2.0.15 → 2.0.17
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/dist/index.js +138 -68
- package/dist/index.mjs +138 -68
- package/firebase.json +9 -0
- package/firestore.indexes.json +33 -0
- package/firestore.rules +11 -0
- package/jest.e2e.config.js +14 -0
- package/package.json +5 -2
- package/service-account.json +13 -0
package/dist/index.js
CHANGED
|
@@ -443,8 +443,7 @@ var FieldValue = {
|
|
|
443
443
|
delete: deleteField
|
|
444
444
|
};
|
|
445
445
|
|
|
446
|
-
// src/firestore
|
|
447
|
-
var FIRESTORE_API = "https://firestore.googleapis.com/v1";
|
|
446
|
+
// src/firestore/converters.ts
|
|
448
447
|
function toFirestoreValue(value) {
|
|
449
448
|
if (value === null || value === void 0) {
|
|
450
449
|
return { nullValue: null };
|
|
@@ -504,58 +503,6 @@ function convertToFirestoreFormat(data) {
|
|
|
504
503
|
}
|
|
505
504
|
return result;
|
|
506
505
|
}
|
|
507
|
-
function extractFieldTransforms(data, fieldPrefix = "") {
|
|
508
|
-
const transforms = [];
|
|
509
|
-
for (const [key, value] of Object.entries(data)) {
|
|
510
|
-
const fieldPath = fieldPrefix ? `${fieldPrefix}.${key}` : key;
|
|
511
|
-
if (isFieldValue(value)) {
|
|
512
|
-
switch (value._type) {
|
|
513
|
-
case "serverTimestamp":
|
|
514
|
-
transforms.push({
|
|
515
|
-
fieldPath,
|
|
516
|
-
setToServerValue: "REQUEST_TIME"
|
|
517
|
-
});
|
|
518
|
-
break;
|
|
519
|
-
case "increment":
|
|
520
|
-
transforms.push({
|
|
521
|
-
fieldPath,
|
|
522
|
-
increment: toFirestoreValue(value._value)
|
|
523
|
-
});
|
|
524
|
-
break;
|
|
525
|
-
case "arrayUnion":
|
|
526
|
-
transforms.push({
|
|
527
|
-
fieldPath,
|
|
528
|
-
appendMissingElements: {
|
|
529
|
-
values: value._value.map((v) => toFirestoreValue(v))
|
|
530
|
-
}
|
|
531
|
-
});
|
|
532
|
-
break;
|
|
533
|
-
case "arrayRemove":
|
|
534
|
-
transforms.push({
|
|
535
|
-
fieldPath,
|
|
536
|
-
removeAllFromArray: {
|
|
537
|
-
values: value._value.map((v) => toFirestoreValue(v))
|
|
538
|
-
}
|
|
539
|
-
});
|
|
540
|
-
break;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
return transforms;
|
|
545
|
-
}
|
|
546
|
-
function removeFieldTransforms(data) {
|
|
547
|
-
const result = {};
|
|
548
|
-
for (const [key, value] of Object.entries(data)) {
|
|
549
|
-
if (isFieldValue(value)) {
|
|
550
|
-
if (value._type === "delete") {
|
|
551
|
-
continue;
|
|
552
|
-
}
|
|
553
|
-
continue;
|
|
554
|
-
}
|
|
555
|
-
result[key] = value;
|
|
556
|
-
}
|
|
557
|
-
return result;
|
|
558
|
-
}
|
|
559
506
|
function fromFirestoreValue(value) {
|
|
560
507
|
if ("stringValue" in value) {
|
|
561
508
|
return value.stringValue;
|
|
@@ -584,12 +531,17 @@ function fromFirestoreValue(value) {
|
|
|
584
531
|
return null;
|
|
585
532
|
}
|
|
586
533
|
function convertFromFirestoreFormat(fields) {
|
|
534
|
+
if (!fields) {
|
|
535
|
+
return {};
|
|
536
|
+
}
|
|
587
537
|
const result = {};
|
|
588
538
|
for (const [key, value] of Object.entries(fields)) {
|
|
589
539
|
result[key] = fromFirestoreValue(value);
|
|
590
540
|
}
|
|
591
541
|
return result;
|
|
592
542
|
}
|
|
543
|
+
|
|
544
|
+
// src/firestore/query-builder.ts
|
|
593
545
|
function buildStructuredQuery(collectionPath, options) {
|
|
594
546
|
const pathSegments = collectionPath.split("/");
|
|
595
547
|
const collectionId = pathSegments[pathSegments.length - 1];
|
|
@@ -672,14 +624,109 @@ function mapWhereOp(op) {
|
|
|
672
624
|
};
|
|
673
625
|
return opMap[op] || "EQUAL";
|
|
674
626
|
}
|
|
675
|
-
|
|
627
|
+
|
|
628
|
+
// src/firestore/transforms.ts
|
|
629
|
+
function extractFieldTransforms(data, fieldPrefix = "") {
|
|
630
|
+
const transforms = [];
|
|
631
|
+
for (const [key, value] of Object.entries(data)) {
|
|
632
|
+
const fieldPath = fieldPrefix ? `${fieldPrefix}.${key}` : key;
|
|
633
|
+
if (isFieldValue(value)) {
|
|
634
|
+
switch (value._type) {
|
|
635
|
+
case "serverTimestamp":
|
|
636
|
+
transforms.push({
|
|
637
|
+
fieldPath,
|
|
638
|
+
setToServerValue: "REQUEST_TIME"
|
|
639
|
+
});
|
|
640
|
+
break;
|
|
641
|
+
case "increment":
|
|
642
|
+
transforms.push({
|
|
643
|
+
fieldPath,
|
|
644
|
+
increment: toFirestoreValue(value._value)
|
|
645
|
+
});
|
|
646
|
+
break;
|
|
647
|
+
case "arrayUnion":
|
|
648
|
+
transforms.push({
|
|
649
|
+
fieldPath,
|
|
650
|
+
appendMissingElements: {
|
|
651
|
+
values: value._value.map((v) => toFirestoreValue(v))
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
break;
|
|
655
|
+
case "arrayRemove":
|
|
656
|
+
transforms.push({
|
|
657
|
+
fieldPath,
|
|
658
|
+
removeAllFromArray: {
|
|
659
|
+
values: value._value.map((v) => toFirestoreValue(v))
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
break;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return transforms;
|
|
667
|
+
}
|
|
668
|
+
function removeFieldTransforms(data) {
|
|
669
|
+
const result = {};
|
|
670
|
+
for (const [key, value] of Object.entries(data)) {
|
|
671
|
+
if (isFieldValue(value)) {
|
|
672
|
+
if (value._type === "delete") {
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
result[key] = value;
|
|
678
|
+
}
|
|
679
|
+
return result;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// src/firestore-rest.ts
|
|
683
|
+
var FIRESTORE_API = "https://firestore.googleapis.com/v1";
|
|
684
|
+
async function commitWrites(writes) {
|
|
676
685
|
const accessToken = await getAdminAccessToken();
|
|
677
686
|
const projectId = getProjectId();
|
|
678
|
-
const url = `${FIRESTORE_API}/projects/${projectId}/databases/(default)/documents
|
|
687
|
+
const url = `${FIRESTORE_API}/projects/${projectId}/databases/(default)/documents:commit`;
|
|
688
|
+
const response = await fetch(url, {
|
|
689
|
+
method: "POST",
|
|
690
|
+
headers: {
|
|
691
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
692
|
+
"Content-Type": "application/json"
|
|
693
|
+
},
|
|
694
|
+
body: JSON.stringify({ writes })
|
|
695
|
+
});
|
|
696
|
+
if (!response.ok) {
|
|
697
|
+
const errorText = await response.text();
|
|
698
|
+
throw new Error(`Failed to commit writes: ${errorText}`);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
async function setDocument(collectionPath, documentId, data, options) {
|
|
702
|
+
const projectId = getProjectId();
|
|
703
|
+
const documentPath = `projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
|
|
679
704
|
const cleanData = removeFieldTransforms(data);
|
|
680
705
|
const firestoreData = convertToFirestoreFormat(cleanData);
|
|
681
706
|
const transforms = extractFieldTransforms(data);
|
|
682
|
-
|
|
707
|
+
if (transforms.length > 0) {
|
|
708
|
+
const updateWrite = {
|
|
709
|
+
update: {
|
|
710
|
+
name: documentPath,
|
|
711
|
+
fields: firestoreData
|
|
712
|
+
},
|
|
713
|
+
updateTransforms: transforms
|
|
714
|
+
};
|
|
715
|
+
const nonTransformFields = Object.keys(cleanData);
|
|
716
|
+
if (nonTransformFields.length > 0) {
|
|
717
|
+
if (options?.merge) {
|
|
718
|
+
updateWrite.updateMask = { fieldPaths: ["*"] };
|
|
719
|
+
} else if (options?.mergeFields && options.mergeFields.length > 0) {
|
|
720
|
+
updateWrite.updateMask = { fieldPaths: options.mergeFields };
|
|
721
|
+
} else {
|
|
722
|
+
updateWrite.updateMask = { fieldPaths: nonTransformFields };
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
await commitWrites([updateWrite]);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const accessToken = await getAdminAccessToken();
|
|
729
|
+
const url = `${FIRESTORE_API}/${documentPath}`;
|
|
683
730
|
let queryParams = "";
|
|
684
731
|
if (options?.merge) {
|
|
685
732
|
queryParams = "?updateMask.fieldPaths=*";
|
|
@@ -687,16 +734,13 @@ async function setDocument(collectionPath, documentId, data, options) {
|
|
|
687
734
|
const fieldPaths = options.mergeFields.join("&updateMask.fieldPaths=");
|
|
688
735
|
queryParams = `?updateMask.fieldPaths=${fieldPaths}`;
|
|
689
736
|
}
|
|
690
|
-
if (transforms.length > 0) {
|
|
691
|
-
body.transforms = transforms;
|
|
692
|
-
}
|
|
693
737
|
const response = await fetch(`${url}${queryParams}`, {
|
|
694
738
|
method: "PATCH",
|
|
695
739
|
headers: {
|
|
696
740
|
"Authorization": `Bearer ${accessToken}`,
|
|
697
741
|
"Content-Type": "application/json"
|
|
698
742
|
},
|
|
699
|
-
body: JSON.stringify(
|
|
743
|
+
body: JSON.stringify({ fields: firestoreData })
|
|
700
744
|
});
|
|
701
745
|
if (!response.ok) {
|
|
702
746
|
const errorText = await response.text();
|
|
@@ -754,25 +798,51 @@ async function getDocument(collectionPath, documentId) {
|
|
|
754
798
|
return convertFromFirestoreFormat(result.fields);
|
|
755
799
|
}
|
|
756
800
|
async function updateDocument(collectionPath, documentId, data) {
|
|
757
|
-
const accessToken = await getAdminAccessToken();
|
|
758
801
|
const projectId = getProjectId();
|
|
759
|
-
const
|
|
802
|
+
const documentPath = `projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
|
|
760
803
|
const cleanData = removeFieldTransforms(data);
|
|
761
804
|
const firestoreData = convertToFirestoreFormat(cleanData);
|
|
762
805
|
const transforms = extractFieldTransforms(data);
|
|
763
|
-
const updateMaskFields = Object.keys(data).filter((key) =>
|
|
764
|
-
|
|
765
|
-
|
|
806
|
+
const updateMaskFields = Object.keys(data).filter((key) => {
|
|
807
|
+
if (!isFieldValue(data[key])) {
|
|
808
|
+
return true;
|
|
809
|
+
}
|
|
810
|
+
return data[key]._type === "delete";
|
|
811
|
+
});
|
|
766
812
|
if (transforms.length > 0) {
|
|
767
|
-
|
|
813
|
+
const nonTransformFields = Object.keys(cleanData);
|
|
814
|
+
if (nonTransformFields.length === 0) {
|
|
815
|
+
const transformWrite = {
|
|
816
|
+
transform: {
|
|
817
|
+
document: documentPath,
|
|
818
|
+
fieldTransforms: transforms
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
await commitWrites([transformWrite]);
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
const updateWrite = {
|
|
825
|
+
update: {
|
|
826
|
+
name: documentPath,
|
|
827
|
+
fields: firestoreData
|
|
828
|
+
},
|
|
829
|
+
updateMask: { fieldPaths: updateMaskFields },
|
|
830
|
+
updateTransforms: transforms,
|
|
831
|
+
currentDocument: { exists: true }
|
|
832
|
+
};
|
|
833
|
+
await commitWrites([updateWrite]);
|
|
834
|
+
return;
|
|
768
835
|
}
|
|
836
|
+
const accessToken = await getAdminAccessToken();
|
|
837
|
+
const url = `${FIRESTORE_API}/${documentPath}`;
|
|
838
|
+
const updateMaskParams = updateMaskFields.map((field) => `updateMask.fieldPaths=${encodeURIComponent(field)}`).join("&");
|
|
769
839
|
const response = await fetch(`${url}?${updateMaskParams}¤tDocument.exists=true`, {
|
|
770
840
|
method: "PATCH",
|
|
771
841
|
headers: {
|
|
772
842
|
"Authorization": `Bearer ${accessToken}`,
|
|
773
843
|
"Content-Type": "application/json"
|
|
774
844
|
},
|
|
775
|
-
body: JSON.stringify(
|
|
845
|
+
body: JSON.stringify({ fields: firestoreData })
|
|
776
846
|
});
|
|
777
847
|
if (!response.ok) {
|
|
778
848
|
const errorText = await response.text();
|
package/dist/index.mjs
CHANGED
|
@@ -400,8 +400,7 @@ var FieldValue = {
|
|
|
400
400
|
delete: deleteField
|
|
401
401
|
};
|
|
402
402
|
|
|
403
|
-
// src/firestore
|
|
404
|
-
var FIRESTORE_API = "https://firestore.googleapis.com/v1";
|
|
403
|
+
// src/firestore/converters.ts
|
|
405
404
|
function toFirestoreValue(value) {
|
|
406
405
|
if (value === null || value === void 0) {
|
|
407
406
|
return { nullValue: null };
|
|
@@ -461,58 +460,6 @@ function convertToFirestoreFormat(data) {
|
|
|
461
460
|
}
|
|
462
461
|
return result;
|
|
463
462
|
}
|
|
464
|
-
function extractFieldTransforms(data, fieldPrefix = "") {
|
|
465
|
-
const transforms = [];
|
|
466
|
-
for (const [key, value] of Object.entries(data)) {
|
|
467
|
-
const fieldPath = fieldPrefix ? `${fieldPrefix}.${key}` : key;
|
|
468
|
-
if (isFieldValue(value)) {
|
|
469
|
-
switch (value._type) {
|
|
470
|
-
case "serverTimestamp":
|
|
471
|
-
transforms.push({
|
|
472
|
-
fieldPath,
|
|
473
|
-
setToServerValue: "REQUEST_TIME"
|
|
474
|
-
});
|
|
475
|
-
break;
|
|
476
|
-
case "increment":
|
|
477
|
-
transforms.push({
|
|
478
|
-
fieldPath,
|
|
479
|
-
increment: toFirestoreValue(value._value)
|
|
480
|
-
});
|
|
481
|
-
break;
|
|
482
|
-
case "arrayUnion":
|
|
483
|
-
transforms.push({
|
|
484
|
-
fieldPath,
|
|
485
|
-
appendMissingElements: {
|
|
486
|
-
values: value._value.map((v) => toFirestoreValue(v))
|
|
487
|
-
}
|
|
488
|
-
});
|
|
489
|
-
break;
|
|
490
|
-
case "arrayRemove":
|
|
491
|
-
transforms.push({
|
|
492
|
-
fieldPath,
|
|
493
|
-
removeAllFromArray: {
|
|
494
|
-
values: value._value.map((v) => toFirestoreValue(v))
|
|
495
|
-
}
|
|
496
|
-
});
|
|
497
|
-
break;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
return transforms;
|
|
502
|
-
}
|
|
503
|
-
function removeFieldTransforms(data) {
|
|
504
|
-
const result = {};
|
|
505
|
-
for (const [key, value] of Object.entries(data)) {
|
|
506
|
-
if (isFieldValue(value)) {
|
|
507
|
-
if (value._type === "delete") {
|
|
508
|
-
continue;
|
|
509
|
-
}
|
|
510
|
-
continue;
|
|
511
|
-
}
|
|
512
|
-
result[key] = value;
|
|
513
|
-
}
|
|
514
|
-
return result;
|
|
515
|
-
}
|
|
516
463
|
function fromFirestoreValue(value) {
|
|
517
464
|
if ("stringValue" in value) {
|
|
518
465
|
return value.stringValue;
|
|
@@ -541,12 +488,17 @@ function fromFirestoreValue(value) {
|
|
|
541
488
|
return null;
|
|
542
489
|
}
|
|
543
490
|
function convertFromFirestoreFormat(fields) {
|
|
491
|
+
if (!fields) {
|
|
492
|
+
return {};
|
|
493
|
+
}
|
|
544
494
|
const result = {};
|
|
545
495
|
for (const [key, value] of Object.entries(fields)) {
|
|
546
496
|
result[key] = fromFirestoreValue(value);
|
|
547
497
|
}
|
|
548
498
|
return result;
|
|
549
499
|
}
|
|
500
|
+
|
|
501
|
+
// src/firestore/query-builder.ts
|
|
550
502
|
function buildStructuredQuery(collectionPath, options) {
|
|
551
503
|
const pathSegments = collectionPath.split("/");
|
|
552
504
|
const collectionId = pathSegments[pathSegments.length - 1];
|
|
@@ -629,14 +581,109 @@ function mapWhereOp(op) {
|
|
|
629
581
|
};
|
|
630
582
|
return opMap[op] || "EQUAL";
|
|
631
583
|
}
|
|
632
|
-
|
|
584
|
+
|
|
585
|
+
// src/firestore/transforms.ts
|
|
586
|
+
function extractFieldTransforms(data, fieldPrefix = "") {
|
|
587
|
+
const transforms = [];
|
|
588
|
+
for (const [key, value] of Object.entries(data)) {
|
|
589
|
+
const fieldPath = fieldPrefix ? `${fieldPrefix}.${key}` : key;
|
|
590
|
+
if (isFieldValue(value)) {
|
|
591
|
+
switch (value._type) {
|
|
592
|
+
case "serverTimestamp":
|
|
593
|
+
transforms.push({
|
|
594
|
+
fieldPath,
|
|
595
|
+
setToServerValue: "REQUEST_TIME"
|
|
596
|
+
});
|
|
597
|
+
break;
|
|
598
|
+
case "increment":
|
|
599
|
+
transforms.push({
|
|
600
|
+
fieldPath,
|
|
601
|
+
increment: toFirestoreValue(value._value)
|
|
602
|
+
});
|
|
603
|
+
break;
|
|
604
|
+
case "arrayUnion":
|
|
605
|
+
transforms.push({
|
|
606
|
+
fieldPath,
|
|
607
|
+
appendMissingElements: {
|
|
608
|
+
values: value._value.map((v) => toFirestoreValue(v))
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
break;
|
|
612
|
+
case "arrayRemove":
|
|
613
|
+
transforms.push({
|
|
614
|
+
fieldPath,
|
|
615
|
+
removeAllFromArray: {
|
|
616
|
+
values: value._value.map((v) => toFirestoreValue(v))
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return transforms;
|
|
624
|
+
}
|
|
625
|
+
function removeFieldTransforms(data) {
|
|
626
|
+
const result = {};
|
|
627
|
+
for (const [key, value] of Object.entries(data)) {
|
|
628
|
+
if (isFieldValue(value)) {
|
|
629
|
+
if (value._type === "delete") {
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
result[key] = value;
|
|
635
|
+
}
|
|
636
|
+
return result;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// src/firestore-rest.ts
|
|
640
|
+
var FIRESTORE_API = "https://firestore.googleapis.com/v1";
|
|
641
|
+
async function commitWrites(writes) {
|
|
633
642
|
const accessToken = await getAdminAccessToken();
|
|
634
643
|
const projectId = getProjectId();
|
|
635
|
-
const url = `${FIRESTORE_API}/projects/${projectId}/databases/(default)/documents
|
|
644
|
+
const url = `${FIRESTORE_API}/projects/${projectId}/databases/(default)/documents:commit`;
|
|
645
|
+
const response = await fetch(url, {
|
|
646
|
+
method: "POST",
|
|
647
|
+
headers: {
|
|
648
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
649
|
+
"Content-Type": "application/json"
|
|
650
|
+
},
|
|
651
|
+
body: JSON.stringify({ writes })
|
|
652
|
+
});
|
|
653
|
+
if (!response.ok) {
|
|
654
|
+
const errorText = await response.text();
|
|
655
|
+
throw new Error(`Failed to commit writes: ${errorText}`);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
async function setDocument(collectionPath, documentId, data, options) {
|
|
659
|
+
const projectId = getProjectId();
|
|
660
|
+
const documentPath = `projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
|
|
636
661
|
const cleanData = removeFieldTransforms(data);
|
|
637
662
|
const firestoreData = convertToFirestoreFormat(cleanData);
|
|
638
663
|
const transforms = extractFieldTransforms(data);
|
|
639
|
-
|
|
664
|
+
if (transforms.length > 0) {
|
|
665
|
+
const updateWrite = {
|
|
666
|
+
update: {
|
|
667
|
+
name: documentPath,
|
|
668
|
+
fields: firestoreData
|
|
669
|
+
},
|
|
670
|
+
updateTransforms: transforms
|
|
671
|
+
};
|
|
672
|
+
const nonTransformFields = Object.keys(cleanData);
|
|
673
|
+
if (nonTransformFields.length > 0) {
|
|
674
|
+
if (options?.merge) {
|
|
675
|
+
updateWrite.updateMask = { fieldPaths: ["*"] };
|
|
676
|
+
} else if (options?.mergeFields && options.mergeFields.length > 0) {
|
|
677
|
+
updateWrite.updateMask = { fieldPaths: options.mergeFields };
|
|
678
|
+
} else {
|
|
679
|
+
updateWrite.updateMask = { fieldPaths: nonTransformFields };
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
await commitWrites([updateWrite]);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
const accessToken = await getAdminAccessToken();
|
|
686
|
+
const url = `${FIRESTORE_API}/${documentPath}`;
|
|
640
687
|
let queryParams = "";
|
|
641
688
|
if (options?.merge) {
|
|
642
689
|
queryParams = "?updateMask.fieldPaths=*";
|
|
@@ -644,16 +691,13 @@ async function setDocument(collectionPath, documentId, data, options) {
|
|
|
644
691
|
const fieldPaths = options.mergeFields.join("&updateMask.fieldPaths=");
|
|
645
692
|
queryParams = `?updateMask.fieldPaths=${fieldPaths}`;
|
|
646
693
|
}
|
|
647
|
-
if (transforms.length > 0) {
|
|
648
|
-
body.transforms = transforms;
|
|
649
|
-
}
|
|
650
694
|
const response = await fetch(`${url}${queryParams}`, {
|
|
651
695
|
method: "PATCH",
|
|
652
696
|
headers: {
|
|
653
697
|
"Authorization": `Bearer ${accessToken}`,
|
|
654
698
|
"Content-Type": "application/json"
|
|
655
699
|
},
|
|
656
|
-
body: JSON.stringify(
|
|
700
|
+
body: JSON.stringify({ fields: firestoreData })
|
|
657
701
|
});
|
|
658
702
|
if (!response.ok) {
|
|
659
703
|
const errorText = await response.text();
|
|
@@ -711,25 +755,51 @@ async function getDocument(collectionPath, documentId) {
|
|
|
711
755
|
return convertFromFirestoreFormat(result.fields);
|
|
712
756
|
}
|
|
713
757
|
async function updateDocument(collectionPath, documentId, data) {
|
|
714
|
-
const accessToken = await getAdminAccessToken();
|
|
715
758
|
const projectId = getProjectId();
|
|
716
|
-
const
|
|
759
|
+
const documentPath = `projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
|
|
717
760
|
const cleanData = removeFieldTransforms(data);
|
|
718
761
|
const firestoreData = convertToFirestoreFormat(cleanData);
|
|
719
762
|
const transforms = extractFieldTransforms(data);
|
|
720
|
-
const updateMaskFields = Object.keys(data).filter((key) =>
|
|
721
|
-
|
|
722
|
-
|
|
763
|
+
const updateMaskFields = Object.keys(data).filter((key) => {
|
|
764
|
+
if (!isFieldValue(data[key])) {
|
|
765
|
+
return true;
|
|
766
|
+
}
|
|
767
|
+
return data[key]._type === "delete";
|
|
768
|
+
});
|
|
723
769
|
if (transforms.length > 0) {
|
|
724
|
-
|
|
770
|
+
const nonTransformFields = Object.keys(cleanData);
|
|
771
|
+
if (nonTransformFields.length === 0) {
|
|
772
|
+
const transformWrite = {
|
|
773
|
+
transform: {
|
|
774
|
+
document: documentPath,
|
|
775
|
+
fieldTransforms: transforms
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
await commitWrites([transformWrite]);
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
const updateWrite = {
|
|
782
|
+
update: {
|
|
783
|
+
name: documentPath,
|
|
784
|
+
fields: firestoreData
|
|
785
|
+
},
|
|
786
|
+
updateMask: { fieldPaths: updateMaskFields },
|
|
787
|
+
updateTransforms: transforms,
|
|
788
|
+
currentDocument: { exists: true }
|
|
789
|
+
};
|
|
790
|
+
await commitWrites([updateWrite]);
|
|
791
|
+
return;
|
|
725
792
|
}
|
|
793
|
+
const accessToken = await getAdminAccessToken();
|
|
794
|
+
const url = `${FIRESTORE_API}/${documentPath}`;
|
|
795
|
+
const updateMaskParams = updateMaskFields.map((field) => `updateMask.fieldPaths=${encodeURIComponent(field)}`).join("&");
|
|
726
796
|
const response = await fetch(`${url}?${updateMaskParams}¤tDocument.exists=true`, {
|
|
727
797
|
method: "PATCH",
|
|
728
798
|
headers: {
|
|
729
799
|
"Authorization": `Bearer ${accessToken}`,
|
|
730
800
|
"Content-Type": "application/json"
|
|
731
801
|
},
|
|
732
|
-
body: JSON.stringify(
|
|
802
|
+
body: JSON.stringify({ fields: firestoreData })
|
|
733
803
|
});
|
|
734
804
|
if (!response.ok) {
|
|
735
805
|
const errorText = await response.text();
|
package/firebase.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"indexes": [
|
|
3
|
+
{
|
|
4
|
+
"collectionGroup": "e2e-tests",
|
|
5
|
+
"queryScope": "COLLECTION",
|
|
6
|
+
"fields": [
|
|
7
|
+
{
|
|
8
|
+
"fieldPath": "_test",
|
|
9
|
+
"order": "ASCENDING"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"fieldPath": "age",
|
|
13
|
+
"order": "ASCENDING"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"collectionGroup": "messages",
|
|
19
|
+
"queryScope": "COLLECTION",
|
|
20
|
+
"fields": [
|
|
21
|
+
{
|
|
22
|
+
"fieldPath": "_test",
|
|
23
|
+
"order": "ASCENDING"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"fieldPath": "timestamp",
|
|
27
|
+
"order": "ASCENDING"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"fieldOverrides": []
|
|
33
|
+
}
|
package/firestore.rules
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
preset: 'ts-jest',
|
|
3
|
+
testEnvironment: 'node',
|
|
4
|
+
testMatch: ['**/*.e2e.ts'],
|
|
5
|
+
testTimeout: 30000, // 30 seconds for real API calls
|
|
6
|
+
roots: ['<rootDir>/src'],
|
|
7
|
+
collectCoverageFrom: [
|
|
8
|
+
'src/**/*.ts',
|
|
9
|
+
'!src/**/*.spec.ts',
|
|
10
|
+
'!src/**/*.e2e.ts',
|
|
11
|
+
'!src/types.ts',
|
|
12
|
+
'!src/index.ts',
|
|
13
|
+
],
|
|
14
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prmichaelsen/firebase-admin-sdk-v8",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.17",
|
|
4
4
|
"description": "Firebase Admin SDK for Cloudflare Workers and edge runtimes using REST APIs",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -26,7 +26,10 @@
|
|
|
26
26
|
"typecheck": "tsc --noEmit",
|
|
27
27
|
"test": "jest",
|
|
28
28
|
"test:watch": "jest --watch",
|
|
29
|
-
"
|
|
29
|
+
"test:e2e": "jest --config jest.e2e.config.js",
|
|
30
|
+
"test:e2e:watch": "jest --config jest.e2e.config.js --watch",
|
|
31
|
+
"test:all": "npm test && npm run test:e2e",
|
|
32
|
+
"prepublishOnly": "npm run build"
|
|
30
33
|
},
|
|
31
34
|
"keywords": [
|
|
32
35
|
"firebase",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "service_account",
|
|
3
|
+
"project_id": "prmichaelsen-firebase-e2e",
|
|
4
|
+
"private_key_id": "84caabb8515ec5fab4caa53a0f85405c270d5cd4",
|
|
5
|
+
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC/0n8V7m7vCQ9/\n5FGYL5V20pQxliUMF/ycNBSV/xHrXAvTMxyMEjFK8Tg9sBLdYhuxC488lnwm3/o+\nn5JkckQ2AyzXX6mi+camwxdLXmT97kXqdFLisO4PY7Fv6HvvMgRQqysj7aJkyiz7\nrAs0DpQq1L70sBFdVLiScPH9FFnuV0EeyOTuVgYwGYk1L76/U7bb3WLNCmcmtG+Y\nt4BNzyMqFgtIdXCQFVBQf9HXy3x5xk8a1rrkf7pEuiwK0PXCDIVH7GYX4xCiQ1qs\nYF4IunNFhcwu1hk5XTIDSVrIn/qLPMZvEcBqHxIJd5JvePjx5hPP81b1mrGUVske\nsaRimjxxAgMBAAECggEAAWNKVG7KslkMRH5y5q56yYbMLVsAPp5SehDYZfNtU5jG\nucr2CxS7SDxcOKS0UOdmUDlyBQaztED3mbS5hZeGuHtSZjvaHtnpdDMXe+NIHhw3\nY520LHwKOjtG69/bPFz4x1qjBRoG4Zgi4NlFpXqbhinO4zdTkNYi6xBSd+R0oshP\nSo59lQvs8e3bMD4f4uTl7JxUifjVM64R3gCkH6AbGqU01wg8UzsllCf0fQM04w8i\nq1oVjhBVWYYVE7w2H+EU+LdiRSRET+Vrk5dtBL3vI6yckvGmxTBMnkK2gTFAAR6F\nnN4NKlUsiFol9WVRY3tdRNOnStjlORDyerSEFQaqhwKBgQDfAa5ddxBVbxUS3E2a\nnGJRA44fYlRyZf9KnJdz8uWsCvuG7UYbe2bp7TxlpEXdTG6Vqu/hhYWd/msmhupZ\nc+YFg1HWE1Ee+nQv7iwAD+okfncnKGIC65XjwaSEMRGrmtxBL6Qq4yxn/Yug/8YR\nniGRQKuMtJ2ZA0p41vI6Mkt7kwKBgQDcM7eQQ5482FcXuOfUlPoiWnd+pF5mNrU1\ngKoxdsXMYRKyTGMPLrOKqsf6kisQUlA48vWRCQOa9QBSjHktfOt2EZ3mNQz5k3AR\n/dIjCGf+ATr2P9Sc0uo7sA20XB62wmG+t2A/vxf9WzBAgdDk6nENB4nz6lL9xcak\ngvVt2vXSawKBgQCV6JRk4f/J3oVFC3DjaRKyMPid4kSwLh6B8mfhGrwHfc59cgz5\ntmeFAuPh057fV1zTIXhlmpMqlPdEi9cHUOCkfhVKGewjLetiuPE9DXWxGI5SdVQF\ncIZu9yH3duDRAaXj7/mklteoBAmTrbxg5XLdKKLpUBTM4ihyuNNWCa8yHwKBgCYK\noTnBFMM6NMGaZiKpohTxQBeW2eAar2+QzNZCyKUoWAyJecuTq9zW6Dl3qwzky4sr\nHhVyUzcgAHBCaGTdYehB3t94ZsdvGztgeD8pIp4VJFSKbnaxUVoCbjusdnnoVu6V\ny4D3yHMyn8FlK+uAPQudM835u2CwHEMrhK731uQFAoGBAKO0crVWuY1EP1RLSyLK\nIS3uUgbauBbQlE4ZPBTi84vXCZjN/53obFgkPHJ5tdjxj6C8x58jgHg/Hufyvbib\ngOQQxadM90UBumlzA8lV7A9jvL2ryfop9ketnVO5hPvB4O8iw9Z1R+25FeM9ZFJI\nqmEjiy9Nhmvpvur+FZ85qcRm\n-----END PRIVATE KEY-----\n",
|
|
6
|
+
"client_email": "firebase-adminsdk-fbsvc@prmichaelsen-firebase-e2e.iam.gserviceaccount.com",
|
|
7
|
+
"client_id": "103889460014910902622",
|
|
8
|
+
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
|
9
|
+
"token_uri": "https://oauth2.googleapis.com/token",
|
|
10
|
+
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
|
11
|
+
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40prmichaelsen-firebase-e2e.iam.gserviceaccount.com",
|
|
12
|
+
"universe_domain": "googleapis.com"
|
|
13
|
+
}
|