@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.
- package/LICENSE +21 -0
- package/README.md +2 -0
- package/dist/connect/auth-storage.d.ts.map +1 -0
- package/dist/connect/create-app-identity.d.ts.map +1 -0
- package/dist/connect/create-auth-url.d.ts.map +1 -0
- package/dist/connect/create-auth-url.js +35 -0
- package/dist/connect/create-auth-url.js.map +1 -0
- package/dist/connect/create-callback-params.d.ts.map +1 -0
- package/dist/connect/create-callback-params.js +17 -0
- package/dist/connect/create-callback-params.js.map +1 -0
- package/dist/connect/create-identity-keys.d.ts.map +1 -0
- package/dist/connect/identity-encryption.d.ts.map +1 -0
- package/dist/connect/index.d.ts.map +1 -0
- package/dist/connect/login.d.ts.map +1 -0
- package/dist/connect/parse-callback-params.d.ts.map +1 -0
- package/dist/connect/parse-callback-params.js +63 -0
- package/dist/connect/parse-callback-params.js.map +1 -0
- package/dist/connect/prove-ownership.d.ts.map +1 -0
- package/dist/connect/types.d.ts +57 -0
- package/dist/connect/types.d.ts.map +1 -0
- package/dist/connect/types.js +24 -0
- package/dist/connect/types.js.map +1 -0
- package/dist/entity/create.d.ts.map +1 -0
- package/dist/entity/decodedEntitiesCache.d.ts.map +1 -0
- package/dist/entity/delete.d.ts.map +1 -0
- package/dist/entity/entity.d.ts.map +1 -0
- package/dist/entity/entityRelationParentsMap.d.ts.map +1 -0
- package/dist/entity/findMany.d.ts.map +1 -0
- package/dist/entity/findMany.js +436 -0
- package/dist/entity/findMany.js.map +1 -0
- package/dist/entity/findOne.d.ts.map +1 -0
- package/dist/entity/getEntityRelations.d.ts.map +1 -0
- package/dist/entity/index.d.ts.map +1 -0
- package/dist/entity/relationParentsMap.d.ts.map +1 -0
- package/dist/entity/removeRelation.d.ts.map +1 -0
- package/dist/entity/types.d.ts +79 -0
- package/dist/entity/types.d.ts.map +1 -0
- package/dist/entity/types.js +2 -0
- package/dist/entity/types.js.map +1 -0
- package/dist/entity/update.d.ts.map +1 -0
- package/dist/identity/auth-storage.d.ts.map +1 -0
- package/dist/identity/get-verified-identity.d.ts.map +1 -0
- package/dist/identity/identity-encryption.d.ts.map +1 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/logout.d.ts.map +1 -0
- package/dist/identity/prove-ownership.d.ts.map +1 -0
- package/dist/inboxes/create-inbox.d.ts.map +1 -0
- package/dist/inboxes/get-list-inboxes.d.ts.map +1 -0
- package/dist/inboxes/index.d.ts.map +1 -0
- package/dist/inboxes/merge-messages.d.ts.map +1 -0
- package/dist/inboxes/message-encryption.d.ts.map +1 -0
- package/dist/inboxes/message-validation.d.ts.map +1 -0
- package/dist/inboxes/prepare-message.d.ts +31 -0
- package/dist/inboxes/prepare-message.d.ts.map +1 -0
- package/dist/inboxes/recover-inbox-creator.d.ts.map +1 -0
- package/dist/inboxes/recover-inbox-message-signer.d.ts.map +1 -0
- package/dist/inboxes/send-message.d.ts.map +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/messages/index.d.ts.map +1 -0
- package/dist/messages/signed-update-message.d.ts.map +1 -0
- package/dist/messages/types.d.ts.map +1 -0
- package/dist/space-events/accept-invitation.d.ts.map +1 -0
- package/dist/space-events/apply-event.d.ts.map +1 -0
- package/dist/space-events/create-inbox.d.ts.map +1 -0
- package/dist/space-events/create-invitation.d.ts.map +1 -0
- package/dist/space-events/create-space.d.ts.map +1 -0
- package/dist/space-events/delete-space.d.ts.map +1 -0
- package/dist/space-events/hash-event.d.ts.map +1 -0
- package/dist/space-events/index.d.ts.map +1 -0
- package/dist/space-info/decrypt-space-info.d.ts.map +1 -0
- package/dist/space-info/encrypt-and-sign-space-info.d.ts.map +1 -0
- package/dist/space-info/index.d.ts.map +1 -0
- package/dist/store-connect.d.ts.map +1 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/type/type.d.ts.map +1 -0
- package/dist/utils/automergeId.d.ts +9 -0
- package/dist/utils/automergeId.d.ts.map +1 -0
- package/dist/utils/automergeId.js +17 -0
- package/dist/utils/automergeId.js.map +1 -0
- package/dist/utils/generateId.d.ts +15 -0
- package/dist/utils/generateId.d.ts.map +1 -0
- package/dist/utils/generateId.js +18 -0
- package/dist/utils/generateId.js.map +1 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/package.json +35 -0
- package/src/connect/auth-storage.ts +67 -0
- package/src/connect/create-app-identity.ts +16 -0
- package/src/connect/create-auth-url.ts +42 -0
- package/src/connect/create-callback-params.ts +30 -0
- package/src/connect/create-identity-keys.ts +20 -0
- package/src/connect/identity-encryption.ts +232 -0
- package/src/connect/index.ts +10 -0
- package/src/connect/login.ts +114 -0
- package/src/connect/parse-auth-params.ts +37 -0
- package/src/connect/parse-callback-params.ts +67 -0
- package/src/connect/prove-ownership.ts +58 -0
- package/src/connect/types.ts +67 -0
- package/src/entity/create.ts +58 -0
- package/src/entity/decodedEntitiesCache.ts +38 -0
- package/src/entity/delete.ts +52 -0
- package/src/entity/entity.ts +26 -0
- package/src/entity/entityRelationParentsMap.ts +6 -0
- package/src/entity/findMany.ts +506 -0
- package/src/entity/findOne.ts +34 -0
- package/src/entity/getEntityRelations.ts +45 -0
- package/src/entity/hasValidTypesProperty.ts +8 -0
- package/src/entity/index.ts +8 -0
- package/src/entity/relationParentsMap.ts +6 -0
- package/src/entity/removeRelation.ts +21 -0
- package/src/entity/test.ts +0 -0
- package/src/entity/types.ts +100 -0
- package/src/entity/update.ts +58 -0
- package/src/entity/variant-schema.ts +677 -0
- package/src/identity/auth-storage.ts +57 -0
- package/src/identity/get-verified-identity.ts +53 -0
- package/src/identity/identity-encryption.ts +140 -0
- package/src/identity/index.ts +6 -0
- package/src/identity/logout.ts +8 -0
- package/src/identity/prove-ownership.ts +58 -0
- package/src/identity/types.ts +44 -0
- package/src/inboxes/create-inbox.ts +102 -0
- package/src/inboxes/get-list-inboxes.ts +52 -0
- package/src/inboxes/index.ts +10 -0
- package/src/inboxes/merge-messages.ts +28 -0
- package/src/inboxes/message-encryption.ts +35 -0
- package/src/inboxes/message-validation.ts +66 -0
- package/src/inboxes/prepare-message.ts +85 -0
- package/src/inboxes/recover-inbox-creator.ts +29 -0
- package/src/inboxes/recover-inbox-message-signer.ts +42 -0
- package/src/inboxes/send-message.ts +75 -0
- package/src/inboxes/types.ts +9 -0
- package/src/index.ts +13 -0
- package/src/key/create-key.ts +27 -0
- package/src/key/decrypt-key.ts +19 -0
- package/src/key/encrypt-key.ts +27 -0
- package/src/key/index.ts +4 -0
- package/src/key/key-box.ts +31 -0
- package/src/messages/decrypt-message.ts +13 -0
- package/src/messages/encrypt-message.ts +14 -0
- package/src/messages/index.ts +5 -0
- package/src/messages/serialize.ts +24 -0
- package/src/messages/signed-update-message.ts +84 -0
- package/src/messages/types.ts +506 -0
- package/src/space-events/accept-invitation.ts +36 -0
- package/src/space-events/apply-event.ts +150 -0
- package/src/space-events/create-inbox.ts +56 -0
- package/src/space-events/create-invitation.ts +41 -0
- package/src/space-events/create-space.ts +35 -0
- package/src/space-events/delete-space.ts +36 -0
- package/src/space-events/hash-event.ts +10 -0
- package/src/space-events/index.ts +8 -0
- package/src/space-events/types.ts +137 -0
- package/src/space-info/decrypt-space-info.ts +22 -0
- package/src/space-info/encrypt-and-sign-space-info.ts +50 -0
- package/src/space-info/index.ts +3 -0
- package/src/space-info/types.ts +7 -0
- package/src/store-connect.ts +504 -0
- package/src/store.ts +493 -0
- package/src/type/type.ts +25 -0
- package/src/types.ts +47 -0
- package/src/utils/assertExhaustive.ts +3 -0
- package/src/utils/automergeId.ts +18 -0
- package/src/utils/base58.ts +74 -0
- package/src/utils/generateId.ts +18 -0
- package/src/utils/hexBytesAddressUtils.ts +25 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/internal/base58Utils.ts +47 -0
- package/src/utils/internal/deep-merge.ts +38 -0
- package/src/utils/isRelationField.ts +9 -0
- package/src/utils/jsc.ts +94 -0
- 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
|
+
});
|
package/src/type/type.ts
ADDED
|
@@ -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,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
|
+
};
|