Skip to content

Commit 54d06e8

Browse files
Merge upstream develop
2 parents 2cd4c8d + 8c44e81 commit 54d06e8

32 files changed

Lines changed: 904 additions & 610 deletions

File tree

.gitlab-ci.yml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
include:
2-
- local: .gitlab/ci/build_rs_core.yml
3-
- local: .gitlab/ci/build_js_sdks.yml
4-
- local: .gitlab/ci/build_server.yml
5-
- local: .gitlab/ci/deploy_pages.yml
6-
2+
- local: ".gitlab/ci/build_rs_core.yml"
3+
- local: ".gitlab/ci/build_js_sdks.yml"
4+
- local: ".gitlab/ci/build_server.yml"
5+
- local: ".gitlab/ci/deploy_pages.yml"
6+
- local: ".gitlab/ci/scan_dependencies.yml"
77
stages:
8-
- setup
9-
- check
10-
- build
11-
- publish
12-
- deploy
13-
8+
- setup
9+
- check
10+
- build
11+
- publish
12+
- deploy
13+
- test
1414
default:
1515
tags:
16-
- dind
16+
- dind

.gitlab/ci/scan_dependencies.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
trivy:
2+
stage: test
3+
image:
4+
name: aquasec/trivy:0.69.3
5+
entrypoint: [""]
6+
variables:
7+
TRIVY_NO_PROGRESS: "true"
8+
TRIVY_CACHE_DIR: ".trivycache/"
9+
allow_failure: true
10+
script:
11+
# Gitlab code quality report output
12+
- trivy filesystem --scanners misconfig,vuln --exit-code 0 --severity CRITICAL --format template --template "@/contrib/gitlab-codequality.tpl" -o gl-codeclimate.json .
13+
# Console output
14+
- trivy filesystem --scanners misconfig,vuln --exit-code 1 --severity CRITICAL .
15+
cache:
16+
paths:
17+
- .trivycache/
18+
artifacts:
19+
paths:
20+
- gl-codeclimate.json
21+
reports:
22+
codequality: gl-codeclimate.json

apps/polycentric/src/features/composer/ComposeSheetInner.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export function ComposeSheetInner({
9494

9595
const event = await client.buildEvent(content);
9696

97-
event.vectorClocks = await client.buildVectorClock(event);
97+
console.log(event);
9898

9999
const signedEvent = await client.signEvent(event);
100100

packages/js-browser/examples/react/src/components/posts/post-compose.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const PostCompose = () => {
2727
const event = await client.buildEvent(content);
2828

2929
const signedEvent = await client.signEvent(event);
30-
await client.commitEvent(signedEvent);
30+
await client.commitEvent(signedEvent, content);
3131

3232
postField.current.value = '';
3333
};

packages/js-browser/src/storage/indexeddb/content.repository.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,35 @@ export class IndexedDBContentRepository implements IContentRepository {
7676
throw new DatabaseError('Failed to get content: ', error);
7777
}
7878
}
79+
80+
async getAll(): Promise<
81+
{ digest: Proto.ContentDigest; content: Proto.Content }[]
82+
> {
83+
try {
84+
const transaction = this.database.createTransaction(
85+
IndexedDBContentRepository.STORE_NAME,
86+
'readonly',
87+
);
88+
const store = transaction.objectStore(
89+
IndexedDBContentRepository.STORE_NAME,
90+
);
91+
92+
const rows = await IndexedDBDatabase.requestAsPromise<
93+
{ digestHex: string; contentBytes: Uint8Array }[]
94+
>(store.getAll());
95+
96+
return rows.map((row) => {
97+
// digestHex encodes the full serialized ContentDigest proto.
98+
const digestBytes = new Uint8Array(
99+
row.digestHex.match(/.{1,2}/g)!.map((b) => parseInt(b, 16)),
100+
);
101+
return {
102+
digest: Proto.ContentDigest.fromBinary(digestBytes),
103+
content: Proto.Content.fromBinary(row.contentBytes),
104+
};
105+
});
106+
} catch (error) {
107+
throw new DatabaseError('Failed to get all content: ', error);
108+
}
109+
}
79110
}

packages/js-browser/src/storage/indexeddb/event.repository.ts

Lines changed: 48 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -132,83 +132,26 @@ export class IndexedDBEventRepository implements IEventRepository {
132132
}
133133
}
134134

135-
async getNextSequence(
136-
publicKey: Proto.PublicKey,
137-
collection: number,
138-
identity: string,
139-
): Promise<bigint> {
135+
async getByEventKey(key: Proto.EventKey): Promise<Proto.SignedEvent | null> {
136+
if (!key.signedBy) return null;
140137
try {
141-
const pubKeyHex = bytesToHex(Proto.PublicKey.toBinary(publicKey));
142-
console.log(pubKeyHex);
143-
144138
const transaction = this.database.createTransaction(
145139
IndexedDBEventRepository.STORE_NAME,
146140
'readonly',
147141
);
148142
const store = transaction.objectStore(
149143
IndexedDBEventRepository.STORE_NAME,
150144
);
151-
152-
const results = await IndexedDBDatabase.requestAsPromise<
153-
PersistedEvent[]
154-
>(store.getAll());
155-
156-
let maxSeq = 0n;
157-
for (const row of results) {
158-
if (
159-
row.publicKey === pubKeyHex &&
160-
row.collection === collection &&
161-
row.identity === identity
162-
) {
163-
const seq = BigInt(row.sequence);
164-
if (seq > maxSeq) maxSeq = seq;
165-
}
166-
}
167-
168-
return maxSeq + 1n;
145+
const pkHex = bytesToHex(Proto.PublicKey.toBinary(key.signedBy));
146+
const result = await IndexedDBDatabase.requestAsPromise<
147+
PersistedEvent | undefined
148+
>(store.get([key.identity, pkHex, key.collection, Number(key.sequence)]));
149+
return result ? this.toSignedEvent(result) : null;
169150
} catch (error) {
170-
throw new DatabaseError('Failed to get next sequence: ', error);
151+
throw new DatabaseError('Failed to get event by key: ', error);
171152
}
172153
}
173154

174-
async getEventsByIdentity(
175-
publicKey: Proto.PublicKey,
176-
identity: string,
177-
): Promise<Proto.SignedEvent[]> {
178-
try {
179-
const pubKeyHex = bytesToHex(Proto.PublicKey.toBinary(publicKey));
180-
181-
const transaction = this.database.createTransaction(
182-
IndexedDBEventRepository.STORE_NAME,
183-
'readonly',
184-
);
185-
const store = transaction.objectStore(
186-
IndexedDBEventRepository.STORE_NAME,
187-
);
188-
189-
const results = await IndexedDBDatabase.requestAsPromise<
190-
PersistedEvent[]
191-
>(store.getAll());
192-
193-
return results
194-
.filter(
195-
(row) => row.publicKey === pubKeyHex && row.identity === identity,
196-
)
197-
.sort((a, b) => a.sequence - b.sequence)
198-
.map((row) => this.toSignedEvent(row));
199-
} catch (error) {
200-
throw new DatabaseError('Failed to get events by identity: ', error);
201-
}
202-
}
203-
204-
async getLatestEvent(
205-
publicKey: Proto.PublicKey,
206-
identity: string,
207-
): Promise<Proto.SignedEvent | null> {
208-
const events = await this.getEventsByIdentity(publicKey, identity);
209-
return events.length > 0 ? events[events.length - 1] : null;
210-
}
211-
212155
async getBatch(
213156
batchSize: number,
214157
offset?: number,
@@ -222,7 +165,14 @@ export class IndexedDBEventRepository implements IEventRepository {
222165
return { events, offset: start + events.length };
223166
}
224167

225-
async getHeadsByIdentity(identity: string): Promise<Proto.SignedEvent[]> {
168+
async getByIdentity(
169+
identity: string,
170+
options?: {
171+
signer?: Proto.PublicKey;
172+
collection?: number;
173+
headsOnly?: boolean;
174+
},
175+
): Promise<Proto.SignedEvent[]> {
226176
try {
227177
const transaction = this.database.createTransaction(
228178
IndexedDBEventRepository.STORE_NAME,
@@ -233,34 +183,48 @@ export class IndexedDBEventRepository implements IEventRepository {
233183
);
234184

235185
// Compound keyPath: [identity, publicKey, collection, sequence].
236-
// Reverse cursor hits the max-sequence entry for each
237-
// (publicKey, collection) group first. After reading a head,
238-
// skip the rest of the group by continuing to
239-
// [identity, publicKey, collection] (without sequence) — this
240-
// compares less than any key with a sequence component, so the
241-
// reverse cursor jumps to the previous group's max.
242-
const range = IDBKeyRange.bound([identity], [identity, '\uffff']);
243-
const heads: PersistedEvent[] = [];
186+
// Build the longest prefix of fixed components so the cursor scans
187+
// only matching rows.
188+
const prefix: (string | number)[] = [identity];
189+
if (options?.signer) {
190+
prefix.push(bytesToHex(Proto.PublicKey.toBinary(options.signer)));
191+
if (options.collection !== undefined) prefix.push(options.collection);
192+
}
193+
const range = IDBKeyRange.bound(prefix, [...prefix, []]);
194+
const headsOnly = options?.headsOnly ?? false;
195+
const collectionFilter = options?.collection;
196+
197+
// headsOnly: walk in reverse; first hit per (signer, collection) is
198+
// its max-sequence entry, then skip the rest of the group.
199+
// Otherwise: walk forward and collect everything matching.
200+
const direction: IDBCursorDirection = headsOnly ? 'prev' : 'next';
201+
const rows: PersistedEvent[] = [];
244202

245203
await new Promise<void>((resolve, reject) => {
246-
const request = store.openCursor(range, 'prev');
204+
const request = store.openCursor(range, direction);
247205
request.onerror = () => reject(request.error);
248206
request.onsuccess = () => {
249207
const cursor = request.result;
250-
if (!cursor) {
251-
resolve();
252-
return;
253-
}
208+
if (!cursor) return resolve();
254209
const row = cursor.value as PersistedEvent;
255-
heads.push(row);
256-
// Skip to the previous group's max entry.
257-
cursor.continue([identity, row.publicKey, row.collection]);
210+
if (
211+
collectionFilter === undefined ||
212+
row.collection === collectionFilter
213+
) {
214+
rows.push(row);
215+
}
216+
if (headsOnly) {
217+
// Skip the rest of this (signer, collection) group.
218+
cursor.continue([identity, row.publicKey, row.collection]);
219+
} else {
220+
cursor.continue();
221+
}
258222
};
259223
});
260224

261-
return heads.map((row) => this.toSignedEvent(row));
225+
return rows.map((row) => this.toSignedEvent(row));
262226
} catch (error) {
263-
throw new DatabaseError('Failed to get heads by identity: ', error);
227+
throw new DatabaseError('Failed to get events by identity: ', error);
264228
}
265229
}
266230
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@ export class IdentityManager {
8989

9090
const digest = this.client.contentManager.buildDigest(content);
9191

92-
const sequence = await this.client.storage.events.getNextSequence(
93-
this.client.currentKeyPair.publicKey,
94-
COLLECTION.IDENTITY,
92+
const sequence = this.client.core!.next_sequence(
9593
identityKey,
94+
COLLECTION.IDENTITY,
95+
Proto.PublicKey.toBinary(this.client.currentKeyPair.publicKey),
9696
);
9797

9898
const event = Proto.Event.create({
@@ -109,7 +109,7 @@ export class IdentityManager {
109109

110110
await this.client.storage.content.save(digest, content);
111111
const signedEvent = await this.client.signEvent(event);
112-
await this.client.commitEvent(signedEvent);
112+
await this.client.commitEvent(signedEvent, content);
113113

114114
this.client.setActiveIdentityKey(identityKey);
115115

packages/js-core/src/platform-interfaces/content.repository.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,10 @@ export interface IContentRepository {
1919
* @returns The content bytes, or null if not found
2020
*/
2121
get(digest: Proto.ContentDigest): Promise<Proto.Content | null>;
22+
23+
/**
24+
* Return every stored (digest, content) pair. Used to hydrate the
25+
* in-memory Rust content store at startup.
26+
*/
27+
getAll(): Promise<{ digest: Proto.ContentDigest; content: Proto.Content }[]>;
2228
}

packages/js-core/src/platform-interfaces/event.repository.ts

Lines changed: 15 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,50 +19,29 @@ export interface IEventRepository {
1919
}>;
2020

2121
/**
22-
* Get the next sequence number for a given public key, collection, and identity.
23-
* Returns max(sequence) + 1 across all stored events matching, or 1n if none exist.
22+
* Point lookup by EventKey. Returns null if not present locally.
2423
*
25-
* @param publicKey - The public key bytes of the signer
26-
* @param collection - The collection ID
27-
* @param identity - The identity key (hex hash)
24+
* @param key - The full EventKey (collection, identity, signedBy, sequence)
2825
*/
29-
getNextSequence(
30-
publicKey: Proto.PublicKey,
31-
collection: number,
32-
identity: string,
33-
): Promise<bigint>;
26+
getByEventKey(key: Proto.EventKey): Promise<Proto.SignedEvent | null>;
3427

3528
/**
36-
* Get the event with the highest sequence number for a given public key and identity.
37-
* Returns null if no events exist for the key+identity.
29+
* Query events for an identity.
3830
*
39-
* @param publicKey - The public key bytes of the signer
40-
* @param identity - The identity key (hex hash)
41-
*/
42-
getLatestEvent(
43-
publicKey: Proto.PublicKey,
44-
identity: string,
45-
): Promise<Proto.SignedEvent | null>;
46-
47-
/**
48-
* Get all events for a given public key and identity, ordered by sequence ascending.
31+
* Optional `signer` and `collection` filters narrow the scan. When
32+
* `headsOnly` is true, returns one event per (signer, collection) — the
33+
* highest-sequence entry for each stream. Otherwise returns all matching
34+
* events sorted by sequence ascending.
4935
*
50-
* @param publicKey - The public key bytes of the signer
5136
* @param identity - The identity key (hex hash)
37+
* @param options - Optional filters and head-only flag
5238
*/
53-
getEventsByIdentity(
54-
publicKey: Proto.PublicKey,
39+
getByIdentity(
5540
identity: string,
41+
options?: {
42+
signer?: Proto.PublicKey;
43+
collection?: number;
44+
headsOnly?: boolean;
45+
},
5646
): Promise<Proto.SignedEvent[]>;
57-
58-
/**
59-
* Get the head (highest-sequence) event for each unique (signer, collection)
60-
* pair within a given identity. Useful for building vector clocks without
61-
* loading every event.
62-
*
63-
* @param identity - The identity key (hex hash)
64-
* @returns One SignedEvent per (signer, collection), each being the
65-
* highest-sequence event for that stream.
66-
*/
67-
getHeadsByIdentity(identity: string): Promise<Proto.SignedEvent[]>;
6847
}

0 commit comments

Comments
 (0)