jazz-tools 0.18.31 → 0.18.32

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 (42) hide show
  1. package/.turbo/turbo-build.log +61 -61
  2. package/CHANGELOG.md +11 -0
  3. package/dist/{chunk-6BIYT3KH.js → chunk-JXRJMGKV.js} +2 -2
  4. package/dist/chunk-JXRJMGKV.js.map +1 -0
  5. package/dist/index.js +1 -1
  6. package/dist/inspector/{custom-element-RQTLPAPJ.js → custom-element-RBBL46TI.js} +636 -193
  7. package/dist/inspector/custom-element-RBBL46TI.js.map +1 -0
  8. package/dist/inspector/index.js +626 -183
  9. package/dist/inspector/index.js.map +1 -1
  10. package/dist/inspector/register-custom-element.js +1 -1
  11. package/dist/inspector/tests/ui/data-table.test.d.ts +2 -0
  12. package/dist/inspector/tests/ui/data-table.test.d.ts.map +1 -0
  13. package/dist/inspector/tests/viewer/history-view.test.d.ts +2 -0
  14. package/dist/inspector/tests/viewer/history-view.test.d.ts.map +1 -0
  15. package/dist/inspector/ui/data-table.d.ts +23 -0
  16. package/dist/inspector/ui/data-table.d.ts.map +1 -0
  17. package/dist/inspector/ui/index.d.ts +1 -0
  18. package/dist/inspector/ui/index.d.ts.map +1 -1
  19. package/dist/inspector/viewer/history-view.d.ts +6 -0
  20. package/dist/inspector/viewer/history-view.d.ts.map +1 -0
  21. package/dist/inspector/viewer/page.d.ts.map +1 -1
  22. package/dist/testing.js +1 -1
  23. package/dist/tools/coValues/coFeed.d.ts +1 -1
  24. package/dist/tools/coValues/coFeed.d.ts.map +1 -1
  25. package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts +2 -2
  26. package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts.map +1 -1
  27. package/dist/worker/index.d.ts +2 -1
  28. package/dist/worker/index.d.ts.map +1 -1
  29. package/dist/worker/index.js +2 -1
  30. package/dist/worker/index.js.map +1 -1
  31. package/package.json +4 -4
  32. package/src/inspector/tests/ui/data-table.test.tsx +296 -0
  33. package/src/inspector/tests/viewer/history-view.test.tsx +246 -0
  34. package/src/inspector/ui/data-table.tsx +265 -0
  35. package/src/inspector/ui/index.ts +1 -0
  36. package/src/inspector/viewer/history-view.tsx +379 -0
  37. package/src/inspector/viewer/page.tsx +2 -0
  38. package/src/tools/coValues/coFeed.ts +2 -2
  39. package/src/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.ts +1 -1
  40. package/src/worker/index.ts +9 -1
  41. package/dist/chunk-6BIYT3KH.js.map +0 -1
  42. 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
  );
@@ -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
  /**
@@ -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
  }
@@ -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>;