@graphprotocol/hypergraph 0.0.1

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 (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -0
  3. package/dist/connect/auth-storage.d.ts.map +1 -0
  4. package/dist/connect/create-app-identity.d.ts.map +1 -0
  5. package/dist/connect/create-auth-url.d.ts.map +1 -0
  6. package/dist/connect/create-auth-url.js +35 -0
  7. package/dist/connect/create-auth-url.js.map +1 -0
  8. package/dist/connect/create-callback-params.d.ts.map +1 -0
  9. package/dist/connect/create-callback-params.js +17 -0
  10. package/dist/connect/create-callback-params.js.map +1 -0
  11. package/dist/connect/create-identity-keys.d.ts.map +1 -0
  12. package/dist/connect/identity-encryption.d.ts.map +1 -0
  13. package/dist/connect/index.d.ts.map +1 -0
  14. package/dist/connect/login.d.ts.map +1 -0
  15. package/dist/connect/parse-callback-params.d.ts.map +1 -0
  16. package/dist/connect/parse-callback-params.js +63 -0
  17. package/dist/connect/parse-callback-params.js.map +1 -0
  18. package/dist/connect/prove-ownership.d.ts.map +1 -0
  19. package/dist/connect/types.d.ts +57 -0
  20. package/dist/connect/types.d.ts.map +1 -0
  21. package/dist/connect/types.js +24 -0
  22. package/dist/connect/types.js.map +1 -0
  23. package/dist/entity/create.d.ts.map +1 -0
  24. package/dist/entity/decodedEntitiesCache.d.ts.map +1 -0
  25. package/dist/entity/delete.d.ts.map +1 -0
  26. package/dist/entity/entity.d.ts.map +1 -0
  27. package/dist/entity/entityRelationParentsMap.d.ts.map +1 -0
  28. package/dist/entity/findMany.d.ts.map +1 -0
  29. package/dist/entity/findMany.js +436 -0
  30. package/dist/entity/findMany.js.map +1 -0
  31. package/dist/entity/findOne.d.ts.map +1 -0
  32. package/dist/entity/getEntityRelations.d.ts.map +1 -0
  33. package/dist/entity/index.d.ts.map +1 -0
  34. package/dist/entity/relationParentsMap.d.ts.map +1 -0
  35. package/dist/entity/removeRelation.d.ts.map +1 -0
  36. package/dist/entity/types.d.ts +79 -0
  37. package/dist/entity/types.d.ts.map +1 -0
  38. package/dist/entity/types.js +2 -0
  39. package/dist/entity/types.js.map +1 -0
  40. package/dist/entity/update.d.ts.map +1 -0
  41. package/dist/identity/auth-storage.d.ts.map +1 -0
  42. package/dist/identity/get-verified-identity.d.ts.map +1 -0
  43. package/dist/identity/identity-encryption.d.ts.map +1 -0
  44. package/dist/identity/index.d.ts.map +1 -0
  45. package/dist/identity/logout.d.ts.map +1 -0
  46. package/dist/identity/prove-ownership.d.ts.map +1 -0
  47. package/dist/inboxes/create-inbox.d.ts.map +1 -0
  48. package/dist/inboxes/get-list-inboxes.d.ts.map +1 -0
  49. package/dist/inboxes/index.d.ts.map +1 -0
  50. package/dist/inboxes/merge-messages.d.ts.map +1 -0
  51. package/dist/inboxes/message-encryption.d.ts.map +1 -0
  52. package/dist/inboxes/message-validation.d.ts.map +1 -0
  53. package/dist/inboxes/prepare-message.d.ts +31 -0
  54. package/dist/inboxes/prepare-message.d.ts.map +1 -0
  55. package/dist/inboxes/recover-inbox-creator.d.ts.map +1 -0
  56. package/dist/inboxes/recover-inbox-message-signer.d.ts.map +1 -0
  57. package/dist/inboxes/send-message.d.ts.map +1 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/messages/index.d.ts.map +1 -0
  60. package/dist/messages/signed-update-message.d.ts.map +1 -0
  61. package/dist/messages/types.d.ts.map +1 -0
  62. package/dist/space-events/accept-invitation.d.ts.map +1 -0
  63. package/dist/space-events/apply-event.d.ts.map +1 -0
  64. package/dist/space-events/create-inbox.d.ts.map +1 -0
  65. package/dist/space-events/create-invitation.d.ts.map +1 -0
  66. package/dist/space-events/create-space.d.ts.map +1 -0
  67. package/dist/space-events/delete-space.d.ts.map +1 -0
  68. package/dist/space-events/hash-event.d.ts.map +1 -0
  69. package/dist/space-events/index.d.ts.map +1 -0
  70. package/dist/space-info/decrypt-space-info.d.ts.map +1 -0
  71. package/dist/space-info/encrypt-and-sign-space-info.d.ts.map +1 -0
  72. package/dist/space-info/index.d.ts.map +1 -0
  73. package/dist/store-connect.d.ts.map +1 -0
  74. package/dist/store.d.ts.map +1 -0
  75. package/dist/type/type.d.ts.map +1 -0
  76. package/dist/utils/automergeId.d.ts +9 -0
  77. package/dist/utils/automergeId.d.ts.map +1 -0
  78. package/dist/utils/automergeId.js +17 -0
  79. package/dist/utils/automergeId.js.map +1 -0
  80. package/dist/utils/generateId.d.ts +15 -0
  81. package/dist/utils/generateId.d.ts.map +1 -0
  82. package/dist/utils/generateId.js +18 -0
  83. package/dist/utils/generateId.js.map +1 -0
  84. package/dist/utils/index.d.ts.map +1 -0
  85. package/package.json +35 -0
  86. package/src/connect/auth-storage.ts +67 -0
  87. package/src/connect/create-app-identity.ts +16 -0
  88. package/src/connect/create-auth-url.ts +42 -0
  89. package/src/connect/create-callback-params.ts +30 -0
  90. package/src/connect/create-identity-keys.ts +20 -0
  91. package/src/connect/identity-encryption.ts +232 -0
  92. package/src/connect/index.ts +10 -0
  93. package/src/connect/login.ts +114 -0
  94. package/src/connect/parse-auth-params.ts +37 -0
  95. package/src/connect/parse-callback-params.ts +67 -0
  96. package/src/connect/prove-ownership.ts +58 -0
  97. package/src/connect/types.ts +67 -0
  98. package/src/entity/create.ts +58 -0
  99. package/src/entity/decodedEntitiesCache.ts +38 -0
  100. package/src/entity/delete.ts +52 -0
  101. package/src/entity/entity.ts +26 -0
  102. package/src/entity/entityRelationParentsMap.ts +6 -0
  103. package/src/entity/findMany.ts +506 -0
  104. package/src/entity/findOne.ts +34 -0
  105. package/src/entity/getEntityRelations.ts +45 -0
  106. package/src/entity/hasValidTypesProperty.ts +8 -0
  107. package/src/entity/index.ts +8 -0
  108. package/src/entity/relationParentsMap.ts +6 -0
  109. package/src/entity/removeRelation.ts +21 -0
  110. package/src/entity/test.ts +0 -0
  111. package/src/entity/types.ts +100 -0
  112. package/src/entity/update.ts +58 -0
  113. package/src/entity/variant-schema.ts +677 -0
  114. package/src/identity/auth-storage.ts +57 -0
  115. package/src/identity/get-verified-identity.ts +53 -0
  116. package/src/identity/identity-encryption.ts +140 -0
  117. package/src/identity/index.ts +6 -0
  118. package/src/identity/logout.ts +8 -0
  119. package/src/identity/prove-ownership.ts +58 -0
  120. package/src/identity/types.ts +44 -0
  121. package/src/inboxes/create-inbox.ts +102 -0
  122. package/src/inboxes/get-list-inboxes.ts +52 -0
  123. package/src/inboxes/index.ts +10 -0
  124. package/src/inboxes/merge-messages.ts +28 -0
  125. package/src/inboxes/message-encryption.ts +35 -0
  126. package/src/inboxes/message-validation.ts +66 -0
  127. package/src/inboxes/prepare-message.ts +85 -0
  128. package/src/inboxes/recover-inbox-creator.ts +29 -0
  129. package/src/inboxes/recover-inbox-message-signer.ts +42 -0
  130. package/src/inboxes/send-message.ts +75 -0
  131. package/src/inboxes/types.ts +9 -0
  132. package/src/index.ts +13 -0
  133. package/src/key/create-key.ts +27 -0
  134. package/src/key/decrypt-key.ts +19 -0
  135. package/src/key/encrypt-key.ts +27 -0
  136. package/src/key/index.ts +4 -0
  137. package/src/key/key-box.ts +31 -0
  138. package/src/messages/decrypt-message.ts +13 -0
  139. package/src/messages/encrypt-message.ts +14 -0
  140. package/src/messages/index.ts +5 -0
  141. package/src/messages/serialize.ts +24 -0
  142. package/src/messages/signed-update-message.ts +84 -0
  143. package/src/messages/types.ts +506 -0
  144. package/src/space-events/accept-invitation.ts +36 -0
  145. package/src/space-events/apply-event.ts +150 -0
  146. package/src/space-events/create-inbox.ts +56 -0
  147. package/src/space-events/create-invitation.ts +41 -0
  148. package/src/space-events/create-space.ts +35 -0
  149. package/src/space-events/delete-space.ts +36 -0
  150. package/src/space-events/hash-event.ts +10 -0
  151. package/src/space-events/index.ts +8 -0
  152. package/src/space-events/types.ts +137 -0
  153. package/src/space-info/decrypt-space-info.ts +22 -0
  154. package/src/space-info/encrypt-and-sign-space-info.ts +50 -0
  155. package/src/space-info/index.ts +3 -0
  156. package/src/space-info/types.ts +7 -0
  157. package/src/store-connect.ts +504 -0
  158. package/src/store.ts +493 -0
  159. package/src/type/type.ts +25 -0
  160. package/src/types.ts +47 -0
  161. package/src/utils/assertExhaustive.ts +3 -0
  162. package/src/utils/automergeId.ts +18 -0
  163. package/src/utils/base58.ts +74 -0
  164. package/src/utils/generateId.ts +18 -0
  165. package/src/utils/hexBytesAddressUtils.ts +25 -0
  166. package/src/utils/index.ts +8 -0
  167. package/src/utils/internal/base58Utils.ts +47 -0
  168. package/src/utils/internal/deep-merge.ts +38 -0
  169. package/src/utils/isRelationField.ts +9 -0
  170. package/src/utils/jsc.ts +94 -0
  171. package/src/utils/stringToUint8Array.ts +9 -0
package/src/store.ts ADDED
@@ -0,0 +1,493 @@
1
+ import type { AnyDocumentId, DocHandle, Repo } from '@automerge/automerge-repo';
2
+ import { type Store, createStore } from '@xstate/store';
3
+ import type { PrivateAppIdentity } from './connect/types.js';
4
+ import { mergeMessages } from './inboxes/merge-messages.js';
5
+ import type { InboxSenderAuthPolicy } from './inboxes/types.js';
6
+ import type { Invitation, Updates } from './messages/index.js';
7
+ import type { SpaceEvent, SpaceState } from './space-events/index.js';
8
+ import { idToAutomergeId } from './utils/automergeId.js';
9
+
10
+ export type InboxMessageStorageEntry = {
11
+ id: string;
12
+ plaintext: string;
13
+ ciphertext: string;
14
+ signature: {
15
+ hex: string;
16
+ recovery: number;
17
+ } | null;
18
+ createdAt: string;
19
+ authorAccountAddress: string | null;
20
+ };
21
+
22
+ export type SpaceInboxStorageEntry = {
23
+ inboxId: string;
24
+ isPublic: boolean;
25
+ authPolicy: InboxSenderAuthPolicy;
26
+ encryptionPublicKey: string;
27
+ secretKey: string;
28
+ lastMessageClock: string;
29
+ messages: InboxMessageStorageEntry[]; // Kept sorted by UUIDv7
30
+ seenMessageIds: Set<string>; // For deduplication
31
+ };
32
+
33
+ export type AccountInboxStorageEntry = {
34
+ inboxId: string;
35
+ isPublic: boolean;
36
+ authPolicy: InboxSenderAuthPolicy;
37
+ encryptionPublicKey: string;
38
+ lastMessageClock: string;
39
+ messages: InboxMessageStorageEntry[]; // Kept sorted by UUIDv7
40
+ seenMessageIds: Set<string>; // For deduplication
41
+ };
42
+
43
+ export type SpaceStorageEntry = {
44
+ id: string;
45
+ events: SpaceEvent[];
46
+ state: SpaceState | undefined;
47
+ keys: { id: string; key: string }[];
48
+ automergeDocHandle: DocHandle<unknown> | undefined;
49
+ inboxes: SpaceInboxStorageEntry[];
50
+ };
51
+
52
+ interface StoreContext {
53
+ spaces: SpaceStorageEntry[];
54
+ updatesInFlight: string[];
55
+ invitations: Invitation[];
56
+ repo: Repo | null;
57
+ identities: {
58
+ [accountAddress: string]: {
59
+ encryptionPublicKey: string;
60
+ signaturePublicKey: string;
61
+ accountProof: string;
62
+ keyProof: string;
63
+ };
64
+ };
65
+ authenticated: boolean;
66
+ identity: PrivateAppIdentity | null;
67
+ lastUpdateClock: { [spaceId: string]: number };
68
+ accountInboxes: AccountInboxStorageEntry[];
69
+ }
70
+
71
+ const initialStoreContext: StoreContext = {
72
+ spaces: [],
73
+ updatesInFlight: [],
74
+ invitations: [],
75
+ repo: null,
76
+ identities: {},
77
+ authenticated: false,
78
+ identity: null,
79
+ lastUpdateClock: {},
80
+ accountInboxes: [],
81
+ };
82
+
83
+ type StoreEvent =
84
+ | { type: 'setInvitations'; invitations: Invitation[] }
85
+ | { type: 'reset' }
86
+ | { type: 'addUpdateInFlight'; updateId: string }
87
+ | { type: 'removeUpdateInFlight'; updateId: string }
88
+ | { type: 'setSpaceFromList'; spaceId: string }
89
+ | { type: 'applyEvent'; spaceId: string; event: SpaceEvent; state: SpaceState }
90
+ | { type: 'updateConfirmed'; spaceId: string; clock: number }
91
+ | { type: 'applyUpdate'; spaceId: string; firstUpdateClock: number; lastUpdateClock: number }
92
+ | {
93
+ type: 'addVerifiedIdentity';
94
+ accountAddress: string;
95
+ encryptionPublicKey: string;
96
+ signaturePublicKey: string;
97
+ accountProof: string;
98
+ keyProof: string;
99
+ }
100
+ | {
101
+ type: 'setSpaceInbox';
102
+ spaceId: string;
103
+ inbox: SpaceInboxStorageEntry;
104
+ }
105
+ | {
106
+ type: 'setSpaceInboxMessages';
107
+ spaceId: string;
108
+ inboxId: string;
109
+ messages: InboxMessageStorageEntry[];
110
+ lastMessageClock: string;
111
+ }
112
+ | {
113
+ type: 'setAccountInbox';
114
+ inbox: AccountInboxStorageEntry;
115
+ }
116
+ | {
117
+ type: 'setAccountInboxMessages';
118
+ inboxId: string;
119
+ messages: InboxMessageStorageEntry[];
120
+ lastMessageClock: string;
121
+ }
122
+ | {
123
+ type: 'setSpace';
124
+ spaceId: string;
125
+ updates?: Updates;
126
+ events: SpaceEvent[];
127
+ inboxes?: SpaceInboxStorageEntry[];
128
+ spaceState: SpaceState;
129
+ keys: {
130
+ id: string;
131
+ key: string;
132
+ }[];
133
+ }
134
+ | {
135
+ type: 'setAuth';
136
+ identity: PrivateAppIdentity;
137
+ }
138
+ | {
139
+ type: 'resetAuth';
140
+ }
141
+ | {
142
+ type: 'setRepo';
143
+ repo: Repo;
144
+ };
145
+
146
+ type GenericEventObject = { type: string };
147
+
148
+ export const store: Store<StoreContext, StoreEvent, GenericEventObject> = createStore({
149
+ context: initialStoreContext,
150
+ on: {
151
+ setInvitations: (context, event: { invitations: Invitation[] }) => {
152
+ return {
153
+ ...context,
154
+ invitations: event.invitations,
155
+ };
156
+ },
157
+ reset: (context) => {
158
+ // once the repo is initialized, there is no need to reset it
159
+ return { ...initialStoreContext, repo: context.repo };
160
+ },
161
+ addUpdateInFlight: (context, event: { updateId: string }) => {
162
+ return {
163
+ ...context,
164
+ updatesInFlight: [...context.updatesInFlight, event.updateId],
165
+ };
166
+ },
167
+ removeUpdateInFlight: (context, event: { updateId: string }) => {
168
+ return {
169
+ ...context,
170
+ updatesInFlight: context.updatesInFlight.filter((id) => id !== event.updateId),
171
+ };
172
+ },
173
+ setSpaceFromList: (context, event: { spaceId: string }) => {
174
+ if (!context.repo) {
175
+ return context;
176
+ }
177
+ const existingSpace = context.spaces.find((s) => s.id === event.spaceId);
178
+ const lastUpdateClock = context.lastUpdateClock[event.spaceId] ?? -1;
179
+ const result = context.repo.findWithProgress(idToAutomergeId(event.spaceId) as AnyDocumentId);
180
+
181
+ // set it to ready to interact with the document
182
+ result.handle.doneLoading();
183
+
184
+ if (existingSpace) {
185
+ return {
186
+ ...context,
187
+ spaces: context.spaces.map((existingSpace) => {
188
+ if (existingSpace.id === event.spaceId) {
189
+ const newSpace: SpaceStorageEntry = {
190
+ id: existingSpace.id,
191
+ events: existingSpace.events ?? [],
192
+ state: existingSpace.state,
193
+ keys: existingSpace.keys ?? [],
194
+ automergeDocHandle: result.handle,
195
+ inboxes: existingSpace.inboxes ?? [],
196
+ };
197
+ return newSpace;
198
+ }
199
+ return existingSpace;
200
+ }),
201
+ lastUpdateClock: {
202
+ ...context.lastUpdateClock,
203
+ [event.spaceId]: lastUpdateClock,
204
+ },
205
+ };
206
+ }
207
+ return {
208
+ ...context,
209
+ spaces: [
210
+ ...context.spaces,
211
+ {
212
+ id: event.spaceId,
213
+ events: [],
214
+ state: undefined,
215
+ keys: [],
216
+ inboxes: [],
217
+ updates: [],
218
+ lastUpdateClock: -1,
219
+ automergeDocHandle: result.handle,
220
+ },
221
+ ],
222
+ };
223
+ },
224
+ applyEvent: (context, event: { spaceId: string; event: SpaceEvent; state: SpaceState }) => {
225
+ return {
226
+ ...context,
227
+ spaces: context.spaces.map((space) => {
228
+ if (space.id === event.spaceId) {
229
+ return { ...space, events: [...space.events, event.event], state: event.state };
230
+ }
231
+ return space;
232
+ }),
233
+ };
234
+ },
235
+ updateConfirmed: (context, event: { spaceId: string; clock: number }) => {
236
+ return {
237
+ ...context,
238
+ lastUpdateClock: {
239
+ ...context.lastUpdateClock,
240
+ [event.spaceId]: event.clock,
241
+ },
242
+ };
243
+ },
244
+ applyUpdate: (context, event: { spaceId: string; firstUpdateClock: number; lastUpdateClock: number }) => {
245
+ const lastUpdateClock = context.lastUpdateClock[event.spaceId] ?? -1;
246
+ if (event.firstUpdateClock === lastUpdateClock + 1) {
247
+ return {
248
+ ...context,
249
+ lastUpdateClock: {
250
+ ...context.lastUpdateClock,
251
+ [event.spaceId]: event.lastUpdateClock,
252
+ },
253
+ };
254
+ }
255
+
256
+ // TODO else case: request missing updates from server
257
+ return context;
258
+ },
259
+ addVerifiedIdentity: (
260
+ context,
261
+ event: {
262
+ accountAddress: string;
263
+ encryptionPublicKey: string;
264
+ signaturePublicKey: string;
265
+ accountProof: string;
266
+ keyProof: string;
267
+ },
268
+ ) => {
269
+ return {
270
+ ...context,
271
+ identities: {
272
+ ...context.identities,
273
+ [event.accountAddress]: {
274
+ encryptionPublicKey: event.encryptionPublicKey,
275
+ signaturePublicKey: event.signaturePublicKey,
276
+ accountProof: event.accountProof,
277
+ keyProof: event.keyProof,
278
+ },
279
+ },
280
+ };
281
+ },
282
+ setSpaceInbox: (context, event: { spaceId: string; inbox: SpaceInboxStorageEntry }) => {
283
+ return {
284
+ ...context,
285
+ spaces: context.spaces.map((space) => {
286
+ if (space.id === event.spaceId) {
287
+ const existingInbox = space.inboxes.find((inbox) => inbox.inboxId === event.inbox.inboxId);
288
+ if (existingInbox) {
289
+ return {
290
+ ...space,
291
+ inboxes: space.inboxes.map((inbox) => {
292
+ if (inbox.inboxId === event.inbox.inboxId) {
293
+ const { messages, seenMessageIds } = mergeMessages(
294
+ existingInbox.messages,
295
+ existingInbox.seenMessageIds,
296
+ event.inbox.messages,
297
+ );
298
+ return {
299
+ ...event.inbox,
300
+ messages,
301
+ seenMessageIds,
302
+ };
303
+ }
304
+ return inbox;
305
+ }),
306
+ };
307
+ }
308
+ return { ...space, inboxes: [...space.inboxes, event.inbox] };
309
+ }
310
+ return space;
311
+ }),
312
+ };
313
+ },
314
+ setSpaceInboxMessages: (
315
+ context,
316
+ event: { spaceId: string; inboxId: string; messages: InboxMessageStorageEntry[]; lastMessageClock: string },
317
+ ) => {
318
+ return {
319
+ ...context,
320
+ spaces: context.spaces.map((space) => {
321
+ if (space.id === event.spaceId) {
322
+ return {
323
+ ...space,
324
+ inboxes: space.inboxes.map((inbox) => {
325
+ if (inbox.inboxId === event.inboxId) {
326
+ const { messages, seenMessageIds } = mergeMessages(
327
+ inbox.messages,
328
+ inbox.seenMessageIds,
329
+ event.messages,
330
+ );
331
+ return {
332
+ ...inbox,
333
+ messages,
334
+ seenMessageIds,
335
+ lastMessageClock: new Date(
336
+ Math.max(new Date(inbox.lastMessageClock).getTime(), new Date(event.lastMessageClock).getTime()),
337
+ ).toISOString(),
338
+ };
339
+ }
340
+ return inbox;
341
+ }),
342
+ };
343
+ }
344
+ return space;
345
+ }),
346
+ };
347
+ },
348
+ setAccountInbox: (context, event: { inbox: AccountInboxStorageEntry }) => {
349
+ const existingInbox = context.accountInboxes.find((inbox) => inbox.inboxId === event.inbox.inboxId);
350
+ if (existingInbox) {
351
+ return {
352
+ ...context,
353
+ accountInboxes: context.accountInboxes.map((inbox) => {
354
+ if (inbox.inboxId === event.inbox.inboxId) {
355
+ const { messages, seenMessageIds } = mergeMessages(
356
+ existingInbox.messages,
357
+ existingInbox.seenMessageIds,
358
+ event.inbox.messages,
359
+ );
360
+ return {
361
+ ...event.inbox,
362
+ messages,
363
+ seenMessageIds,
364
+ };
365
+ }
366
+ return inbox;
367
+ }),
368
+ };
369
+ }
370
+ return {
371
+ ...context,
372
+ accountInboxes: [...context.accountInboxes, event.inbox],
373
+ };
374
+ },
375
+ setAccountInboxMessages: (
376
+ context,
377
+ event: { inboxId: string; messages: InboxMessageStorageEntry[]; lastMessageClock: string },
378
+ ) => {
379
+ return {
380
+ ...context,
381
+ accountInboxes: context.accountInboxes.map((inbox) => {
382
+ if (inbox.inboxId === event.inboxId) {
383
+ const { messages, seenMessageIds } = mergeMessages(inbox.messages, inbox.seenMessageIds, event.messages);
384
+ return {
385
+ ...inbox,
386
+ messages,
387
+ seenMessageIds,
388
+ lastMessageClock: new Date(
389
+ Math.max(new Date(inbox.lastMessageClock).getTime(), new Date(event.lastMessageClock).getTime()),
390
+ ).toISOString(),
391
+ };
392
+ }
393
+ return inbox;
394
+ }),
395
+ };
396
+ },
397
+ setSpace: (
398
+ context,
399
+ event: {
400
+ spaceId: string;
401
+ updates?: Updates;
402
+ inboxes?: SpaceInboxStorageEntry[];
403
+ events: SpaceEvent[];
404
+ spaceState: SpaceState;
405
+ keys: {
406
+ id: string;
407
+ key: string;
408
+ }[];
409
+ },
410
+ ) => {
411
+ const existingSpace = context.spaces.find((s) => s.id === event.spaceId);
412
+ if (!existingSpace && context.repo) {
413
+ const result = context.repo.findWithProgress(idToAutomergeId(event.spaceId) as AnyDocumentId);
414
+ // set it to ready to interact with the document
415
+ result.handle.doneLoading();
416
+
417
+ const newSpace: SpaceStorageEntry = {
418
+ id: event.spaceId,
419
+ events: event.events,
420
+ state: event.spaceState,
421
+ keys: event.keys,
422
+ automergeDocHandle: result.handle,
423
+ inboxes: event.inboxes ?? [],
424
+ };
425
+ return {
426
+ ...context,
427
+ spaces: [...context.spaces, newSpace],
428
+ lastUpdateClock: {
429
+ ...context.lastUpdateClock,
430
+ [event.spaceId]: -1,
431
+ },
432
+ };
433
+ }
434
+
435
+ let lastUpdateClock = context.lastUpdateClock[event.spaceId] ?? -1;
436
+ if (event.updates?.firstUpdateClock === lastUpdateClock + 1) {
437
+ lastUpdateClock = event.updates.lastUpdateClock;
438
+ } else {
439
+ // TODO request missing updates from server
440
+ }
441
+
442
+ return {
443
+ ...context,
444
+ spaces: context.spaces.map((space) => {
445
+ if (space.id === event.spaceId) {
446
+ // Merge inboxes: keep existing ones and add new ones
447
+ const mergedInboxes = [...space.inboxes];
448
+ for (const newInbox of event.inboxes ?? []) {
449
+ const existingInboxIndex = mergedInboxes.findIndex((inbox) => inbox.inboxId === newInbox.inboxId);
450
+ if (existingInboxIndex === -1) {
451
+ // Only add if it's a new inbox
452
+ mergedInboxes.push(newInbox);
453
+ }
454
+ }
455
+
456
+ return {
457
+ ...space,
458
+ events: event.events,
459
+ state: event.spaceState,
460
+ keys: event.keys,
461
+ inboxes: mergedInboxes,
462
+ };
463
+ }
464
+ return space;
465
+ }),
466
+ lastUpdateClock: {
467
+ ...context.lastUpdateClock,
468
+ [event.spaceId]: lastUpdateClock,
469
+ },
470
+ };
471
+ },
472
+ setAuth: (context, event: { identity: PrivateAppIdentity }) => {
473
+ return {
474
+ ...context,
475
+ authenticated: true,
476
+ identity: event.identity,
477
+ };
478
+ },
479
+ resetAuth: (context) => {
480
+ return {
481
+ ...context,
482
+ identity: null,
483
+ authenticated: false,
484
+ };
485
+ },
486
+ setRepo: (context, event: { repo: Repo }) => {
487
+ return {
488
+ ...context,
489
+ repo: event.repo,
490
+ };
491
+ },
492
+ },
493
+ });
@@ -0,0 +1,25 @@
1
+ import * as Schema from 'effect/Schema';
2
+ import { Field } from '../entity/entity.js';
3
+ import type { AnyNoContext, EntityWithRelation } from '../entity/types.js';
4
+
5
+ export const Text = Schema.String;
6
+ // biome-ignore lint/suspicious/noShadowRestrictedNames: is part of a namespaces module and therefor ok
7
+ export const Number = Schema.Number;
8
+ export const Checkbox = Schema.Boolean;
9
+ // biome-ignore lint/suspicious/noShadowRestrictedNames: is part of a namespaces module and therefor ok
10
+ export const Date = Schema.Date;
11
+ export const Url = Schema.URL;
12
+ export const Point = Schema.transform(Schema.String, Schema.Array(Number), {
13
+ strict: true,
14
+ decode: (str: string) => {
15
+ return str.split(',').map((n: string) => globalThis.Number(n));
16
+ },
17
+ encode: (points: readonly number[]) => points.join(','),
18
+ });
19
+
20
+ export const Relation = <S extends AnyNoContext>(schema: S) =>
21
+ Field({
22
+ select: Schema.Array(schema) as unknown as Schema.Schema<ReadonlyArray<EntityWithRelation<S>>>,
23
+ insert: Schema.optional(Schema.Array(Schema.String)),
24
+ update: Schema.Undefined,
25
+ });
package/src/types.ts ADDED
@@ -0,0 +1,47 @@
1
+ import * as Data from 'effect/Data';
2
+ import * as Schema from 'effect/Schema';
3
+
4
+ export const SignatureWithRecovery = Schema.Struct({
5
+ hex: Schema.String,
6
+ recovery: Schema.Number,
7
+ });
8
+
9
+ export type SignatureWithRecovery = Schema.Schema.Type<typeof SignatureWithRecovery>;
10
+
11
+ export const ConnectAuthPayload = Schema.Struct({
12
+ expiry: Schema.Number,
13
+ encryptionPublicKey: Schema.String,
14
+ appId: Schema.String,
15
+ });
16
+
17
+ export type ConnectAuthPayload = Schema.Schema.Type<typeof ConnectAuthPayload>;
18
+
19
+ export const ConnectCallbackResult = Schema.Struct({
20
+ appIdentityAddress: Schema.String,
21
+ appIdentityAddressPrivateKey: Schema.String,
22
+ signaturePublicKey: Schema.String,
23
+ signaturePrivateKey: Schema.String,
24
+ encryptionPublicKey: Schema.String,
25
+ encryptionPrivateKey: Schema.String,
26
+ sessionToken: Schema.String,
27
+ sessionTokenExpires: Schema.Date,
28
+ spaces: Schema.Array(Schema.Struct({ id: Schema.String })),
29
+ });
30
+
31
+ export type ConnectCallbackResult = Schema.Schema.Type<typeof ConnectCallbackResult>;
32
+
33
+ export const ConnectCallbackDecryptedData = Schema.Struct({
34
+ ...ConnectCallbackResult.fields,
35
+ sessionTokenExpires: Schema.Number,
36
+ expiry: Schema.Number,
37
+ });
38
+
39
+ export type ConnectCallbackDecryptedData = Schema.Schema.Type<typeof ConnectCallbackDecryptedData>;
40
+
41
+ export class FailedToParseConnectAuthUrl extends Data.TaggedError('FailedToParseConnectAuthUrl')<{
42
+ message: string;
43
+ }> {}
44
+
45
+ export class FailedToParseAuthCallbackUrl extends Data.TaggedError('FailedToParseAuthCallbackUrl')<{
46
+ message: string;
47
+ }> {}
@@ -0,0 +1,3 @@
1
+ export const assertExhaustive = (_value: never, message = 'Reached unexpected case in exhaustive switch'): never => {
2
+ throw new Error(message);
3
+ };
@@ -0,0 +1,18 @@
1
+ import bs58check from 'bs58check';
2
+ import { parse as parseUuid, stringify as stringifyUuid } from 'uuid';
3
+
4
+ /**
5
+ * Converts a UUID into Base58Check
6
+ */
7
+ export function idToAutomergeId(uuid: string, _versionByte = 0x00) {
8
+ const payload = parseUuid(uuid);
9
+ return bs58check.encode(payload);
10
+ }
11
+
12
+ /**
13
+ * Converts a Base58Check-encoded UUID back to UUID
14
+ */
15
+ export function automergeIdToId(base58CheckUuid: string) {
16
+ const versionedPayload = bs58check.decode(base58CheckUuid);
17
+ return stringifyUuid(versionedPayload);
18
+ }
@@ -0,0 +1,74 @@
1
+ import { BASE58_ALLOWED_CHARS } from './internal/base58Utils.js';
2
+
3
+ export type Base58 = string;
4
+
5
+ /**
6
+ * Base58 encodes the given string value.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { v4 as uuidv4 } from "uuid";
11
+ *
12
+ * const uuid = uuidv4(); // 92539817-7989-4083-ab80-e9c2b2b66669
13
+ * const dashesRemoved = uuid.replaceAll(/-/g, ""); // 9253981779894083ab80e9c2b2b66669
14
+ * const encoded = encodeBase58(dashesRemoved)
15
+ * console.log(encoded) // K51CbDqxW35osbjPo5ZF77
16
+ * ```
17
+ *
18
+ * @param val string to encode as base58
19
+ * @returns the base58 encoded string
20
+ */
21
+ export function encodeBase58(val: string): Base58 {
22
+ const hex = BigInt(`0x${val}`);
23
+ let remainder = hex;
24
+ const result: string[] = []; // Use an array to store encoded characters
25
+
26
+ while (remainder > 0n) {
27
+ const mod = remainder % 58n;
28
+ const base58CharAtMod = BASE58_ALLOWED_CHARS[Number(mod)];
29
+ if (base58CharAtMod) {
30
+ result.push(base58CharAtMod);
31
+ }
32
+ remainder = remainder / 58n;
33
+ }
34
+
35
+ // Reverse and join the array to get the final Base58 encoded string
36
+ return result.reverse().join('');
37
+ }
38
+
39
+ export type UUID = string;
40
+
41
+ /**
42
+ * Expand the base58 encoded UUID back to its original UUID format
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * const uuid = 92539817-7989-4083-ab80-e9c2b2b66669;
47
+ * const encoded = encodeBase58(dashesRemoved); // K51CbDqxW35osbjPo5ZF77
48
+ * const decoded = decodeBase58ToUUID(encoded); // 92539817-7989-4083-ab80-e9c2b2b66669
49
+ *
50
+ * expect(decoded).toEqual(uuid);
51
+ * ```
52
+ *
53
+ * @param encoded base58 encoded UUID
54
+ * @returns the expanded UUID from the base58 encoded value
55
+ */
56
+ export function decodeBase58ToUUID(encoded: string): UUID {
57
+ let decoded = 0n;
58
+
59
+ for (const char of encoded) {
60
+ const index = BASE58_ALLOWED_CHARS.indexOf(char);
61
+ if (index === -1) {
62
+ throw new Error('Invalid Base58 character');
63
+ }
64
+ decoded = decoded * 58n + BigInt(index);
65
+ }
66
+
67
+ // Convert the bigint to a hex string, padded to 32 characters
68
+ let hexStr = decoded.toString(16);
69
+ hexStr = hexStr.padStart(32, '0'); // Ensure it is 32 characters
70
+
71
+ return [hexStr.slice(0, 8), hexStr.slice(8, 12), hexStr.slice(12, 16), hexStr.slice(16, 20), hexStr.slice(20)].join(
72
+ '-',
73
+ );
74
+ }
@@ -0,0 +1,18 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+
3
+ /**
4
+ * Generate a v4 UUID.
5
+ *
6
+ * @example
7
+ * ```
8
+ * import { generateId } from '@graph-framework/utils'
9
+ *
10
+ * const id = generateId()
11
+ * console.log(id)
12
+ * ```
13
+ *
14
+ * @returns v4 UUID
15
+ */
16
+ export function generateId() {
17
+ return uuidv4();
18
+ }
@@ -0,0 +1,25 @@
1
+ import { bytesToHex as nobleBytesToHex, hexToBytes as nobleHexToBytes } from '@noble/ciphers/utils';
2
+ import { ProjectivePoint } from '@noble/secp256k1';
3
+ import type { Hex } from 'viem';
4
+ import { publicKeyToAddress as viemPublicKeyToAddress } from 'viem/accounts';
5
+
6
+ export const bytesToHex = (bytes: Uint8Array): string => {
7
+ return `0x${nobleBytesToHex(bytes)}`;
8
+ };
9
+
10
+ export const hexToBytes = (hex: string): Uint8Array => {
11
+ return nobleHexToBytes(hex.slice(2));
12
+ };
13
+
14
+ function decompressPublicKey(compressedKey: string): string {
15
+ // Decompress the public key
16
+ const point = ProjectivePoint.fromHex(compressedKey.slice(2));
17
+
18
+ // Get the uncompressed public key
19
+ const uncompressedKey = point.toRawBytes(false); // `false` = uncompressed format
20
+ return bytesToHex(uncompressedKey);
21
+ }
22
+ export const publicKeyToAddress = (publicKey: string): string => {
23
+ const uncompressedKey = decompressPublicKey(publicKey);
24
+ return viemPublicKeyToAddress(uncompressedKey as Hex);
25
+ };