@atproto/bsync 0.0.18 → 0.0.20
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/CHANGELOG.md +13 -0
- package/LICENSE.txt +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +1 -0
- package/dist/context.js.map +1 -1
- package/dist/db/index.js +17 -7
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations/20250527T022203400Z-add-operation.d.ts +4 -0
- package/dist/db/migrations/20250527T022203400Z-add-operation.d.ts.map +1 -0
- package/dist/db/migrations/20250527T022203400Z-add-operation.js +21 -0
- package/dist/db/migrations/20250527T022203400Z-add-operation.js.map +1 -0
- package/dist/db/migrations/index.d.ts +1 -0
- package/dist/db/migrations/index.d.ts.map +1 -1
- package/dist/db/migrations/index.js +19 -8
- package/dist/db/migrations/index.js.map +1 -1
- package/dist/db/schema/index.d.ts +2 -1
- package/dist/db/schema/index.d.ts.map +1 -1
- package/dist/db/schema/operation.d.ts +18 -0
- package/dist/db/schema/operation.d.ts.map +1 -0
- package/dist/db/schema/operation.js +6 -0
- package/dist/db/schema/operation.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/proto/bsync_connect.d.ts +19 -1
- package/dist/proto/bsync_connect.d.ts.map +1 -1
- package/dist/proto/bsync_connect.js +18 -0
- package/dist/proto/bsync_connect.js.map +1 -1
- package/dist/proto/bsync_pb.d.ts +150 -0
- package/dist/proto/bsync_pb.d.ts.map +1 -1
- package/dist/proto/bsync_pb.js +401 -1
- package/dist/proto/bsync_pb.js.map +1 -1
- package/dist/routes/add-mute-operation.d.ts.map +1 -1
- package/dist/routes/add-notif-operation.d.ts.map +1 -1
- package/dist/routes/auth.d.ts.map +1 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +4 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/put-operation.d.ts +6 -0
- package/dist/routes/put-operation.d.ts.map +1 -0
- package/dist/routes/put-operation.js +72 -0
- package/dist/routes/put-operation.js.map +1 -0
- package/dist/routes/scan-mute-operations.d.ts.map +1 -1
- package/dist/routes/scan-notif-operations.d.ts.map +1 -1
- package/dist/routes/scan-operations.d.ts +6 -0
- package/dist/routes/scan-operations.d.ts.map +1 -0
- package/dist/routes/scan-operations.js +59 -0
- package/dist/routes/scan-operations.js.map +1 -0
- package/dist/routes/util.d.ts.map +1 -1
- package/package.json +2 -2
- package/proto/bsync.proto +40 -0
- package/src/context.ts +2 -0
- package/src/db/migrations/20250527T022203400Z-add-operation.ts +20 -0
- package/src/db/migrations/index.ts +1 -0
- package/src/db/schema/index.ts +3 -1
- package/src/db/schema/operation.ts +20 -0
- package/src/index.ts +5 -0
- package/src/proto/bsync_connect.ts +22 -0
- package/src/proto/bsync_pb.ts +355 -0
- package/src/routes/index.ts +4 -0
- package/src/routes/put-operation.ts +101 -0
- package/src/routes/scan-operations.ts +67 -0
- package/tests/operations.test.ts +295 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.tests.tsbuildinfo +1 -1
package/src/proto/bsync_pb.ts
CHANGED
|
@@ -13,6 +13,38 @@ import type {
|
|
|
13
13
|
} from '@bufbuild/protobuf'
|
|
14
14
|
import { Message, proto3 } from '@bufbuild/protobuf'
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* @generated from enum bsync.Method
|
|
18
|
+
*/
|
|
19
|
+
export enum Method {
|
|
20
|
+
/**
|
|
21
|
+
* @generated from enum value: METHOD_UNSPECIFIED = 0;
|
|
22
|
+
*/
|
|
23
|
+
UNSPECIFIED = 0,
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @generated from enum value: METHOD_CREATE = 1;
|
|
27
|
+
*/
|
|
28
|
+
CREATE = 1,
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @generated from enum value: METHOD_UPDATE = 2;
|
|
32
|
+
*/
|
|
33
|
+
UPDATE = 2,
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @generated from enum value: METHOD_DELETE = 3;
|
|
37
|
+
*/
|
|
38
|
+
DELETE = 3,
|
|
39
|
+
}
|
|
40
|
+
// Retrieve enum metadata with: proto3.getEnumType(Method)
|
|
41
|
+
proto3.util.setEnumType(Method, 'bsync.Method', [
|
|
42
|
+
{ no: 0, name: 'METHOD_UNSPECIFIED' },
|
|
43
|
+
{ no: 1, name: 'METHOD_CREATE' },
|
|
44
|
+
{ no: 2, name: 'METHOD_UPDATE' },
|
|
45
|
+
{ no: 3, name: 'METHOD_DELETE' },
|
|
46
|
+
])
|
|
47
|
+
|
|
16
48
|
/**
|
|
17
49
|
* @generated from message bsync.MuteOperation
|
|
18
50
|
*/
|
|
@@ -689,6 +721,329 @@ export class ScanNotifOperationsResponse extends Message<ScanNotifOperationsResp
|
|
|
689
721
|
}
|
|
690
722
|
}
|
|
691
723
|
|
|
724
|
+
/**
|
|
725
|
+
* @generated from message bsync.Operation
|
|
726
|
+
*/
|
|
727
|
+
export class Operation extends Message<Operation> {
|
|
728
|
+
/**
|
|
729
|
+
* @generated from field: string id = 1;
|
|
730
|
+
*/
|
|
731
|
+
id = ''
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* @generated from field: string actor_did = 2;
|
|
735
|
+
*/
|
|
736
|
+
actorDid = ''
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* @generated from field: string collection = 3;
|
|
740
|
+
*/
|
|
741
|
+
collection = ''
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* @generated from field: string rkey = 4;
|
|
745
|
+
*/
|
|
746
|
+
rkey = ''
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* @generated from field: bsync.Method method = 5;
|
|
750
|
+
*/
|
|
751
|
+
method = Method.UNSPECIFIED
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* @generated from field: bytes payload = 6;
|
|
755
|
+
*/
|
|
756
|
+
payload = new Uint8Array(0)
|
|
757
|
+
|
|
758
|
+
constructor(data?: PartialMessage<Operation>) {
|
|
759
|
+
super()
|
|
760
|
+
proto3.util.initPartial(data, this)
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
static readonly runtime: typeof proto3 = proto3
|
|
764
|
+
static readonly typeName = 'bsync.Operation'
|
|
765
|
+
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
|
766
|
+
{ no: 1, name: 'id', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
|
|
767
|
+
{ no: 2, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
|
|
768
|
+
{ no: 3, name: 'collection', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
|
|
769
|
+
{ no: 4, name: 'rkey', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
|
|
770
|
+
{ no: 5, name: 'method', kind: 'enum', T: proto3.getEnumType(Method) },
|
|
771
|
+
{ no: 6, name: 'payload', kind: 'scalar', T: 12 /* ScalarType.BYTES */ },
|
|
772
|
+
])
|
|
773
|
+
|
|
774
|
+
static fromBinary(
|
|
775
|
+
bytes: Uint8Array,
|
|
776
|
+
options?: Partial<BinaryReadOptions>,
|
|
777
|
+
): Operation {
|
|
778
|
+
return new Operation().fromBinary(bytes, options)
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
static fromJson(
|
|
782
|
+
jsonValue: JsonValue,
|
|
783
|
+
options?: Partial<JsonReadOptions>,
|
|
784
|
+
): Operation {
|
|
785
|
+
return new Operation().fromJson(jsonValue, options)
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
static fromJsonString(
|
|
789
|
+
jsonString: string,
|
|
790
|
+
options?: Partial<JsonReadOptions>,
|
|
791
|
+
): Operation {
|
|
792
|
+
return new Operation().fromJsonString(jsonString, options)
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
static equals(
|
|
796
|
+
a: Operation | PlainMessage<Operation> | undefined,
|
|
797
|
+
b: Operation | PlainMessage<Operation> | undefined,
|
|
798
|
+
): boolean {
|
|
799
|
+
return proto3.util.equals(Operation, a, b)
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* @generated from message bsync.PutOperationRequest
|
|
805
|
+
*/
|
|
806
|
+
export class PutOperationRequest extends Message<PutOperationRequest> {
|
|
807
|
+
/**
|
|
808
|
+
* @generated from field: string collection = 1;
|
|
809
|
+
*/
|
|
810
|
+
collection = ''
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* @generated from field: string actor_did = 2;
|
|
814
|
+
*/
|
|
815
|
+
actorDid = ''
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* @generated from field: string rkey = 3;
|
|
819
|
+
*/
|
|
820
|
+
rkey = ''
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* @generated from field: bsync.Method method = 4;
|
|
824
|
+
*/
|
|
825
|
+
method = Method.UNSPECIFIED
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* @generated from field: bytes payload = 5;
|
|
829
|
+
*/
|
|
830
|
+
payload = new Uint8Array(0)
|
|
831
|
+
|
|
832
|
+
constructor(data?: PartialMessage<PutOperationRequest>) {
|
|
833
|
+
super()
|
|
834
|
+
proto3.util.initPartial(data, this)
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
static readonly runtime: typeof proto3 = proto3
|
|
838
|
+
static readonly typeName = 'bsync.PutOperationRequest'
|
|
839
|
+
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
|
840
|
+
{ no: 1, name: 'collection', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
|
|
841
|
+
{ no: 2, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
|
|
842
|
+
{ no: 3, name: 'rkey', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
|
|
843
|
+
{ no: 4, name: 'method', kind: 'enum', T: proto3.getEnumType(Method) },
|
|
844
|
+
{ no: 5, name: 'payload', kind: 'scalar', T: 12 /* ScalarType.BYTES */ },
|
|
845
|
+
])
|
|
846
|
+
|
|
847
|
+
static fromBinary(
|
|
848
|
+
bytes: Uint8Array,
|
|
849
|
+
options?: Partial<BinaryReadOptions>,
|
|
850
|
+
): PutOperationRequest {
|
|
851
|
+
return new PutOperationRequest().fromBinary(bytes, options)
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
static fromJson(
|
|
855
|
+
jsonValue: JsonValue,
|
|
856
|
+
options?: Partial<JsonReadOptions>,
|
|
857
|
+
): PutOperationRequest {
|
|
858
|
+
return new PutOperationRequest().fromJson(jsonValue, options)
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
static fromJsonString(
|
|
862
|
+
jsonString: string,
|
|
863
|
+
options?: Partial<JsonReadOptions>,
|
|
864
|
+
): PutOperationRequest {
|
|
865
|
+
return new PutOperationRequest().fromJsonString(jsonString, options)
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
static equals(
|
|
869
|
+
a: PutOperationRequest | PlainMessage<PutOperationRequest> | undefined,
|
|
870
|
+
b: PutOperationRequest | PlainMessage<PutOperationRequest> | undefined,
|
|
871
|
+
): boolean {
|
|
872
|
+
return proto3.util.equals(PutOperationRequest, a, b)
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* @generated from message bsync.PutOperationResponse
|
|
878
|
+
*/
|
|
879
|
+
export class PutOperationResponse extends Message<PutOperationResponse> {
|
|
880
|
+
/**
|
|
881
|
+
* @generated from field: bsync.Operation operation = 1;
|
|
882
|
+
*/
|
|
883
|
+
operation?: Operation
|
|
884
|
+
|
|
885
|
+
constructor(data?: PartialMessage<PutOperationResponse>) {
|
|
886
|
+
super()
|
|
887
|
+
proto3.util.initPartial(data, this)
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
static readonly runtime: typeof proto3 = proto3
|
|
891
|
+
static readonly typeName = 'bsync.PutOperationResponse'
|
|
892
|
+
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
|
893
|
+
{ no: 1, name: 'operation', kind: 'message', T: Operation },
|
|
894
|
+
])
|
|
895
|
+
|
|
896
|
+
static fromBinary(
|
|
897
|
+
bytes: Uint8Array,
|
|
898
|
+
options?: Partial<BinaryReadOptions>,
|
|
899
|
+
): PutOperationResponse {
|
|
900
|
+
return new PutOperationResponse().fromBinary(bytes, options)
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
static fromJson(
|
|
904
|
+
jsonValue: JsonValue,
|
|
905
|
+
options?: Partial<JsonReadOptions>,
|
|
906
|
+
): PutOperationResponse {
|
|
907
|
+
return new PutOperationResponse().fromJson(jsonValue, options)
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
static fromJsonString(
|
|
911
|
+
jsonString: string,
|
|
912
|
+
options?: Partial<JsonReadOptions>,
|
|
913
|
+
): PutOperationResponse {
|
|
914
|
+
return new PutOperationResponse().fromJsonString(jsonString, options)
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
static equals(
|
|
918
|
+
a: PutOperationResponse | PlainMessage<PutOperationResponse> | undefined,
|
|
919
|
+
b: PutOperationResponse | PlainMessage<PutOperationResponse> | undefined,
|
|
920
|
+
): boolean {
|
|
921
|
+
return proto3.util.equals(PutOperationResponse, a, b)
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* @generated from message bsync.ScanOperationsRequest
|
|
927
|
+
*/
|
|
928
|
+
export class ScanOperationsRequest extends Message<ScanOperationsRequest> {
|
|
929
|
+
/**
|
|
930
|
+
* @generated from field: string cursor = 1;
|
|
931
|
+
*/
|
|
932
|
+
cursor = ''
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* @generated from field: int32 limit = 2;
|
|
936
|
+
*/
|
|
937
|
+
limit = 0
|
|
938
|
+
|
|
939
|
+
constructor(data?: PartialMessage<ScanOperationsRequest>) {
|
|
940
|
+
super()
|
|
941
|
+
proto3.util.initPartial(data, this)
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
static readonly runtime: typeof proto3 = proto3
|
|
945
|
+
static readonly typeName = 'bsync.ScanOperationsRequest'
|
|
946
|
+
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
|
947
|
+
{ no: 1, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
|
|
948
|
+
{ no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ },
|
|
949
|
+
])
|
|
950
|
+
|
|
951
|
+
static fromBinary(
|
|
952
|
+
bytes: Uint8Array,
|
|
953
|
+
options?: Partial<BinaryReadOptions>,
|
|
954
|
+
): ScanOperationsRequest {
|
|
955
|
+
return new ScanOperationsRequest().fromBinary(bytes, options)
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
static fromJson(
|
|
959
|
+
jsonValue: JsonValue,
|
|
960
|
+
options?: Partial<JsonReadOptions>,
|
|
961
|
+
): ScanOperationsRequest {
|
|
962
|
+
return new ScanOperationsRequest().fromJson(jsonValue, options)
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
static fromJsonString(
|
|
966
|
+
jsonString: string,
|
|
967
|
+
options?: Partial<JsonReadOptions>,
|
|
968
|
+
): ScanOperationsRequest {
|
|
969
|
+
return new ScanOperationsRequest().fromJsonString(jsonString, options)
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
static equals(
|
|
973
|
+
a: ScanOperationsRequest | PlainMessage<ScanOperationsRequest> | undefined,
|
|
974
|
+
b: ScanOperationsRequest | PlainMessage<ScanOperationsRequest> | undefined,
|
|
975
|
+
): boolean {
|
|
976
|
+
return proto3.util.equals(ScanOperationsRequest, a, b)
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* @generated from message bsync.ScanOperationsResponse
|
|
982
|
+
*/
|
|
983
|
+
export class ScanOperationsResponse extends Message<ScanOperationsResponse> {
|
|
984
|
+
/**
|
|
985
|
+
* @generated from field: repeated bsync.Operation operations = 1;
|
|
986
|
+
*/
|
|
987
|
+
operations: Operation[] = []
|
|
988
|
+
|
|
989
|
+
/**
|
|
990
|
+
* @generated from field: string cursor = 2;
|
|
991
|
+
*/
|
|
992
|
+
cursor = ''
|
|
993
|
+
|
|
994
|
+
constructor(data?: PartialMessage<ScanOperationsResponse>) {
|
|
995
|
+
super()
|
|
996
|
+
proto3.util.initPartial(data, this)
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
static readonly runtime: typeof proto3 = proto3
|
|
1000
|
+
static readonly typeName = 'bsync.ScanOperationsResponse'
|
|
1001
|
+
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
|
1002
|
+
{
|
|
1003
|
+
no: 1,
|
|
1004
|
+
name: 'operations',
|
|
1005
|
+
kind: 'message',
|
|
1006
|
+
T: Operation,
|
|
1007
|
+
repeated: true,
|
|
1008
|
+
},
|
|
1009
|
+
{ no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
|
|
1010
|
+
])
|
|
1011
|
+
|
|
1012
|
+
static fromBinary(
|
|
1013
|
+
bytes: Uint8Array,
|
|
1014
|
+
options?: Partial<BinaryReadOptions>,
|
|
1015
|
+
): ScanOperationsResponse {
|
|
1016
|
+
return new ScanOperationsResponse().fromBinary(bytes, options)
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
static fromJson(
|
|
1020
|
+
jsonValue: JsonValue,
|
|
1021
|
+
options?: Partial<JsonReadOptions>,
|
|
1022
|
+
): ScanOperationsResponse {
|
|
1023
|
+
return new ScanOperationsResponse().fromJson(jsonValue, options)
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
static fromJsonString(
|
|
1027
|
+
jsonString: string,
|
|
1028
|
+
options?: Partial<JsonReadOptions>,
|
|
1029
|
+
): ScanOperationsResponse {
|
|
1030
|
+
return new ScanOperationsResponse().fromJsonString(jsonString, options)
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
static equals(
|
|
1034
|
+
a:
|
|
1035
|
+
| ScanOperationsResponse
|
|
1036
|
+
| PlainMessage<ScanOperationsResponse>
|
|
1037
|
+
| undefined,
|
|
1038
|
+
b:
|
|
1039
|
+
| ScanOperationsResponse
|
|
1040
|
+
| PlainMessage<ScanOperationsResponse>
|
|
1041
|
+
| undefined,
|
|
1042
|
+
): boolean {
|
|
1043
|
+
return proto3.util.equals(ScanOperationsResponse, a, b)
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
692
1047
|
/**
|
|
693
1048
|
* Ping
|
|
694
1049
|
*
|
package/src/routes/index.ts
CHANGED
|
@@ -4,8 +4,10 @@ import { AppContext } from '../context'
|
|
|
4
4
|
import { Service } from '../proto/bsync_connect'
|
|
5
5
|
import addMuteOperation from './add-mute-operation'
|
|
6
6
|
import addNotifOperation from './add-notif-operation'
|
|
7
|
+
import putOperation from './put-operation'
|
|
7
8
|
import scanMuteOperations from './scan-mute-operations'
|
|
8
9
|
import scanNotifOperations from './scan-notif-operations'
|
|
10
|
+
import scanOperations from './scan-operations'
|
|
9
11
|
|
|
10
12
|
export default (ctx: AppContext) => (router: ConnectRouter) => {
|
|
11
13
|
return router.service(Service, {
|
|
@@ -13,6 +15,8 @@ export default (ctx: AppContext) => (router: ConnectRouter) => {
|
|
|
13
15
|
...scanMuteOperations(ctx),
|
|
14
16
|
...addNotifOperation(ctx),
|
|
15
17
|
...scanNotifOperations(ctx),
|
|
18
|
+
...putOperation(ctx),
|
|
19
|
+
...scanOperations(ctx),
|
|
16
20
|
async ping() {
|
|
17
21
|
const { db } = ctx
|
|
18
22
|
await sql`select 1`.execute(db.db)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Code, ConnectError, ServiceImpl } from '@connectrpc/connect'
|
|
2
|
+
import { sql } from 'kysely'
|
|
3
|
+
import { ensureValidNsid, ensureValidRecordKey } from '@atproto/syntax'
|
|
4
|
+
import { AppContext } from '../context'
|
|
5
|
+
import { Database } from '../db'
|
|
6
|
+
import { OperationMethod, createOperationChannel } from '../db/schema/operation'
|
|
7
|
+
import { Service } from '../proto/bsync_connect'
|
|
8
|
+
import {
|
|
9
|
+
Method,
|
|
10
|
+
PutOperationRequest,
|
|
11
|
+
PutOperationResponse,
|
|
12
|
+
} from '../proto/bsync_pb'
|
|
13
|
+
import { authWithApiKey } from './auth'
|
|
14
|
+
import { isValidDid } from './util'
|
|
15
|
+
|
|
16
|
+
export default (ctx: AppContext): Partial<ServiceImpl<typeof Service>> => ({
|
|
17
|
+
async putOperation(req, handlerCtx) {
|
|
18
|
+
authWithApiKey(ctx, handlerCtx)
|
|
19
|
+
const { db } = ctx
|
|
20
|
+
const op = validateOp(req)
|
|
21
|
+
const id = await db.transaction(async (txn) => {
|
|
22
|
+
return putOp(txn, op)
|
|
23
|
+
})
|
|
24
|
+
return new PutOperationResponse({
|
|
25
|
+
operation: {
|
|
26
|
+
id: String(id),
|
|
27
|
+
collection: op.collection,
|
|
28
|
+
actorDid: op.actorDid,
|
|
29
|
+
rkey: op.rkey,
|
|
30
|
+
method: op.method,
|
|
31
|
+
payload: op.payload,
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const putOp = async (db: Database, op: Operation) => {
|
|
38
|
+
const { ref } = db.db.dynamic
|
|
39
|
+
const { id } = await db.db
|
|
40
|
+
.insertInto('operation')
|
|
41
|
+
.values({
|
|
42
|
+
collection: op.collection,
|
|
43
|
+
actorDid: op.actorDid,
|
|
44
|
+
rkey: op.rkey,
|
|
45
|
+
method: op.method,
|
|
46
|
+
payload: op.payload,
|
|
47
|
+
})
|
|
48
|
+
.returning('id')
|
|
49
|
+
.executeTakeFirstOrThrow()
|
|
50
|
+
await sql`notify ${ref(createOperationChannel)}`.execute(db.db) // emitted transactionally
|
|
51
|
+
return id
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const validateOp = (req: PutOperationRequest): Operation => {
|
|
55
|
+
try {
|
|
56
|
+
ensureValidNsid(req.collection)
|
|
57
|
+
} catch (error) {
|
|
58
|
+
throw new ConnectError(
|
|
59
|
+
'operation collection is invalid NSID',
|
|
60
|
+
Code.InvalidArgument,
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!isValidDid(req.actorDid)) {
|
|
65
|
+
throw new ConnectError(
|
|
66
|
+
'operation actor_did is invalid DID',
|
|
67
|
+
Code.InvalidArgument,
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
ensureValidRecordKey(req.rkey)
|
|
73
|
+
} catch (error) {
|
|
74
|
+
throw new ConnectError('operation rkey is required', Code.InvalidArgument)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (
|
|
78
|
+
req.method !== Method.CREATE &&
|
|
79
|
+
req.method !== Method.UPDATE &&
|
|
80
|
+
req.method !== Method.DELETE
|
|
81
|
+
) {
|
|
82
|
+
throw new ConnectError('operation method is invalid', Code.InvalidArgument)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (req.method === Method.DELETE && req.payload.length > 0) {
|
|
86
|
+
throw new ConnectError(
|
|
87
|
+
'cannot specify a payload when method is DELETE',
|
|
88
|
+
Code.InvalidArgument,
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return req as Operation
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
type Operation = {
|
|
96
|
+
collection: string
|
|
97
|
+
actorDid: string
|
|
98
|
+
rkey: string
|
|
99
|
+
payload: Uint8Array
|
|
100
|
+
method: OperationMethod
|
|
101
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { once } from 'node:events'
|
|
2
|
+
import { ServiceImpl } from '@connectrpc/connect'
|
|
3
|
+
import { AppContext } from '../context'
|
|
4
|
+
import { createOperationChannel } from '../db/schema/operation'
|
|
5
|
+
import { Service } from '../proto/bsync_connect'
|
|
6
|
+
import { ScanOperationsResponse } from '../proto/bsync_pb'
|
|
7
|
+
import { authWithApiKey } from './auth'
|
|
8
|
+
import { combineSignals, validCursor } from './util'
|
|
9
|
+
|
|
10
|
+
export default (ctx: AppContext): Partial<ServiceImpl<typeof Service>> => ({
|
|
11
|
+
async scanOperations(req, handlerCtx) {
|
|
12
|
+
authWithApiKey(ctx, handlerCtx)
|
|
13
|
+
const { db, events } = ctx
|
|
14
|
+
const limit = req.limit || 1000
|
|
15
|
+
const cursor = validCursor(req.cursor)
|
|
16
|
+
const nextOpPromise = once(events, createOperationChannel, {
|
|
17
|
+
signal: combineSignals(
|
|
18
|
+
ctx.shutdown,
|
|
19
|
+
AbortSignal.timeout(ctx.cfg.service.longPollTimeoutMs),
|
|
20
|
+
),
|
|
21
|
+
})
|
|
22
|
+
nextOpPromise.catch(() => null) // ensure timeout is always handled
|
|
23
|
+
|
|
24
|
+
const nextOpPageQb = db.db
|
|
25
|
+
.selectFrom('operation')
|
|
26
|
+
.selectAll()
|
|
27
|
+
.where('id', '>', cursor ?? -1)
|
|
28
|
+
.orderBy('id', 'asc')
|
|
29
|
+
.limit(limit)
|
|
30
|
+
|
|
31
|
+
let ops = await nextOpPageQb.execute()
|
|
32
|
+
|
|
33
|
+
if (!ops.length) {
|
|
34
|
+
// if there were no ops on the page, wait for an event then try again.
|
|
35
|
+
try {
|
|
36
|
+
await nextOpPromise
|
|
37
|
+
} catch (err) {
|
|
38
|
+
ctx.shutdown.throwIfAborted()
|
|
39
|
+
return new ScanOperationsResponse({
|
|
40
|
+
operations: [],
|
|
41
|
+
cursor: req.cursor,
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
ops = await nextOpPageQb.execute()
|
|
45
|
+
if (!ops.length) {
|
|
46
|
+
return new ScanOperationsResponse({
|
|
47
|
+
operations: [],
|
|
48
|
+
cursor: req.cursor,
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const lastOp = ops[ops.length - 1]
|
|
54
|
+
|
|
55
|
+
return new ScanOperationsResponse({
|
|
56
|
+
operations: ops.map((op) => ({
|
|
57
|
+
id: op.id.toString(),
|
|
58
|
+
collection: op.collection,
|
|
59
|
+
actorDid: op.actorDid,
|
|
60
|
+
rkey: op.rkey,
|
|
61
|
+
method: op.method,
|
|
62
|
+
payload: op.payload,
|
|
63
|
+
})),
|
|
64
|
+
cursor: lastOp.id.toString(),
|
|
65
|
+
})
|
|
66
|
+
},
|
|
67
|
+
})
|