@decentrl/event-store 0.0.7 → 0.0.8
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/event-store.d.ts +15 -1
- package/dist/event-store.d.ts.map +1 -1
- package/dist/event-store.js +85 -1
- package/dist/event-store.test.js +138 -9
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/event-store.test.ts +179 -10
- package/src/event-store.ts +149 -1
- package/src/types.ts +28 -0
package/dist/event-store.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type EventStoreConfig, type PaginatedResult, type PublishOptions, type QueryOptions } from './types';
|
|
1
|
+
import { type EventStoreConfig, type PaginatedResult, type PublicEventResult, type PublicPublishOptions, type PublicQueryOptions, type PublishOptions, type QueryOptions } from './types';
|
|
2
2
|
export declare class DecentrlEventStore {
|
|
3
3
|
private config;
|
|
4
4
|
constructor(config: EventStoreConfig);
|
|
@@ -40,6 +40,20 @@ export declare class DecentrlEventStore {
|
|
|
40
40
|
}): Promise<PaginatedResult<T & {
|
|
41
41
|
_mediatorEventId?: string;
|
|
42
42
|
}>>;
|
|
43
|
+
/**
|
|
44
|
+
* Publish a public (unencrypted, signed) event to the mediator.
|
|
45
|
+
*/
|
|
46
|
+
publishPublicEvent(event: string, options: PublicPublishOptions): Promise<{
|
|
47
|
+
publicEventId: string;
|
|
48
|
+
}>;
|
|
49
|
+
/**
|
|
50
|
+
* Delete a public event from the mediator.
|
|
51
|
+
*/
|
|
52
|
+
deletePublicEvent(publicEventId: string): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Fetch public events from any publisher (no identity needed).
|
|
55
|
+
*/
|
|
56
|
+
static fetchPublicEvents(options: PublicQueryOptions): Promise<PaginatedResult<PublicEventResult>>;
|
|
43
57
|
/**
|
|
44
58
|
* Shared logic: filter by known senders, decrypt, store locally, acknowledge.
|
|
45
59
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-store.d.ts","sourceRoot":"","sources":["../src/event-store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"event-store.d.ts","sourceRoot":"","sources":["../src/event-store.ts"],"names":[],"mappings":"AAqBA,OAAO,EACN,KAAK,gBAAgB,EAErB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,MAAM,SAAS,CAAC;AAQjB,qBAAa,kBAAkB;IAClB,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,gBAAgB;IAE5C;;OAEG;IACG,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBvE;;OAEG;IACG,WAAW,CAAC,CAAC,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IA2E7E;;OAEG;IACG,oBAAoB,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC;IAiD7C;;;OAGG;IACG,8BAA8B,CAAC,CAAC,EACrC,SAAS,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,GACnE,OAAO,CAAC,CAAC,EAAE,CAAC;IAgBf;;OAEG;IACG,eAAe,CACpB,MAAM,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,GACzD,OAAO,CAAC,IAAI,CAAC;IAoBhB;;;OAGG;IACG,sBAAsB,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE;QAC5C,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC,GAAG;QAAE,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAkE/D;;OAEG;IACG,kBAAkB,CACvB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,oBAAoB,GAC3B,OAAO,CAAC;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IAwCrC;;OAEG;IACG,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4B7D;;OAEG;WACU,iBAAiB,CAC7B,OAAO,EAAE,kBAAkB,GACzB,OAAO,CAAC,eAAe,CAAC,iBAAiB,CAAC,CAAC;IA2D9C;;OAEG;YACW,kBAAkB;IAgFhC;;OAEG;YACW,eAAe;IAkD7B;;OAEG;YACW,YAAY;IA2C1B;;OAEG;YACW,kBAAkB;IAyChC;;OAEG;YACW,wBAAwB;CAmBtC"}
|
package/dist/event-store.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { base64Decode, decryptString, encryptString, generateEncryptedTag, multibaseDecode, signJsonObject, verifyJsonSignature, } from '@decentrl/crypto';
|
|
2
2
|
import { generateDirectAuthenticatedMediatorCommand, generateTwoWayPrivateMediatorCommand, } from '@decentrl/identity/communication-channels/mediator/direct-authenticated/command/command.service';
|
|
3
|
+
import { generateOneWayPublicMediatorCommand } from '@decentrl/identity/communication-channels/mediator/one-way-public/command/command.service';
|
|
3
4
|
import axiosModule from 'axios';
|
|
4
5
|
import { EventStoreError, } from './types';
|
|
5
6
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
@@ -187,6 +188,88 @@ export class DecentrlEventStore {
|
|
|
187
188
|
throw new EventStoreError(`Failed to query unprocessed events: ${error instanceof Error ? error.message : String(error)}`, 'QUERY_FAILED', { error });
|
|
188
189
|
}
|
|
189
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Publish a public (unencrypted, signed) event to the mediator.
|
|
193
|
+
*/
|
|
194
|
+
async publishPublicEvent(event, options) {
|
|
195
|
+
const { identity } = this.config;
|
|
196
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
197
|
+
const signedData = {
|
|
198
|
+
channel_id: options.channelId,
|
|
199
|
+
event,
|
|
200
|
+
tags: options.tags,
|
|
201
|
+
timestamp,
|
|
202
|
+
};
|
|
203
|
+
const eventSignature = signJsonObject(signedData, identity.keys.signing.privateKey);
|
|
204
|
+
const command = generateOneWayPublicMediatorCommand(identity.did, `${identity.did}#signing`, identity.mediatorDid, {
|
|
205
|
+
type: 'PUBLISH_PUBLIC_EVENT',
|
|
206
|
+
channel_id: options.channelId,
|
|
207
|
+
event,
|
|
208
|
+
tags: options.tags,
|
|
209
|
+
timestamp,
|
|
210
|
+
event_signature: eventSignature,
|
|
211
|
+
}, identity.keys);
|
|
212
|
+
const response = await axios.post(identity.mediatorEndpoint, command);
|
|
213
|
+
if (response.data.type !== 'SUCCESS') {
|
|
214
|
+
throw new EventStoreError('Failed to publish public event', 'PUBLISH_FAILED', response.data);
|
|
215
|
+
}
|
|
216
|
+
return { publicEventId: response.data.public_event_id };
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Delete a public event from the mediator.
|
|
220
|
+
*/
|
|
221
|
+
async deletePublicEvent(publicEventId) {
|
|
222
|
+
const { identity } = this.config;
|
|
223
|
+
const command = generateOneWayPublicMediatorCommand(identity.did, `${identity.did}#signing`, identity.mediatorDid, {
|
|
224
|
+
type: 'DELETE_PUBLIC_EVENT',
|
|
225
|
+
public_event_id: publicEventId,
|
|
226
|
+
}, identity.keys);
|
|
227
|
+
const response = await axios.post(identity.mediatorEndpoint, command);
|
|
228
|
+
if (response.data.type === 'ERROR') {
|
|
229
|
+
throw new EventStoreError(`Failed to delete public event: ${response.data.code}`, 'DELETE_FAILED', response.data);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Fetch public events from any publisher (no identity needed).
|
|
234
|
+
*/
|
|
235
|
+
static async fetchPublicEvents(options) {
|
|
236
|
+
const params = new URLSearchParams();
|
|
237
|
+
if (options.channelId) {
|
|
238
|
+
params.set('channel_id', options.channelId);
|
|
239
|
+
}
|
|
240
|
+
if (options.tags?.length) {
|
|
241
|
+
params.set('tags', options.tags.join(','));
|
|
242
|
+
}
|
|
243
|
+
if (options.afterTimestamp) {
|
|
244
|
+
params.set('after', String(options.afterTimestamp));
|
|
245
|
+
}
|
|
246
|
+
if (options.beforeTimestamp) {
|
|
247
|
+
params.set('before', String(options.beforeTimestamp));
|
|
248
|
+
}
|
|
249
|
+
if (options.pagination) {
|
|
250
|
+
params.set('page', String(options.pagination.page));
|
|
251
|
+
params.set('page_size', String(options.pagination.pageSize));
|
|
252
|
+
}
|
|
253
|
+
const url = `${options.mediatorEndpoint}/public/${options.publisherDid}?${params.toString()}`;
|
|
254
|
+
const response = await axiosModule.get(url, {
|
|
255
|
+
timeout: DEFAULT_TIMEOUT_MS,
|
|
256
|
+
});
|
|
257
|
+
return {
|
|
258
|
+
data: response.data.events.map((e) => ({
|
|
259
|
+
id: e.id,
|
|
260
|
+
channelId: e.channel_id,
|
|
261
|
+
event: e.event,
|
|
262
|
+
tags: e.tags,
|
|
263
|
+
timestamp: e.timestamp,
|
|
264
|
+
eventSignature: e.event_signature,
|
|
265
|
+
})),
|
|
266
|
+
pagination: {
|
|
267
|
+
page: response.data.pagination.page,
|
|
268
|
+
pageSize: response.data.pagination.page_size,
|
|
269
|
+
total: response.data.pagination.total,
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
190
273
|
/**
|
|
191
274
|
* Shared logic: filter by known senders, decrypt, store locally, acknowledge.
|
|
192
275
|
*/
|
|
@@ -232,7 +315,8 @@ export class DecentrlEventStore {
|
|
|
232
315
|
}
|
|
233
316
|
}
|
|
234
317
|
const event = JSON.parse(envelope.event);
|
|
235
|
-
|
|
318
|
+
const eventWithMeta = event;
|
|
319
|
+
if (!eventWithMeta?.meta?.ephemeral) {
|
|
236
320
|
await this.storeReceivedEvent(event, pendingEvent.sender_did, contract.id);
|
|
237
321
|
}
|
|
238
322
|
processedEvents.push(event);
|
package/dist/event-store.test.js
CHANGED
|
@@ -13,12 +13,31 @@ vi.mock('@decentrl/identity/communication-channels/mediator/direct-authenticated
|
|
|
13
13
|
generateDirectAuthenticatedMediatorCommand: vi.fn(() => ({ mock: 'command' })),
|
|
14
14
|
generateTwoWayPrivateMediatorCommand: vi.fn(() => ({ mock: 'two-way-command' })),
|
|
15
15
|
}));
|
|
16
|
-
vi.mock('
|
|
17
|
-
|
|
18
|
-
post: vi.fn(async () => ({ data: { type: 'SUCCESS', payload: {} } })),
|
|
19
|
-
},
|
|
16
|
+
vi.mock('@decentrl/identity/communication-channels/mediator/one-way-public/command/command.service', () => ({
|
|
17
|
+
generateOneWayPublicMediatorCommand: vi.fn(() => ({ mock: 'one-way-public-command' })),
|
|
20
18
|
}));
|
|
19
|
+
vi.mock('axios', () => {
|
|
20
|
+
const post = vi.fn(async () => ({ data: { type: 'SUCCESS', payload: {} } }));
|
|
21
|
+
const get = vi.fn(async () => ({
|
|
22
|
+
data: {
|
|
23
|
+
publisher_did: 'did:decentrl:bob',
|
|
24
|
+
events: [
|
|
25
|
+
{
|
|
26
|
+
id: 'pub-evt-1',
|
|
27
|
+
channel_id: 'blog',
|
|
28
|
+
event: '{"type":"blog.post","data":{"title":"Hello"}}',
|
|
29
|
+
tags: ['blog'],
|
|
30
|
+
timestamp: 1710000000,
|
|
31
|
+
event_signature: 'sig-1',
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
pagination: { page: 0, page_size: 20, total: 1 },
|
|
35
|
+
},
|
|
36
|
+
}));
|
|
37
|
+
return { default: { post, get }, post, get };
|
|
38
|
+
});
|
|
21
39
|
import { decryptString } from '@decentrl/crypto';
|
|
40
|
+
import axios from 'axios';
|
|
22
41
|
import { DecentrlEventStore } from './event-store.js';
|
|
23
42
|
const makeContract = (opts) => ({
|
|
24
43
|
id: opts.id,
|
|
@@ -249,25 +268,135 @@ describe('DecentrlEventStore', () => {
|
|
|
249
268
|
vi.mocked(decryptString).mockReset();
|
|
250
269
|
});
|
|
251
270
|
it('stores locally for non-ephemeral events', async () => {
|
|
252
|
-
const
|
|
271
|
+
const axiosModule = await import('axios');
|
|
253
272
|
const eventStore = new DecentrlEventStore({
|
|
254
273
|
identity: mockIdentity,
|
|
255
274
|
communicationContracts: () => [],
|
|
256
275
|
});
|
|
257
276
|
await eventStore.publishEvent({ type: 'test' }, { tags: ['tag1'] });
|
|
258
277
|
// Should have called axios.post for SAVE_EVENTS
|
|
259
|
-
expect(
|
|
278
|
+
expect(axiosModule.default.post).toHaveBeenCalled();
|
|
260
279
|
});
|
|
261
280
|
it('skips local storage for ephemeral events without recipient', async () => {
|
|
262
|
-
const
|
|
263
|
-
vi.mocked(
|
|
281
|
+
const axiosModule = await import('axios');
|
|
282
|
+
vi.mocked(axiosModule.default.post).mockClear();
|
|
264
283
|
const eventStore = new DecentrlEventStore({
|
|
265
284
|
identity: mockIdentity,
|
|
266
285
|
communicationContracts: () => [],
|
|
267
286
|
});
|
|
268
287
|
await eventStore.publishEvent({ type: 'test' }, { tags: ['tag1'], ephemeral: true });
|
|
269
288
|
// Should NOT have called axios.post since no recipient and ephemeral
|
|
270
|
-
expect(
|
|
289
|
+
expect(axiosModule.default.post).not.toHaveBeenCalled();
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
describe('publishPublicEvent', () => {
|
|
293
|
+
beforeEach(() => {
|
|
294
|
+
vi.mocked(axios.post).mockResolvedValue({
|
|
295
|
+
data: { type: 'SUCCESS', public_event_id: 'pub-123' },
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
it('signs the event data and sends a ONE_WAY_PUBLIC command', async () => {
|
|
299
|
+
const { signJsonObject } = await import('@decentrl/crypto');
|
|
300
|
+
const { generateOneWayPublicMediatorCommand } = await import('@decentrl/identity/communication-channels/mediator/one-way-public/command/command.service');
|
|
301
|
+
const eventStore = new DecentrlEventStore({
|
|
302
|
+
identity: mockIdentity,
|
|
303
|
+
communicationContracts: () => [],
|
|
304
|
+
});
|
|
305
|
+
const result = await eventStore.publishPublicEvent('{"type":"test","data":{}}', {
|
|
306
|
+
channelId: 'blog',
|
|
307
|
+
tags: ['blog', 'test'],
|
|
308
|
+
});
|
|
309
|
+
expect(result.publicEventId).toBe('pub-123');
|
|
310
|
+
expect(signJsonObject).toHaveBeenCalled();
|
|
311
|
+
expect(generateOneWayPublicMediatorCommand).toHaveBeenCalled();
|
|
312
|
+
expect(axios.post).toHaveBeenCalledWith('http://mediator', { mock: 'one-way-public-command' }, { timeout: 30000 });
|
|
313
|
+
});
|
|
314
|
+
it('uses seconds for timestamp', async () => {
|
|
315
|
+
const { signJsonObject } = await import('@decentrl/crypto');
|
|
316
|
+
const eventStore = new DecentrlEventStore({
|
|
317
|
+
identity: mockIdentity,
|
|
318
|
+
communicationContracts: () => [],
|
|
319
|
+
});
|
|
320
|
+
await eventStore.publishPublicEvent('test', { channelId: 'blog', tags: [] });
|
|
321
|
+
const signedData = vi.mocked(signJsonObject).mock.calls[0][0];
|
|
322
|
+
// Timestamp should be in seconds (roughly current time / 1000)
|
|
323
|
+
expect(signedData.timestamp).toBeLessThan(Date.now());
|
|
324
|
+
expect(signedData.timestamp).toBeGreaterThan(Date.now() / 1000 - 10);
|
|
325
|
+
});
|
|
326
|
+
it('throws on error response', async () => {
|
|
327
|
+
vi.mocked(axios.post).mockResolvedValue({
|
|
328
|
+
data: { type: 'ERROR', code: 'INVALID_EVENT_SIGNATURE' },
|
|
329
|
+
});
|
|
330
|
+
const eventStore = new DecentrlEventStore({
|
|
331
|
+
identity: mockIdentity,
|
|
332
|
+
communicationContracts: () => [],
|
|
333
|
+
});
|
|
334
|
+
await expect(eventStore.publishPublicEvent('test', { channelId: 'blog', tags: [] })).rejects.toThrow('Failed to publish public event');
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
describe('deletePublicEvent', () => {
|
|
338
|
+
it('sends a DELETE_PUBLIC_EVENT command', async () => {
|
|
339
|
+
vi.mocked(axios.post).mockResolvedValue({
|
|
340
|
+
data: { type: 'SUCCESS' },
|
|
341
|
+
});
|
|
342
|
+
const eventStore = new DecentrlEventStore({
|
|
343
|
+
identity: mockIdentity,
|
|
344
|
+
communicationContracts: () => [],
|
|
345
|
+
});
|
|
346
|
+
await expect(eventStore.deletePublicEvent('pub-123')).resolves.toBeUndefined();
|
|
347
|
+
expect(axios.post).toHaveBeenCalled();
|
|
348
|
+
});
|
|
349
|
+
it('throws on error response', async () => {
|
|
350
|
+
vi.mocked(axios.post).mockResolvedValue({
|
|
351
|
+
data: { type: 'ERROR', code: 'PUBLIC_EVENT_NOT_FOUND' },
|
|
352
|
+
});
|
|
353
|
+
const eventStore = new DecentrlEventStore({
|
|
354
|
+
identity: mockIdentity,
|
|
355
|
+
communicationContracts: () => [],
|
|
356
|
+
});
|
|
357
|
+
await expect(eventStore.deletePublicEvent('nonexistent')).rejects.toThrow('Failed to delete public event');
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
describe('fetchPublicEvents (static)', () => {
|
|
361
|
+
it('fetches events via GET without requiring identity', async () => {
|
|
362
|
+
const result = await DecentrlEventStore.fetchPublicEvents({
|
|
363
|
+
mediatorEndpoint: 'http://mediator',
|
|
364
|
+
publisherDid: 'did:decentrl:bob',
|
|
365
|
+
channelId: 'blog',
|
|
366
|
+
});
|
|
367
|
+
expect(result.data).toHaveLength(1);
|
|
368
|
+
expect(result.data[0].id).toBe('pub-evt-1');
|
|
369
|
+
expect(result.data[0].channelId).toBe('blog');
|
|
370
|
+
expect(result.data[0].timestamp).toBe(1710000000);
|
|
371
|
+
expect(result.pagination.total).toBe(1);
|
|
372
|
+
});
|
|
373
|
+
it('passes query parameters to the GET URL', async () => {
|
|
374
|
+
vi.mocked(axios.get).mockClear();
|
|
375
|
+
await DecentrlEventStore.fetchPublicEvents({
|
|
376
|
+
mediatorEndpoint: 'http://mediator',
|
|
377
|
+
publisherDid: 'did:decentrl:bob',
|
|
378
|
+
channelId: 'blog',
|
|
379
|
+
tags: ['tag1', 'tag2'],
|
|
380
|
+
afterTimestamp: 1710000000,
|
|
381
|
+
pagination: { page: 2, pageSize: 10 },
|
|
382
|
+
});
|
|
383
|
+
const url = vi.mocked(axios.get).mock.calls[0][0];
|
|
384
|
+
expect(url).toContain('/public/did:decentrl:bob');
|
|
385
|
+
expect(url).toContain('channel_id=blog');
|
|
386
|
+
expect(url).toContain('tags=tag1%2Ctag2');
|
|
387
|
+
expect(url).toContain('after=1710000000');
|
|
388
|
+
expect(url).toContain('page=2');
|
|
389
|
+
expect(url).toContain('page_size=10');
|
|
390
|
+
});
|
|
391
|
+
it('maps snake_case response to camelCase', async () => {
|
|
392
|
+
const result = await DecentrlEventStore.fetchPublicEvents({
|
|
393
|
+
mediatorEndpoint: 'http://mediator',
|
|
394
|
+
publisherDid: 'did:decentrl:bob',
|
|
395
|
+
});
|
|
396
|
+
// Verify camelCase mapping
|
|
397
|
+
expect(result.data[0].channelId).toBe('blog');
|
|
398
|
+
expect(result.data[0].eventSignature).toBe('sig-1');
|
|
399
|
+
expect(result.pagination.pageSize).toBe(20);
|
|
271
400
|
});
|
|
272
401
|
});
|
|
273
402
|
});
|
package/dist/types.d.ts
CHANGED
|
@@ -45,6 +45,32 @@ export interface PaginatedResult<T> {
|
|
|
45
45
|
data: T[];
|
|
46
46
|
pagination: PaginationMeta;
|
|
47
47
|
}
|
|
48
|
+
export interface PublicPublishOptions {
|
|
49
|
+
channelId: string;
|
|
50
|
+
tags: string[];
|
|
51
|
+
}
|
|
52
|
+
export interface PublicQueryOptions {
|
|
53
|
+
mediatorEndpoint: string;
|
|
54
|
+
publisherDid: string;
|
|
55
|
+
channelId?: string;
|
|
56
|
+
tags?: string[];
|
|
57
|
+
/** Unix timestamp in seconds */
|
|
58
|
+
afterTimestamp?: number;
|
|
59
|
+
/** Unix timestamp in seconds */
|
|
60
|
+
beforeTimestamp?: number;
|
|
61
|
+
pagination?: {
|
|
62
|
+
page: number;
|
|
63
|
+
pageSize: number;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export interface PublicEventResult {
|
|
67
|
+
id: string;
|
|
68
|
+
channelId: string;
|
|
69
|
+
event: string;
|
|
70
|
+
tags: string[];
|
|
71
|
+
timestamp: number;
|
|
72
|
+
eventSignature: string;
|
|
73
|
+
}
|
|
48
74
|
export declare class EventStoreError extends Error {
|
|
49
75
|
code: string;
|
|
50
76
|
details?: unknown | undefined;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,yEAAyE,CAAC;AAE3H,MAAM,WAAW,gBAAgB;IAChC,QAAQ,EAAE;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,oBAAoB,CAAC;QAC3B,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,sBAAsB,EAAE,MAAM,oBAAoB,EAAE,CAAC;IACrD,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;CACtE;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,EAAE,MAAM,CAAC;IACvB,2BAA2B,EAAE,2BAA2B,CAAC;IACzD,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,eAAe,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe,CAAC,CAAC;IACjC,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,UAAU,EAAE,cAAc,CAAC;CAC3B;AAED,qBAAa,eAAgB,SAAQ,KAAK;IAGjC,IAAI,EAAE,MAAM;IACZ,OAAO,CAAC,EAAE,OAAO;gBAFxB,OAAO,EAAE,MAAM,EACR,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,OAAO,YAAA;CAKzB"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,yEAAyE,CAAC;AAE3H,MAAM,WAAW,gBAAgB;IAChC,QAAQ,EAAE;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,oBAAoB,CAAC;QAC3B,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,sBAAsB,EAAE,MAAM,oBAAoB,EAAE,CAAC;IACrD,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;CACtE;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,EAAE,MAAM,CAAC;IACvB,2BAA2B,EAAE,2BAA2B,CAAC;IACzD,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,eAAe,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe,CAAC,CAAC;IACjC,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,UAAU,EAAE,cAAc,CAAC;CAC3B;AAID,MAAM,WAAW,oBAAoB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,EAAE,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IAClC,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,gCAAgC;IAChC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gCAAgC;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAChD;AAED,MAAM,WAAW,iBAAiB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,eAAgB,SAAQ,KAAK;IAGjC,IAAI,EAAE,MAAM;IACZ,OAAO,CAAC,EAAE,OAAO;gBAFxB,OAAO,EAAE,MAAM,EACR,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,OAAO,YAAA;CAKzB"}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentrl/event-store",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Event-driven storage and communication library for decentrl",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"axios": "^1.6.0",
|
|
9
|
-
"@decentrl/
|
|
10
|
-
"@decentrl/
|
|
9
|
+
"@decentrl/crypto": "0.0.8",
|
|
10
|
+
"@decentrl/identity": "0.0.8"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"typescript": "^5.0.0",
|
package/src/event-store.test.ts
CHANGED
|
@@ -19,13 +19,37 @@ vi.mock(
|
|
|
19
19
|
}),
|
|
20
20
|
);
|
|
21
21
|
|
|
22
|
-
vi.mock(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
})
|
|
22
|
+
vi.mock(
|
|
23
|
+
'@decentrl/identity/communication-channels/mediator/one-way-public/command/command.service',
|
|
24
|
+
() => ({
|
|
25
|
+
generateOneWayPublicMediatorCommand: vi.fn(() => ({ mock: 'one-way-public-command' })),
|
|
26
|
+
}),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
vi.mock('axios', () => {
|
|
30
|
+
const post = vi.fn(async () => ({ data: { type: 'SUCCESS', payload: {} } }));
|
|
31
|
+
const get = vi.fn(async () => ({
|
|
32
|
+
data: {
|
|
33
|
+
publisher_did: 'did:decentrl:bob',
|
|
34
|
+
events: [
|
|
35
|
+
{
|
|
36
|
+
id: 'pub-evt-1',
|
|
37
|
+
channel_id: 'blog',
|
|
38
|
+
event: '{"type":"blog.post","data":{"title":"Hello"}}',
|
|
39
|
+
tags: ['blog'],
|
|
40
|
+
timestamp: 1710000000,
|
|
41
|
+
event_signature: 'sig-1',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
pagination: { page: 0, page_size: 20, total: 1 },
|
|
45
|
+
},
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
return { default: { post, get }, post, get };
|
|
49
|
+
});
|
|
27
50
|
|
|
28
51
|
import { decryptString } from '@decentrl/crypto';
|
|
52
|
+
import axios from 'axios';
|
|
29
53
|
import { DecentrlEventStore } from './event-store.js';
|
|
30
54
|
import type { StoredSignedContract } from './types.js';
|
|
31
55
|
|
|
@@ -310,7 +334,7 @@ describe('DecentrlEventStore', () => {
|
|
|
310
334
|
});
|
|
311
335
|
|
|
312
336
|
it('stores locally for non-ephemeral events', async () => {
|
|
313
|
-
const
|
|
337
|
+
const axiosModule = await import('axios');
|
|
314
338
|
|
|
315
339
|
const eventStore = new DecentrlEventStore({
|
|
316
340
|
identity: mockIdentity,
|
|
@@ -320,12 +344,12 @@ describe('DecentrlEventStore', () => {
|
|
|
320
344
|
await eventStore.publishEvent({ type: 'test' }, { tags: ['tag1'] });
|
|
321
345
|
|
|
322
346
|
// Should have called axios.post for SAVE_EVENTS
|
|
323
|
-
expect(
|
|
347
|
+
expect(axiosModule.default.post).toHaveBeenCalled();
|
|
324
348
|
});
|
|
325
349
|
|
|
326
350
|
it('skips local storage for ephemeral events without recipient', async () => {
|
|
327
|
-
const
|
|
328
|
-
vi.mocked(
|
|
351
|
+
const axiosModule = await import('axios');
|
|
352
|
+
vi.mocked(axiosModule.default.post).mockClear();
|
|
329
353
|
|
|
330
354
|
const eventStore = new DecentrlEventStore({
|
|
331
355
|
identity: mockIdentity,
|
|
@@ -335,7 +359,152 @@ describe('DecentrlEventStore', () => {
|
|
|
335
359
|
await eventStore.publishEvent({ type: 'test' }, { tags: ['tag1'], ephemeral: true });
|
|
336
360
|
|
|
337
361
|
// Should NOT have called axios.post since no recipient and ephemeral
|
|
338
|
-
expect(
|
|
362
|
+
expect(axiosModule.default.post).not.toHaveBeenCalled();
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
describe('publishPublicEvent', () => {
|
|
367
|
+
beforeEach(() => {
|
|
368
|
+
vi.mocked(axios.post).mockResolvedValue({
|
|
369
|
+
data: { type: 'SUCCESS', public_event_id: 'pub-123' },
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('signs the event data and sends a ONE_WAY_PUBLIC command', async () => {
|
|
374
|
+
const { signJsonObject } = await import('@decentrl/crypto');
|
|
375
|
+
const { generateOneWayPublicMediatorCommand } = await import(
|
|
376
|
+
'@decentrl/identity/communication-channels/mediator/one-way-public/command/command.service'
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
const eventStore = new DecentrlEventStore({
|
|
380
|
+
identity: mockIdentity,
|
|
381
|
+
communicationContracts: () => [],
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const result = await eventStore.publishPublicEvent('{"type":"test","data":{}}', {
|
|
385
|
+
channelId: 'blog',
|
|
386
|
+
tags: ['blog', 'test'],
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
expect(result.publicEventId).toBe('pub-123');
|
|
390
|
+
expect(signJsonObject).toHaveBeenCalled();
|
|
391
|
+
expect(generateOneWayPublicMediatorCommand).toHaveBeenCalled();
|
|
392
|
+
expect(axios.post).toHaveBeenCalledWith(
|
|
393
|
+
'http://mediator',
|
|
394
|
+
{ mock: 'one-way-public-command' },
|
|
395
|
+
{ timeout: 30000 },
|
|
396
|
+
);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('uses seconds for timestamp', async () => {
|
|
400
|
+
const { signJsonObject } = await import('@decentrl/crypto');
|
|
401
|
+
|
|
402
|
+
const eventStore = new DecentrlEventStore({
|
|
403
|
+
identity: mockIdentity,
|
|
404
|
+
communicationContracts: () => [],
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
await eventStore.publishPublicEvent('test', { channelId: 'blog', tags: [] });
|
|
408
|
+
|
|
409
|
+
const signedData = vi.mocked(signJsonObject).mock.calls[0][0] as Record<string, unknown>;
|
|
410
|
+
// Timestamp should be in seconds (roughly current time / 1000)
|
|
411
|
+
expect(signedData.timestamp).toBeLessThan(Date.now());
|
|
412
|
+
expect(signedData.timestamp).toBeGreaterThan(Date.now() / 1000 - 10);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it('throws on error response', async () => {
|
|
416
|
+
vi.mocked(axios.post).mockResolvedValue({
|
|
417
|
+
data: { type: 'ERROR', code: 'INVALID_EVENT_SIGNATURE' },
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const eventStore = new DecentrlEventStore({
|
|
421
|
+
identity: mockIdentity,
|
|
422
|
+
communicationContracts: () => [],
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
await expect(
|
|
426
|
+
eventStore.publishPublicEvent('test', { channelId: 'blog', tags: [] }),
|
|
427
|
+
).rejects.toThrow('Failed to publish public event');
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
describe('deletePublicEvent', () => {
|
|
432
|
+
it('sends a DELETE_PUBLIC_EVENT command', async () => {
|
|
433
|
+
vi.mocked(axios.post).mockResolvedValue({
|
|
434
|
+
data: { type: 'SUCCESS' },
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const eventStore = new DecentrlEventStore({
|
|
438
|
+
identity: mockIdentity,
|
|
439
|
+
communicationContracts: () => [],
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
await expect(eventStore.deletePublicEvent('pub-123')).resolves.toBeUndefined();
|
|
443
|
+
expect(axios.post).toHaveBeenCalled();
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it('throws on error response', async () => {
|
|
447
|
+
vi.mocked(axios.post).mockResolvedValue({
|
|
448
|
+
data: { type: 'ERROR', code: 'PUBLIC_EVENT_NOT_FOUND' },
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
const eventStore = new DecentrlEventStore({
|
|
452
|
+
identity: mockIdentity,
|
|
453
|
+
communicationContracts: () => [],
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
await expect(eventStore.deletePublicEvent('nonexistent')).rejects.toThrow(
|
|
457
|
+
'Failed to delete public event',
|
|
458
|
+
);
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
describe('fetchPublicEvents (static)', () => {
|
|
463
|
+
it('fetches events via GET without requiring identity', async () => {
|
|
464
|
+
const result = await DecentrlEventStore.fetchPublicEvents({
|
|
465
|
+
mediatorEndpoint: 'http://mediator',
|
|
466
|
+
publisherDid: 'did:decentrl:bob',
|
|
467
|
+
channelId: 'blog',
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
expect(result.data).toHaveLength(1);
|
|
471
|
+
expect(result.data[0].id).toBe('pub-evt-1');
|
|
472
|
+
expect(result.data[0].channelId).toBe('blog');
|
|
473
|
+
expect(result.data[0].timestamp).toBe(1710000000);
|
|
474
|
+
expect(result.pagination.total).toBe(1);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it('passes query parameters to the GET URL', async () => {
|
|
478
|
+
vi.mocked(axios.get).mockClear();
|
|
479
|
+
|
|
480
|
+
await DecentrlEventStore.fetchPublicEvents({
|
|
481
|
+
mediatorEndpoint: 'http://mediator',
|
|
482
|
+
publisherDid: 'did:decentrl:bob',
|
|
483
|
+
channelId: 'blog',
|
|
484
|
+
tags: ['tag1', 'tag2'],
|
|
485
|
+
afterTimestamp: 1710000000,
|
|
486
|
+
pagination: { page: 2, pageSize: 10 },
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
const url = vi.mocked(axios.get).mock.calls[0][0] as string;
|
|
490
|
+
expect(url).toContain('/public/did:decentrl:bob');
|
|
491
|
+
expect(url).toContain('channel_id=blog');
|
|
492
|
+
expect(url).toContain('tags=tag1%2Ctag2');
|
|
493
|
+
expect(url).toContain('after=1710000000');
|
|
494
|
+
expect(url).toContain('page=2');
|
|
495
|
+
expect(url).toContain('page_size=10');
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('maps snake_case response to camelCase', async () => {
|
|
499
|
+
const result = await DecentrlEventStore.fetchPublicEvents({
|
|
500
|
+
mediatorEndpoint: 'http://mediator',
|
|
501
|
+
publisherDid: 'did:decentrl:bob',
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// Verify camelCase mapping
|
|
505
|
+
expect(result.data[0].channelId).toBe('blog');
|
|
506
|
+
expect(result.data[0].eventSignature).toBe('sig-1');
|
|
507
|
+
expect(result.pagination.pageSize).toBe(20);
|
|
339
508
|
});
|
|
340
509
|
});
|
|
341
510
|
});
|
package/src/event-store.ts
CHANGED
|
@@ -16,11 +16,16 @@ import type { QueryEventsMediatorCommandResponse } from '@decentrl/identity/comm
|
|
|
16
16
|
import type { QueryPendingEventsMediatorCommandResponse } from '@decentrl/identity/communication-channels/mediator/direct-authenticated/command/query-pending-events.schema';
|
|
17
17
|
import type { SaveEventsMediatorCommandResponse } from '@decentrl/identity/communication-channels/mediator/direct-authenticated/command/save-events.schema';
|
|
18
18
|
import type { UpdateEventTagsMediatorCommandResponse } from '@decentrl/identity/communication-channels/mediator/direct-authenticated/command/update-event-tags.schema';
|
|
19
|
+
import type { PublishPublicEventResponse } from '@decentrl/identity/communication-channels/mediator/one-way-public/command/command.schema';
|
|
20
|
+
import { generateOneWayPublicMediatorCommand } from '@decentrl/identity/communication-channels/mediator/one-way-public/command/command.service';
|
|
19
21
|
import axiosModule from 'axios';
|
|
20
22
|
import {
|
|
21
23
|
type EventStoreConfig,
|
|
22
24
|
EventStoreError,
|
|
23
25
|
type PaginatedResult,
|
|
26
|
+
type PublicEventResult,
|
|
27
|
+
type PublicPublishOptions,
|
|
28
|
+
type PublicQueryOptions,
|
|
24
29
|
type PublishOptions,
|
|
25
30
|
type QueryOptions,
|
|
26
31
|
} from './types';
|
|
@@ -307,6 +312,147 @@ export class DecentrlEventStore {
|
|
|
307
312
|
}
|
|
308
313
|
}
|
|
309
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Publish a public (unencrypted, signed) event to the mediator.
|
|
317
|
+
*/
|
|
318
|
+
async publishPublicEvent(
|
|
319
|
+
event: string,
|
|
320
|
+
options: PublicPublishOptions,
|
|
321
|
+
): Promise<{ publicEventId: string }> {
|
|
322
|
+
const { identity } = this.config;
|
|
323
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
324
|
+
|
|
325
|
+
const signedData = {
|
|
326
|
+
channel_id: options.channelId,
|
|
327
|
+
event,
|
|
328
|
+
tags: options.tags,
|
|
329
|
+
timestamp,
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const eventSignature = signJsonObject(signedData, identity.keys.signing.privateKey);
|
|
333
|
+
|
|
334
|
+
const command = generateOneWayPublicMediatorCommand(
|
|
335
|
+
identity.did,
|
|
336
|
+
`${identity.did}#signing`,
|
|
337
|
+
identity.mediatorDid,
|
|
338
|
+
{
|
|
339
|
+
type: 'PUBLISH_PUBLIC_EVENT',
|
|
340
|
+
channel_id: options.channelId,
|
|
341
|
+
event,
|
|
342
|
+
tags: options.tags,
|
|
343
|
+
timestamp,
|
|
344
|
+
event_signature: eventSignature,
|
|
345
|
+
},
|
|
346
|
+
identity.keys,
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
const response = await axios.post<PublishPublicEventResponse>(
|
|
350
|
+
identity.mediatorEndpoint,
|
|
351
|
+
command,
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
if (response.data.type !== 'SUCCESS') {
|
|
355
|
+
throw new EventStoreError('Failed to publish public event', 'PUBLISH_FAILED', response.data);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return { publicEventId: response.data.public_event_id };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Delete a public event from the mediator.
|
|
363
|
+
*/
|
|
364
|
+
async deletePublicEvent(publicEventId: string): Promise<void> {
|
|
365
|
+
const { identity } = this.config;
|
|
366
|
+
|
|
367
|
+
const command = generateOneWayPublicMediatorCommand(
|
|
368
|
+
identity.did,
|
|
369
|
+
`${identity.did}#signing`,
|
|
370
|
+
identity.mediatorDid,
|
|
371
|
+
{
|
|
372
|
+
type: 'DELETE_PUBLIC_EVENT',
|
|
373
|
+
public_event_id: publicEventId,
|
|
374
|
+
},
|
|
375
|
+
identity.keys,
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
const response = await axios.post<{ type: string; code?: string }>(
|
|
379
|
+
identity.mediatorEndpoint,
|
|
380
|
+
command,
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
if (response.data.type === 'ERROR') {
|
|
384
|
+
throw new EventStoreError(
|
|
385
|
+
`Failed to delete public event: ${response.data.code}`,
|
|
386
|
+
'DELETE_FAILED',
|
|
387
|
+
response.data,
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Fetch public events from any publisher (no identity needed).
|
|
394
|
+
*/
|
|
395
|
+
static async fetchPublicEvents(
|
|
396
|
+
options: PublicQueryOptions,
|
|
397
|
+
): Promise<PaginatedResult<PublicEventResult>> {
|
|
398
|
+
const params = new URLSearchParams();
|
|
399
|
+
|
|
400
|
+
if (options.channelId) {
|
|
401
|
+
params.set('channel_id', options.channelId);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (options.tags?.length) {
|
|
405
|
+
params.set('tags', options.tags.join(','));
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (options.afterTimestamp) {
|
|
409
|
+
params.set('after', String(options.afterTimestamp));
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (options.beforeTimestamp) {
|
|
413
|
+
params.set('before', String(options.beforeTimestamp));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (options.pagination) {
|
|
417
|
+
params.set('page', String(options.pagination.page));
|
|
418
|
+
params.set('page_size', String(options.pagination.pageSize));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const url = `${options.mediatorEndpoint}/public/${options.publisherDid}?${params.toString()}`;
|
|
422
|
+
|
|
423
|
+
interface PublicEventsResponse {
|
|
424
|
+
events: Array<{
|
|
425
|
+
id: string;
|
|
426
|
+
channel_id: string;
|
|
427
|
+
event: string;
|
|
428
|
+
tags: string[];
|
|
429
|
+
timestamp: number;
|
|
430
|
+
event_signature: string;
|
|
431
|
+
}>;
|
|
432
|
+
pagination: { page: number; page_size: number; total: number };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const response = await axiosModule.get<PublicEventsResponse>(url, {
|
|
436
|
+
timeout: DEFAULT_TIMEOUT_MS,
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
return {
|
|
440
|
+
data: response.data.events.map((e) => ({
|
|
441
|
+
id: e.id,
|
|
442
|
+
channelId: e.channel_id,
|
|
443
|
+
event: e.event,
|
|
444
|
+
tags: e.tags,
|
|
445
|
+
timestamp: e.timestamp,
|
|
446
|
+
eventSignature: e.event_signature,
|
|
447
|
+
})),
|
|
448
|
+
pagination: {
|
|
449
|
+
page: response.data.pagination.page,
|
|
450
|
+
pageSize: response.data.pagination.page_size,
|
|
451
|
+
total: response.data.pagination.total,
|
|
452
|
+
},
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
310
456
|
/**
|
|
311
457
|
* Shared logic: filter by known senders, decrypt, store locally, acknowledge.
|
|
312
458
|
*/
|
|
@@ -370,7 +516,9 @@ export class DecentrlEventStore {
|
|
|
370
516
|
|
|
371
517
|
const event = JSON.parse(envelope.event) as T;
|
|
372
518
|
|
|
373
|
-
|
|
519
|
+
const eventWithMeta = event as { meta?: { ephemeral?: boolean } };
|
|
520
|
+
|
|
521
|
+
if (!eventWithMeta?.meta?.ephemeral) {
|
|
374
522
|
await this.storeReceivedEvent(event, pendingEvent.sender_did, contract.id);
|
|
375
523
|
}
|
|
376
524
|
|
package/src/types.ts
CHANGED
|
@@ -49,6 +49,34 @@ export interface PaginatedResult<T> {
|
|
|
49
49
|
pagination: PaginationMeta;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
// --- Public Events ---
|
|
53
|
+
|
|
54
|
+
export interface PublicPublishOptions {
|
|
55
|
+
channelId: string;
|
|
56
|
+
tags: string[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface PublicQueryOptions {
|
|
60
|
+
mediatorEndpoint: string;
|
|
61
|
+
publisherDid: string;
|
|
62
|
+
channelId?: string;
|
|
63
|
+
tags?: string[];
|
|
64
|
+
/** Unix timestamp in seconds */
|
|
65
|
+
afterTimestamp?: number;
|
|
66
|
+
/** Unix timestamp in seconds */
|
|
67
|
+
beforeTimestamp?: number;
|
|
68
|
+
pagination?: { page: number; pageSize: number };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface PublicEventResult {
|
|
72
|
+
id: string;
|
|
73
|
+
channelId: string;
|
|
74
|
+
event: string;
|
|
75
|
+
tags: string[];
|
|
76
|
+
timestamp: number;
|
|
77
|
+
eventSignature: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
52
80
|
export class EventStoreError extends Error {
|
|
53
81
|
constructor(
|
|
54
82
|
message: string,
|