jazz-tools 0.18.31 → 0.18.33

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.
Files changed (53) hide show
  1. package/.svelte-kit/__package__/media/image.svelte +1 -1
  2. package/.svelte-kit/__package__/tests/media/image.svelte.test.js +1 -1
  3. package/.turbo/turbo-build.log +58 -58
  4. package/CHANGELOG.md +23 -0
  5. package/dist/{chunk-6BIYT3KH.js → chunk-OSQ7S47Q.js} +3 -3
  6. package/dist/chunk-OSQ7S47Q.js.map +1 -0
  7. package/dist/index.js +1 -1
  8. package/dist/inspector/{custom-element-RQTLPAPJ.js → custom-element-RBBL46TI.js} +636 -193
  9. package/dist/inspector/custom-element-RBBL46TI.js.map +1 -0
  10. package/dist/inspector/index.js +626 -183
  11. package/dist/inspector/index.js.map +1 -1
  12. package/dist/inspector/register-custom-element.js +1 -1
  13. package/dist/inspector/tests/ui/data-table.test.d.ts +2 -0
  14. package/dist/inspector/tests/ui/data-table.test.d.ts.map +1 -0
  15. package/dist/inspector/tests/viewer/history-view.test.d.ts +2 -0
  16. package/dist/inspector/tests/viewer/history-view.test.d.ts.map +1 -0
  17. package/dist/inspector/ui/data-table.d.ts +23 -0
  18. package/dist/inspector/ui/data-table.d.ts.map +1 -0
  19. package/dist/inspector/ui/index.d.ts +1 -0
  20. package/dist/inspector/ui/index.d.ts.map +1 -1
  21. package/dist/inspector/viewer/history-view.d.ts +6 -0
  22. package/dist/inspector/viewer/history-view.d.ts.map +1 -0
  23. package/dist/inspector/viewer/page.d.ts.map +1 -1
  24. package/dist/svelte/media/image.svelte +1 -1
  25. package/dist/svelte/tests/media/image.svelte.test.js +1 -1
  26. package/dist/testing.js +1 -1
  27. package/dist/tools/coValues/coFeed.d.ts +1 -1
  28. package/dist/tools/coValues/coFeed.d.ts.map +1 -1
  29. package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts +2 -2
  30. package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts.map +1 -1
  31. package/dist/worker/index.d.ts +2 -1
  32. package/dist/worker/index.d.ts.map +1 -1
  33. package/dist/worker/index.js +2 -1
  34. package/dist/worker/index.js.map +1 -1
  35. package/package.json +4 -4
  36. package/src/inspector/tests/ui/data-table.test.tsx +296 -0
  37. package/src/inspector/tests/viewer/history-view.test.tsx +246 -0
  38. package/src/inspector/ui/data-table.tsx +265 -0
  39. package/src/inspector/ui/index.ts +1 -0
  40. package/src/inspector/viewer/history-view.tsx +379 -0
  41. package/src/inspector/viewer/page.tsx +2 -0
  42. package/src/media/create-image-factory.test.ts +2 -2
  43. package/src/svelte/media/image.svelte +1 -1
  44. package/src/svelte/tests/media/image.svelte.test.ts +1 -1
  45. package/src/tools/coValues/coFeed.ts +2 -2
  46. package/src/tools/coValues/interfaces.ts +1 -1
  47. package/src/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.ts +1 -1
  48. package/src/tools/tests/CoValueCoreSubscription.test.ts +1 -1
  49. package/src/tools/tests/coFeed.test.ts +1 -1
  50. package/src/tools/tests/load.test.ts +1 -1
  51. package/src/worker/index.ts +9 -1
  52. package/dist/chunk-6BIYT3KH.js.map +0 -1
  53. package/dist/inspector/custom-element-RQTLPAPJ.js.map +0 -1
@@ -0,0 +1,379 @@
1
+ import {
2
+ AccountRole,
3
+ BinaryStreamStart,
4
+ CoID,
5
+ JsonValue,
6
+ LocalNode,
7
+ OpID,
8
+ RawCoValue,
9
+ Role,
10
+ } from "cojson";
11
+ import { useMemo } from "react";
12
+ import { styled } from "goober";
13
+ import { isCoId } from "./types";
14
+ import { AccountOrGroupText } from "./account-or-group-text";
15
+ import { DataTable, ColumnDef } from "../ui/data-table";
16
+ import { Heading } from "../ui/heading";
17
+ import { MapOpPayload } from "cojson/dist/coValues/coMap.js";
18
+ import {
19
+ DeletionOpPayload,
20
+ InsertionOpPayload,
21
+ } from "cojson/dist/coValues/coList.js";
22
+ import {
23
+ BinaryStreamChunk,
24
+ BinaryStreamEnd,
25
+ } from "cojson/dist/coValues/coStream.js";
26
+ import { VerifiedTransaction } from "cojson/dist/coValueCore/coValueCore.js";
27
+ import { Icon } from "../ui";
28
+
29
+ type HistoryEntry = {
30
+ id: string;
31
+ author: string;
32
+ action: string;
33
+ timestamp: Date;
34
+ isValid: boolean;
35
+ };
36
+
37
+ export function HistoryView({
38
+ coValue,
39
+ node,
40
+ }: {
41
+ coValue: RawCoValue;
42
+ node: LocalNode;
43
+ }) {
44
+ const transactions = useMemo(
45
+ () => getHistory(coValue),
46
+ [coValue.core.verifiedTransactions.length],
47
+ );
48
+
49
+ const columns: ColumnDef<HistoryEntry>[] = [
50
+ {
51
+ id: "author",
52
+ header: "Author",
53
+ accessor: (row) => (
54
+ <>
55
+ {row.isValid || (
56
+ <RedTooltip data-text="This transaction is invalid and is not used">
57
+ <Icon
58
+ name="caution"
59
+ size="xs"
60
+ color="red"
61
+ style={{
62
+ display: "inline-block",
63
+ verticalAlign: "middle",
64
+ marginRight: "0.25rem",
65
+ }}
66
+ />
67
+ </RedTooltip>
68
+ )}
69
+ {row.author.startsWith("co_") ? (
70
+ <AccountOrGroupText
71
+ coId={row.author as CoID<RawCoValue>}
72
+ node={node}
73
+ showId
74
+ />
75
+ ) : (
76
+ row.author
77
+ )}
78
+ </>
79
+ ),
80
+ sortable: false,
81
+ filterable: true,
82
+ sortFn: (a, b) => a.author.localeCompare(b.author),
83
+ filterFn: (row, filterValue) =>
84
+ row.author.toLowerCase().includes(filterValue.toLowerCase()),
85
+ },
86
+ {
87
+ id: "action",
88
+ header: "Action",
89
+ accessor: (row) => row.action,
90
+ sortable: false,
91
+ filterable: true,
92
+ sortFn: (a, b) => a.action.localeCompare(b.action),
93
+ },
94
+ {
95
+ id: "timestamp",
96
+ header: "Timestamp",
97
+ accessor: (row) => row.timestamp.toISOString(),
98
+ sortable: true,
99
+ filterable: true,
100
+ sortFn: (a, b) => a.timestamp.getTime() - b.timestamp.getTime(),
101
+ },
102
+ ];
103
+
104
+ return (
105
+ <section style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
106
+ <Heading>CoValue history</Heading>
107
+ <DataTable
108
+ columns={columns}
109
+ data={transactions}
110
+ pageSize={10}
111
+ initialSort={{ columnId: "timestamp", direction: "desc" }}
112
+ getRowKey={(row) => row.id}
113
+ emptyMessage="No history available"
114
+ />
115
+ </section>
116
+ );
117
+ }
118
+
119
+ function getTransactionChanges(
120
+ tx: VerifiedTransaction,
121
+ coValue: RawCoValue,
122
+ ): JsonValue[] {
123
+ if (tx.isValid === false && tx.tx.privacy === "private") {
124
+ const readKey = coValue.core.getReadKey(tx.tx.keyUsed);
125
+ if (!readKey) {
126
+ throw new Error("Read key not found");
127
+ }
128
+
129
+ return (
130
+ coValue.core.verified.decryptTransaction(
131
+ tx.txID.sessionID,
132
+ tx.txID.txIndex,
133
+ readKey,
134
+ ) ?? []
135
+ );
136
+
137
+ // const decryptedString = coValue.core.verified.sessions.get(tx.txID.sessionID)?.impl.decryptNextTransactionChangesJson(tx.txID.txIndex, readKey);
138
+
139
+ // return decryptedString ? [decryptedString] : [];
140
+ }
141
+
142
+ return tx.changes ?? (tx.tx as any).changes ?? [];
143
+ }
144
+
145
+ function getHistory(coValue: RawCoValue): HistoryEntry[] {
146
+ return coValue.core.verifiedTransactions.flatMap((tx, index) => {
147
+ const changes = getTransactionChanges(tx, coValue);
148
+
149
+ return changes.map((change, changeIndex) => ({
150
+ id: `${tx.txID.sessionID.toString()}-${tx.txID.txIndex}-${index}-${changeIndex}`,
151
+ author: tx.author,
152
+ action: mapTransactionToAction(change, coValue),
153
+ timestamp: new Date(tx.currentMadeAt),
154
+ isValid: tx.isValid,
155
+ }));
156
+ });
157
+ }
158
+
159
+ function mapTransactionToAction(
160
+ change: JsonValue,
161
+ coValue: RawCoValue,
162
+ ): string {
163
+ // Group changes
164
+ if (isUserPromotion(change)) {
165
+ if (change.value === "revoked") {
166
+ return `${change.key} has been revoked`;
167
+ }
168
+
169
+ return `${change.key} has been promoted to ${change.value}`;
170
+ }
171
+
172
+ if (isGroupExtension(change)) {
173
+ const child = change.key.slice(6);
174
+ return `Group became a member of ${child}`;
175
+ }
176
+
177
+ if (isGroupExtendRevocation(change)) {
178
+ const child = change.key.slice(6);
179
+ return `Group's membership of ${child} has been revoked.`;
180
+ }
181
+
182
+ if (isGroupPromotion(change)) {
183
+ const parent = change.key.slice(7);
184
+ return `Group ${parent} has been promoted to ${change.value}`;
185
+ }
186
+
187
+ if (isKeyRevelation(change)) {
188
+ const [key, target] = change.key.split("_for_");
189
+ return `Key "${key}" has been revealed to "${target}"`;
190
+ }
191
+
192
+ // coList changes
193
+ if (isItemAppend(change)) {
194
+ if (change.after === "start") {
195
+ return `"${change.value}" has been appended`;
196
+ }
197
+
198
+ const after = findListChange(change.after, coValue);
199
+
200
+ if (after === undefined) {
201
+ return `"${change.value}" has been inserted after undefined item`;
202
+ }
203
+
204
+ return `"${change.value}" has been inserted after "${(after as any).value}"`;
205
+ }
206
+
207
+ if (isItemPrepend(change)) {
208
+ if (change.before === "end") {
209
+ return `"${change.value}" has been prepended`;
210
+ }
211
+
212
+ const before = findListChange(change.before, coValue);
213
+
214
+ if (before === undefined) {
215
+ return `"${change.value}" has been inserted before undefined item`;
216
+ }
217
+
218
+ return `"${change.value}" has been inserted before "${(before as any).value}"`;
219
+ }
220
+
221
+ if (isItemDeletion(change)) {
222
+ const insertion = findListChange(change.insertion, coValue);
223
+ if (insertion === undefined) {
224
+ return `An undefined item has been deleted`;
225
+ }
226
+
227
+ return `"${(insertion as any).value}" has been deleted`;
228
+ }
229
+
230
+ // coStream changes
231
+ if (isStreamStart(change)) {
232
+ return `Stream started with mime type "${change.mimeType}" and file name "${change.fileName}"`;
233
+ }
234
+
235
+ if (isStreamChunk(change)) {
236
+ return `Stream chunk added`;
237
+ }
238
+
239
+ if (isStreamEnd(change)) {
240
+ return `Stream ended`;
241
+ }
242
+
243
+ // coMap changes
244
+ if (isPropertySet(change)) {
245
+ return `Property "${change.key}" has been set to "${change.value}"`;
246
+ }
247
+
248
+ if (isPropertyDeletion(change)) {
249
+ return `Property "${change.key}" has been deleted`;
250
+ }
251
+
252
+ return "Unknown action: " + JSON.stringify(change);
253
+ }
254
+
255
+ const findListChange = (
256
+ opId: OpID,
257
+ coValue: RawCoValue,
258
+ ): JsonValue | undefined => {
259
+ return coValue.core.verifiedTransactions.find(
260
+ (tx) =>
261
+ tx.txID.sessionID === opId.sessionID && tx.txID.txIndex === opId.txIndex,
262
+ )?.changes?.[opId.changeIdx];
263
+ };
264
+
265
+ const isGroupExtension = (
266
+ change: any,
267
+ ): change is Extract<
268
+ MapOpPayload<`child_${string}`, "extend">,
269
+ { op: "set" }
270
+ > => {
271
+ return change?.op === "set" && change?.value === "extend";
272
+ };
273
+
274
+ const isGroupExtendRevocation = (
275
+ change: any,
276
+ ): change is Extract<
277
+ MapOpPayload<`child_${string}`, "revoked">,
278
+ { op: "set" }
279
+ > => {
280
+ return change?.op === "set" && change?.value === "revoked";
281
+ };
282
+
283
+ const isGroupPromotion = (
284
+ change: any,
285
+ ): change is Extract<
286
+ MapOpPayload<`parent_co_${string}`, AccountRole>,
287
+ { op: "set" }
288
+ > => {
289
+ return change?.op === "set" && change?.key.startsWith("parent_co_");
290
+ };
291
+
292
+ const isUserPromotion = (
293
+ change: any,
294
+ ): change is Extract<MapOpPayload<CoID<RawCoValue>, Role>, { op: "set" }> => {
295
+ return (
296
+ change?.op === "set" && (isCoId(change?.key) || change?.key === "everyone")
297
+ );
298
+ };
299
+
300
+ const isKeyRevelation = (
301
+ change: any,
302
+ ): change is Extract<
303
+ MapOpPayload<`${string}_for_${string}`, string>,
304
+ { op: "set" }
305
+ > => {
306
+ return change?.op === "set" && change?.key.includes("_for_");
307
+ };
308
+
309
+ const isPropertySet = (
310
+ change: any,
311
+ ): change is Extract<MapOpPayload<string, any>, { op: "set" }> => {
312
+ return change?.op === "set" && "key" in change && "value" in change;
313
+ };
314
+ const isPropertyDeletion = (
315
+ change: any,
316
+ ): change is Extract<MapOpPayload<string, any>, { op: "del" }> => {
317
+ return change?.op === "del" && "key" in change;
318
+ };
319
+
320
+ const isItemAppend = (
321
+ change: any,
322
+ ): change is Extract<InsertionOpPayload<any>, { op: "app" }> => {
323
+ return change?.op === "app" && "after" in change && "value" in change;
324
+ };
325
+ const isItemPrepend = (
326
+ change: any,
327
+ ): change is Extract<InsertionOpPayload<any>, { op: "pre" }> => {
328
+ return change?.op === "pre" && "before" in change && "value" in change;
329
+ };
330
+
331
+ const isItemDeletion = (
332
+ change: any,
333
+ ): change is Extract<DeletionOpPayload, { op: "del" }> => {
334
+ return change?.op === "del" && "insertion" in change;
335
+ };
336
+
337
+ const isStreamStart = (change: any): change is BinaryStreamStart => {
338
+ return change?.type === "start" && "mimeType" in change;
339
+ };
340
+
341
+ const isStreamChunk = (change: any): change is BinaryStreamChunk => {
342
+ return change?.type === "chunk" && "chunk" in change;
343
+ };
344
+
345
+ const isStreamEnd = (change: any): change is BinaryStreamEnd => {
346
+ return change?.type === "end";
347
+ };
348
+
349
+ const RedTooltip = styled("span")`
350
+ position:relative; /* making the .tooltip span a container for the tooltip text */
351
+ border-bottom:1px dashed #000; /* little indicater to indicate it's hoverable */
352
+
353
+ &:before {
354
+ content: attr(data-text);
355
+ background-color: red;
356
+ position:absolute;
357
+
358
+ /* vertically center */
359
+ top:50%;
360
+ transform:translateY(-50%);
361
+
362
+ /* move to right */
363
+ left:100%;
364
+ margin-left:15px; /* and add a small left margin */
365
+
366
+ /* basic styles */
367
+ width:200px;
368
+ padding:10px;
369
+ border-radius:10px;
370
+ color: #fff;
371
+ text-align:center;
372
+
373
+ display:none; /* hide by default */
374
+ }
375
+
376
+ &:hover:before {
377
+ display:block;
378
+ }
379
+ `;
@@ -22,6 +22,7 @@ import { TableView } from "./table-viewer.js";
22
22
  import { TypeIcon } from "./type-icon.js";
23
23
  import { PageInfo } from "./types.js";
24
24
  import { resolveCoValue, useResolvedCoValue } from "./use-resolve-covalue.js";
25
+ import { HistoryView } from "./history-view.js";
25
26
 
26
27
  interface PageContainerProps extends React.HTMLAttributes<HTMLDivElement> {
27
28
  isTopLevel?: boolean;
@@ -240,6 +241,7 @@ export function Page(props: PageProps) {
240
241
  </Text>
241
242
  </>
242
243
  )}
244
+ {value && <HistoryView coValue={value} node={node} />}
243
245
  </ContentContainer>
244
246
  </PageContainer>
245
247
  );
@@ -74,7 +74,7 @@ describe("createImage", async () => {
74
74
 
75
75
  getImageSize.mockResolvedValue({ width: 1, height: 1 });
76
76
  getPlaceholderBase64.mockResolvedValue(
77
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==",
77
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
78
78
  );
79
79
 
80
80
  const image = await createImage(imageBlob, {
@@ -204,7 +204,7 @@ describe("createImage", async () => {
204
204
 
205
205
  // 1x1 png
206
206
  const OnePixel = atob(
207
- "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==",
207
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
208
208
  );
209
209
 
210
210
  // Image 1920x400
@@ -113,7 +113,7 @@
113
113
  [
114
114
  Uint8Array.from(
115
115
  atob(
116
- "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==",
116
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
117
117
  ),
118
118
  (c) => c.charCodeAt(0),
119
119
  ),
@@ -626,7 +626,7 @@ describe("Image", async () => {
626
626
 
627
627
  const img = container.querySelector("img");
628
628
  expect(img).toBeDefined();
629
- expect(img!.src).toBe("blob:test-70");
629
+ expect(img!.src).toBe("blob:test-68");
630
630
  });
631
631
 
632
632
  it("should load the image when threshold is reached", async () => {
@@ -286,12 +286,12 @@ export class CoFeed<out Item = any> extends CoValueBase implements CoValue {
286
286
  static load<F extends CoFeed, const R extends RefsToResolve<F> = true>(
287
287
  this: CoValueClass<F>,
288
288
  id: ID<F>,
289
- options: {
289
+ options?: {
290
290
  resolve?: RefsToResolveStrict<F, R>;
291
291
  loadAs?: Account | AnonymousJazzAgent;
292
292
  },
293
293
  ): Promise<Resolved<F, R> | null> {
294
- return loadCoValueWithoutMe(this, id, options);
294
+ return loadCoValueWithoutMe(this, id, options ?? {});
295
295
  }
296
296
 
297
297
  /**
@@ -709,7 +709,7 @@ function loadContentPiecesFromCoValue(
709
709
  }
710
710
  }
711
711
 
712
- const pieces = core.verified.newContentSince(undefined) ?? [];
712
+ const pieces = core.newContentSince() ?? [];
713
713
 
714
714
  for (const piece of pieces) {
715
715
  contentPieces.push(piece);
@@ -78,7 +78,7 @@ export class FileStreamSchema implements CoreFileStreamSchema {
78
78
 
79
79
  load(
80
80
  id: string,
81
- options: { loadAs: Account | AnonymousJazzAgent },
81
+ options?: { loadAs?: Account | AnonymousJazzAgent },
82
82
  ): Promise<FileStream | null> {
83
83
  return this.coValueClass.load(id, options);
84
84
  }
@@ -1251,7 +1251,7 @@ describe("CoValueCoreSubscription", async () => {
1251
1251
  subscription.unsubscribe();
1252
1252
  });
1253
1253
 
1254
- test.skip("should wait for the full streaming of the parent group", async () => {
1254
+ test("should wait for the full streaming of the parent group", async () => {
1255
1255
  disableJazzTestSync();
1256
1256
 
1257
1257
  const alice = await createJazzTestAccount({
@@ -773,7 +773,7 @@ describe("FileStream large file loading", async () => {
773
773
 
774
774
  const loadedChunks = loadedStream.getChunks({ allowUnfinished: true });
775
775
  expect(loadedChunks).not.toBeNull();
776
- expect(loadedChunks?.finished).toBe(undefined);
776
+ expect(loadedChunks?.finished).toBeFalsy();
777
777
 
778
778
  expect(loadedStream.$jazz.raw.core.knownState()).not.toEqual(
779
779
  largeStream.$jazz.raw.core.knownState(),
@@ -337,7 +337,7 @@ test("should wait for the full streaming of the group", async () => {
337
337
  );
338
338
  });
339
339
 
340
- test.skip("should wait for the full streaming of the parent groups", async () => {
340
+ test("should wait for the full streaming of the parent groups", async () => {
341
341
  disableJazzTestSync();
342
342
 
343
343
  const alice = await createJazzTestAccount({
@@ -1,4 +1,10 @@
1
- import { AgentSecret, CryptoProvider, LocalNode, Peer } from "cojson";
1
+ import {
2
+ AgentSecret,
3
+ CryptoProvider,
4
+ LocalNode,
5
+ Peer,
6
+ StorageAPI,
7
+ } from "cojson";
2
8
  import {
3
9
  type AnyWebSocketConstructor,
4
10
  WebSocketPeerWithReconnection,
@@ -35,6 +41,7 @@ type WorkerOptions<
35
41
  * If false, the worker will not set in the global account context
36
42
  */
37
43
  asActiveAccount?: boolean;
44
+ storage?: StorageAPI;
38
45
  };
39
46
 
40
47
  /** @category Context Creation */
@@ -95,6 +102,7 @@ export async function startWorker<
95
102
  peers,
96
103
  crypto: options.crypto ?? (await WasmCrypto.create()),
97
104
  asActiveAccount,
105
+ storage: options.storage,
98
106
  });
99
107
 
100
108
  const account = context.account as InstanceOfSchema<S>;