Skip to content

Commit 5f3863d

Browse files
brandon-futomarkharding
authored andcommitted
[#87] Implement better syncing for events and blobs
Changelog: enhancement
1 parent eddbf08 commit 5f3863d

40 files changed

Lines changed: 3348 additions & 263 deletions

File tree

.gitlab/ci/build_js_sdks.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ js-sdks-format-check:
3030
- pnpm format
3131
allow_failure: true
3232

33+
js-core-tests-check:
34+
extends: .js-sdks-base
35+
stage: check
36+
allow_failure: true
37+
script:
38+
- pnpm --filter js-core test:run
39+
3340
js-sdks-build:
3441
extends: .js-sdks-base
3542
stage: build

apps/polycentric/src/common/lib/polycentric-hooks/PolycentricProvider.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,6 @@ export function PolycentricProvider({
180180

181181
if (cancelled) return;
182182

183-
c.servers = [DEFAULT_SERVER];
184-
185183
const s = createPolycentricStore(c);
186184
await s.getState().refreshIdentities();
187185

apps/polycentric/src/features/moderation/hooks/useReportAction.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { hexToBytes, usePolycentric } from '@/src/common/lib/polycentric-hooks';
2-
import { COLLECTION, v2 } from '@polycentric/react-native';
2+
import { COLLECTION, v2, SyncStrategy } from '@polycentric/react-native';
33
import { useState } from 'react';
44

55
export default function useReportAction() {
@@ -36,7 +36,7 @@ export default function useReportAction() {
3636
});
3737

3838
// Persist the content first: the event references it by digest, and
39-
// `push()` looks the content up from the local store to attach it.
39+
// `sync()` looks the content up from the local store to attach it.
4040
await client.contentManager.save(content);
4141

4242
const event = await client.buildEvent(content, COLLECTION.REPORTS);
@@ -48,7 +48,7 @@ export default function useReportAction() {
4848
// Delivery to servers is best-effort — the report is already saved
4949
// locally and will be pushed on the next sync if this fails.
5050
try {
51-
await client.push();
51+
await client.sync(SyncStrategy.PARTIAL_PUSH);
5252
} catch (e) {
5353
console.warn('Failed to push report to servers:', e);
5454
} finally {

apps/polycentric/src/features/post/hooks/usePostActions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
usePolycentric,
55
} from '@/src/common/lib/polycentric-hooks';
66
import { invalidateQuery } from '@/src/common/query/hooks/useQuery';
7-
import { COLLECTION, v2 } from '@polycentric/react-native';
7+
import { COLLECTION, v2, SyncStrategy } from '@polycentric/react-native';
88
import { router, useSegments } from 'expo-router';
99
import { feedQueryKeys } from '../../feed/hooks/feedCache';
1010

@@ -44,7 +44,7 @@ export default function usePostActions(post: PostData): PostActions {
4444
await client.commitEvent(signedDelete, deleteContent);
4545

4646
// TODO: do we care if push failed?
47-
await client.push();
47+
await client.sync(SyncStrategy.PARTIAL_PUSH);
4848
};
4949

5050
const invalidateFeeds = (identity: string) => {

packages/js-core/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"generate": "mkdir -p ./src/proto && protoc --ts_out=./src/proto --proto_path=../../protos --experimental_allow_proto3_optional ../../protos/polycentric.proto ../../protos/polycentric/v2/keypair.proto ../../protos/polycentric/v2/event_key.proto ../../protos/polycentric/v2/identity.proto ../../protos/polycentric/v2/content.proto ../../protos/polycentric/v2/events.proto ../../protos/polycentric/v2/feeds.proto ../../protos/polycentric/v2/server.proto ../../protos/polycentric/v2/pairing_service.proto ../../protos/polycentric/v2/notifications.proto",
1717
"build": "tsc -b && webpack",
1818
"dev": "tsc -b && webpack --watch",
19-
"test": "echo 'No tests'"
19+
"test": "vitest",
20+
"test:run": "vitest run"
2021
},
2122
"dependencies": {
2223
"@noble/curves": "catalog:",
@@ -34,6 +35,7 @@
3435
"@protobuf-ts/plugin": "^2.11.1",
3536
"ts-loader": "^9.5.4",
3637
"typescript": "catalog:",
38+
"vitest": "^3.2.6",
3739
"webpack": "^5.100.1",
3840
"webpack-cli": "^7.0.0"
3941
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export enum QueryStatus {
2+
Loading = 1,
3+
Success = 2,
4+
Error = 3,
5+
}
6+
7+
class ListEvents {
8+
readonly inner: [unknown];
9+
constructor(args: unknown) {
10+
this.inner = [args];
11+
}
12+
}
13+
14+
export const Query = { ListEvents };

packages/js-core/src/client-internal/content-manager.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,16 @@ export class ContentManager {
3333
}
3434

3535
/**
36-
* Download any blobs that we don't have locally from this content.
36+
* Download any blobs in the array that we don't have locally.
3737
* This is used so that blobs of an identity will eventually
3838
* persist on other devices in that identity.
3939
*/
40-
async pullBlobs(content: Proto.Content): Promise<void> {
41-
const digests = this.collectBlobDigests(content);
40+
async pullBlobs(blobs: Proto.Blob[]): Promise<void> {
41+
const digests = blobs.map((b) => b.digest).filter((d) => !!d);
42+
4243
if (digests.length === 0) return;
4344

44-
await Promise.all(
45+
await Promise.allSettled(
4546
digests.map(async (digest) => {
4647
try {
4748
if (await this.client.filestoreDriver.has(digest)) return;
@@ -56,14 +57,14 @@ export class ContentManager {
5657
}
5758

5859
/**
59-
* Collect all blob digests referenced in a post or profile update
60+
* Collect all blobs referenced in a post or profile update
6061
*/
61-
private collectBlobDigests(content: Proto.Content): Proto.ContentDigest[] {
62-
const out: Proto.ContentDigest[] = [];
62+
static collectBlobs(content: Proto.Content): Proto.Blob[] {
63+
const out: Proto.Blob[] = [];
6364
const pushSet = (set?: Proto.ImageSet) => {
6465
if (!set) return;
6566
for (const img of set.images) {
66-
if (img.blob?.digest) out.push(img.blob.digest);
67+
if (img.blob) out.push(img.blob);
6768
}
6869
};
6970
const body = content.contentBody;

packages/js-core/src/client-internal/identity-manager.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { sha256 } from '@noble/hashes/sha2.js';
22
import { Query, QueryStatus } from '@polycentric/rs-core-uniffi-web/generated';
3-
import { COLLECTION } from '../constants';
3+
import { COLLECTION, SyncStrategy } from '../constants';
44
import type { PolycentricClient } from '../polycentric-client';
55
import * as Proto from '../proto/v2';
66
import { bytesEqual } from '../utils/bytes';
@@ -137,7 +137,7 @@ export class IdentityManager {
137137

138138
const signedEvent = await this.client.signEvent(event);
139139
await this.client.commitEvent(signedEvent, content);
140-
await this.client.push();
140+
await this.client.sync(SyncStrategy.PARTIAL_PUSH);
141141

142142
return { identityKey: resolvedIdentityKey, signedEvent };
143143
}
@@ -252,7 +252,7 @@ export class IdentityManager {
252252
}
253253

254254
await this.client.setActiveIdentityKey(identityKey);
255-
await this.client.pull();
255+
await this.client.sync(SyncStrategy.PARTIAL_PULL);
256256

257257
// Re-publish the same identity document signed by our own key,
258258
// proving this key acknowledged its membership.

packages/js-core/src/constants.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,31 @@ export enum HydrationStrategy {
2323
LAZY = 'lazy',
2424
}
2525

26+
/**
27+
* Specifies how to sync events and blobs for an identity between the local store
28+
* and the remote servers.
29+
*/
30+
export enum SyncStrategy {
31+
/** Push and pull all events. */
32+
FULL,
33+
/** Push all events. */
34+
FULL_PUSH,
35+
/** Pull all events. */
36+
FULL_PULL,
37+
/** Push and pull only events believed to be missing. */
38+
PARTIAL,
39+
/**
40+
* Push only events believed to be missing.
41+
* No events are pulled.
42+
*/
43+
PARTIAL_PUSH,
44+
/**
45+
* Pull only events believed to be missing.
46+
* No events are pushed.
47+
*/
48+
PARTIAL_PULL,
49+
}
50+
2651
export const Defaults = {
2752
DB_NAME: 'polycentric-database',
2853
HYDRATION: {

packages/js-core/src/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,12 @@ export * from './errors';
4343
export * as Errors from './errors';
4444
export * from './utils';
4545

46-
export { KEY_TYPE, COLLECTION, HydrationStrategy } from './constants';
46+
export {
47+
KEY_TYPE,
48+
COLLECTION,
49+
HydrationStrategy,
50+
SyncStrategy,
51+
} from './constants';
4752

4853
export {
4954
ClientState,

0 commit comments

Comments
 (0)