jazz-tools 0.8.44 → 0.8.45

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. package/.turbo/turbo-build.log +16 -10
  2. package/CHANGELOG.md +12 -0
  3. package/dist/chunk-5WLKIDKU.js +2844 -0
  4. package/dist/chunk-5WLKIDKU.js.map +1 -0
  5. package/dist/index.native.js +63 -0
  6. package/dist/index.native.js.map +1 -0
  7. package/dist/index.web.js +61 -0
  8. package/dist/index.web.js.map +1 -0
  9. package/package.json +9 -12
  10. package/src/coValues/account.ts +21 -9
  11. package/src/coValues/coFeed.ts +4 -3
  12. package/src/coValues/coList.ts +8 -5
  13. package/src/coValues/coMap.ts +6 -3
  14. package/src/coValues/deepLoading.ts +6 -12
  15. package/src/coValues/extensions/imageDef.ts +3 -1
  16. package/src/coValues/group.ts +8 -11
  17. package/src/coValues/inbox.ts +377 -0
  18. package/src/coValues/interfaces.ts +9 -6
  19. package/src/coValues/profile.ts +11 -0
  20. package/src/coValues/registeredSchemas.ts +12 -0
  21. package/src/exports.ts +21 -15
  22. package/src/implementation/anonymousJazzAgent.ts +6 -0
  23. package/src/implementation/createContext.ts +6 -7
  24. package/src/implementation/refs.ts +1 -1
  25. package/src/implementation/subscriptionScope.ts +1 -1
  26. package/src/index.native.ts +0 -1
  27. package/src/internal.ts +1 -9
  28. package/src/tests/account.test.ts +2 -2
  29. package/src/tests/coFeed.test.ts +0 -9
  30. package/src/tests/coMap.test.ts +2 -2
  31. package/src/tests/groupsAndAccounts.test.ts +1 -1
  32. package/src/tests/inbox.test.ts +256 -0
  33. package/src/tests/schemaUnion.test.ts +1 -1
  34. package/src/tests/utils.ts +14 -0
  35. package/tsup.config.ts +13 -0
  36. package/dist/native/coValues/account.js +0 -221
  37. package/dist/native/coValues/account.js.map +0 -1
  38. package/dist/native/coValues/coFeed.js +0 -571
  39. package/dist/native/coValues/coFeed.js.map +0 -1
  40. package/dist/native/coValues/coList.js +0 -404
  41. package/dist/native/coValues/coList.js.map +0 -1
  42. package/dist/native/coValues/coMap.js +0 -537
  43. package/dist/native/coValues/coMap.js.map +0 -1
  44. package/dist/native/coValues/deepLoading.js +0 -63
  45. package/dist/native/coValues/deepLoading.js.map +0 -1
  46. package/dist/native/coValues/extensions/imageDef.js +0 -42
  47. package/dist/native/coValues/extensions/imageDef.js.map +0 -1
  48. package/dist/native/coValues/group.js +0 -136
  49. package/dist/native/coValues/group.js.map +0 -1
  50. package/dist/native/coValues/interfaces.js +0 -135
  51. package/dist/native/coValues/interfaces.js.map +0 -1
  52. package/dist/native/coValues/schemaUnion.js +0 -89
  53. package/dist/native/coValues/schemaUnion.js.map +0 -1
  54. package/dist/native/exports.js +0 -5
  55. package/dist/native/exports.js.map +0 -1
  56. package/dist/native/implementation/createContext.js +0 -157
  57. package/dist/native/implementation/createContext.js.map +0 -1
  58. package/dist/native/implementation/devtoolsFormatters.js +0 -95
  59. package/dist/native/implementation/devtoolsFormatters.js.map +0 -1
  60. package/dist/native/implementation/errors.js +0 -2
  61. package/dist/native/implementation/errors.js.map +0 -1
  62. package/dist/native/implementation/inspect.js +0 -2
  63. package/dist/native/implementation/inspect.js.map +0 -1
  64. package/dist/native/implementation/refs.js +0 -115
  65. package/dist/native/implementation/refs.js.map +0 -1
  66. package/dist/native/implementation/schema.js +0 -96
  67. package/dist/native/implementation/schema.js.map +0 -1
  68. package/dist/native/implementation/subscriptionScope.js +0 -91
  69. package/dist/native/implementation/subscriptionScope.js.map +0 -1
  70. package/dist/native/implementation/symbols.js +0 -4
  71. package/dist/native/implementation/symbols.js.map +0 -1
  72. package/dist/native/index.native.js +0 -3
  73. package/dist/native/index.native.js.map +0 -1
  74. package/dist/native/internal.js +0 -18
  75. package/dist/native/internal.js.map +0 -1
  76. package/dist/native/lib/cache.js +0 -13
  77. package/dist/native/lib/cache.js.map +0 -1
  78. package/dist/native/lib/cache.test.js +0 -52
  79. package/dist/native/lib/cache.test.js.map +0 -1
  80. package/dist/web/coValues/account.js +0 -221
  81. package/dist/web/coValues/account.js.map +0 -1
  82. package/dist/web/coValues/coFeed.js +0 -571
  83. package/dist/web/coValues/coFeed.js.map +0 -1
  84. package/dist/web/coValues/coList.js +0 -404
  85. package/dist/web/coValues/coList.js.map +0 -1
  86. package/dist/web/coValues/coMap.js +0 -537
  87. package/dist/web/coValues/coMap.js.map +0 -1
  88. package/dist/web/coValues/deepLoading.js +0 -63
  89. package/dist/web/coValues/deepLoading.js.map +0 -1
  90. package/dist/web/coValues/extensions/imageDef.js +0 -42
  91. package/dist/web/coValues/extensions/imageDef.js.map +0 -1
  92. package/dist/web/coValues/group.js +0 -136
  93. package/dist/web/coValues/group.js.map +0 -1
  94. package/dist/web/coValues/interfaces.js +0 -135
  95. package/dist/web/coValues/interfaces.js.map +0 -1
  96. package/dist/web/coValues/schemaUnion.js +0 -89
  97. package/dist/web/coValues/schemaUnion.js.map +0 -1
  98. package/dist/web/exports.js +0 -5
  99. package/dist/web/exports.js.map +0 -1
  100. package/dist/web/implementation/createContext.js +0 -157
  101. package/dist/web/implementation/createContext.js.map +0 -1
  102. package/dist/web/implementation/devtoolsFormatters.js +0 -95
  103. package/dist/web/implementation/devtoolsFormatters.js.map +0 -1
  104. package/dist/web/implementation/errors.js +0 -2
  105. package/dist/web/implementation/errors.js.map +0 -1
  106. package/dist/web/implementation/inspect.js +0 -2
  107. package/dist/web/implementation/inspect.js.map +0 -1
  108. package/dist/web/implementation/refs.js +0 -115
  109. package/dist/web/implementation/refs.js.map +0 -1
  110. package/dist/web/implementation/schema.js +0 -96
  111. package/dist/web/implementation/schema.js.map +0 -1
  112. package/dist/web/implementation/subscriptionScope.js +0 -91
  113. package/dist/web/implementation/subscriptionScope.js.map +0 -1
  114. package/dist/web/implementation/symbols.js +0 -4
  115. package/dist/web/implementation/symbols.js.map +0 -1
  116. package/dist/web/index.web.js +0 -3
  117. package/dist/web/index.web.js.map +0 -1
  118. package/dist/web/internal.js +0 -18
  119. package/dist/web/internal.js.map +0 -1
  120. package/dist/web/lib/cache.js +0 -13
  121. package/dist/web/lib/cache.js.map +0 -1
  122. package/dist/web/lib/cache.test.js +0 -52
  123. package/dist/web/lib/cache.test.js.map +0 -1
package/package.json CHANGED
@@ -6,25 +6,25 @@
6
6
  "react-native": "dist/native/index.native.js",
7
7
  "exports": {
8
8
  ".": {
9
- "react-native": "./dist/native/index.native.js",
9
+ "react-native": "./dist/index.native.js",
10
10
  "types": "./src/index.web.ts",
11
- "default": "./dist/web/index.web.js"
11
+ "default": "./dist/index.web.js"
12
12
  },
13
13
  "./native": {
14
- "react-native": "./dist/native/index.native.js",
14
+ "react-native": "./dist/index.native.js",
15
15
  "types": "./src/index.native.ts",
16
- "default": "./dist/native/index.native.js"
16
+ "default": "./dist/index.native.js"
17
17
  },
18
18
  "./src/*": "./src/*"
19
19
  },
20
20
  "type": "module",
21
21
  "license": "MIT",
22
- "version": "0.8.44",
22
+ "version": "0.8.45",
23
23
  "dependencies": {
24
- "fast-check": "^3.17.2",
25
- "cojson": "0.8.44"
24
+ "cojson": "0.8.45"
26
25
  },
27
26
  "devDependencies": {
27
+ "tsup": "8.3.5",
28
28
  "typescript": "~5.6.2",
29
29
  "vitest": "1.5.3"
30
30
  },
@@ -38,12 +38,9 @@
38
38
  "scripts": {
39
39
  "format-and-lint": "biome check .",
40
40
  "format-and-lint:fix": "biome check . --write",
41
- "dev:web": "tsc --sourceMap --watch --outDir dist/web -p tsconfig.web.json",
42
- "dev:native": "tsc --sourceMap --watch --outDir dist/native -p tsconfig.native.json",
41
+ "dev": "tsup --watch",
43
42
  "test": "vitest --run --root ../../ --project jazz-tools",
44
43
  "test:watch": "vitest --watch --root ../../ --project jazz-tools",
45
- "build:web": "tsc --sourceMap --outDir dist/web -p tsconfig.web.json",
46
- "build:native": "tsc --sourceMap --outDir dist/native -p tsconfig.native.json",
47
- "build": "rm -rf ./dist && pnpm run build:web && pnpm run build:native"
44
+ "build": "tsup"
48
45
  }
49
46
  }
@@ -14,16 +14,13 @@ import {
14
14
  } from "cojson";
15
15
  import {
16
16
  AnonymousJazzAgent,
17
- CoMap,
18
17
  type CoValue,
19
18
  CoValueBase,
20
19
  CoValueClass,
21
20
  DeeplyLoaded,
22
21
  DepthsIn,
23
- Group,
24
22
  ID,
25
23
  MembersSym,
26
- Profile,
27
24
  Ref,
28
25
  type RefEncoded,
29
26
  RefIfCoValue,
@@ -37,6 +34,11 @@ import {
37
34
  subscriptionsScopes,
38
35
  } from "../internal.js";
39
36
  import { coValuesCache } from "../lib/cache.js";
37
+ import { type CoMap } from "./coMap.js";
38
+ import { type Group } from "./group.js";
39
+ import { createInboxRoot } from "./inbox.js";
40
+ import { Profile } from "./profile.js";
41
+ import { RegisteredSchemas } from "./registeredSchemas.js";
40
42
 
41
43
  /** @category Identity & Permissions */
42
44
  export class Account extends CoValueBase implements CoValue {
@@ -59,7 +61,7 @@ export class Account extends CoValueBase implements CoValue {
59
61
  optional: false,
60
62
  } satisfies RefEncoded<Profile>,
61
63
  root: {
62
- ref: () => CoMap,
64
+ ref: () => RegisteredSchemas["CoMap"],
63
65
  optional: true,
64
66
  } satisfies RefEncoded<CoMap>,
65
67
  };
@@ -234,18 +236,26 @@ export class Account extends CoValueBase implements CoValue {
234
236
  return this.toJSON();
235
237
  }
236
238
 
237
- migrate(
238
- this: Account,
239
- creationProps?: { name: string },
240
- ): void | Promise<void> {
239
+ migrate(this: Account, creationProps?: { name: string }) {
241
240
  if (creationProps) {
242
- const profileGroup = Group.create({ owner: this });
241
+ const profileGroup = RegisteredSchemas["Group"].create({ owner: this });
243
242
  profileGroup.addMember("everyone", "reader");
244
243
  this.profile = Profile.create(
245
244
  { name: creationProps.name },
246
245
  { owner: profileGroup },
247
246
  );
248
247
  }
248
+
249
+ const node = this._raw.core.node;
250
+ const profile = node
251
+ .expectCoValueLoaded(this._raw.get("profile")!)
252
+ .getCurrentContent() as RawCoMap;
253
+
254
+ if (!profile.get("inbox")) {
255
+ const inboxRoot = createInboxRoot(this);
256
+ profile.set("inbox", inboxRoot.id);
257
+ profile.set("inboxInvite", inboxRoot.inviteLink);
258
+ }
249
259
  }
250
260
 
251
261
  /** @category Subscription & Loading */
@@ -388,3 +398,5 @@ export function isControlledAccount(account: Account): account is Account & {
388
398
  export type AccountClass<Acc extends Account> = CoValueClass<Acc> & {
389
399
  fromNode: (typeof Account)["fromNode"];
390
400
  };
401
+
402
+ RegisteredSchemas["Account"] = Account;
@@ -16,7 +16,6 @@ import type {
16
16
  CoValueClass,
17
17
  DeeplyLoaded,
18
18
  DepthsIn,
19
- Group,
20
19
  ID,
21
20
  IfCo,
22
21
  Schema,
@@ -24,7 +23,6 @@ import type {
24
23
  UnCo,
25
24
  } from "../internal.js";
26
25
  import {
27
- Account,
28
26
  CoValueBase,
29
27
  ItemsSym,
30
28
  Ref,
@@ -37,6 +35,9 @@ import {
37
35
  subscribeToCoValue,
38
36
  subscribeToExistingCoValue,
39
37
  } from "../internal.js";
38
+ import { type Account } from "./account.js";
39
+ import { type Group } from "./group.js";
40
+ import { RegisteredSchemas } from "./registeredSchemas.js";
40
41
 
41
42
  /** @deprecated Use CoFeedEntry instead */
42
43
  export type CoStreamEntry<Item> = CoFeedEntry<Item>;
@@ -436,7 +437,7 @@ function entryFromRawEntry<Item>(
436
437
  return (
437
438
  accountID &&
438
439
  new Ref<Account>(accountID as unknown as ID<Account>, loadedAs, {
439
- ref: Account,
440
+ ref: RegisteredSchemas["Account"],
440
441
  optional: false,
441
442
  })?.accessFrom(
442
443
  accessFrom,
@@ -13,9 +13,7 @@ import type {
13
13
  UnCo,
14
14
  } from "../internal.js";
15
15
  import {
16
- Account,
17
16
  AnonymousJazzAgent,
18
- Group,
19
17
  ItemsSym,
20
18
  Ref,
21
19
  SchemaInit,
@@ -30,6 +28,9 @@ import {
30
28
  subscriptionsScopes,
31
29
  } from "../internal.js";
32
30
  import { coValuesCache } from "../lib/cache.js";
31
+ import { type Account } from "./account.js";
32
+ import { type Group } from "./group.js";
33
+ import { RegisteredSchemas } from "./registeredSchemas.js";
33
34
 
34
35
  /**
35
36
  * CoLists are collaborative versions of plain arrays.
@@ -108,8 +109,8 @@ export class CoList<Item = any> extends Array<Item> implements CoValue {
108
109
  /** @category Collaboration */
109
110
  get _owner(): Account | Group {
110
111
  return this._raw.group instanceof RawAccount
111
- ? Account.fromRaw(this._raw.group)
112
- : Group.fromRaw(this._raw.group);
112
+ ? RegisteredSchemas["Account"].fromRaw(this._raw.group)
113
+ : RegisteredSchemas["Group"].fromRaw(this._raw.group);
113
114
  }
114
115
 
115
116
  /**
@@ -162,7 +163,9 @@ export class CoList<Item = any> extends Array<Item> implements CoValue {
162
163
  const rawAccount = this._raw.core.node.account;
163
164
 
164
165
  if (rawAccount instanceof RawAccount) {
165
- return coValuesCache.get(rawAccount, () => Account.fromRaw(rawAccount));
166
+ return coValuesCache.get(rawAccount, () =>
167
+ RegisteredSchemas["Account"].fromRaw(rawAccount),
168
+ );
166
169
  }
167
170
 
168
171
  return new AnonymousJazzAgent(this._raw.core.node);
@@ -13,7 +13,6 @@ import type {
13
13
  CoValueClass,
14
14
  DeeplyLoaded,
15
15
  DepthsIn,
16
- Group,
17
16
  ID,
18
17
  IfCo,
19
18
  RefEncoded,
@@ -22,7 +21,6 @@ import type {
22
21
  co,
23
22
  } from "../internal.js";
24
23
  import {
25
- Account,
26
24
  CoValueBase,
27
25
  ItemsSym,
28
26
  Ref,
@@ -36,6 +34,9 @@ import {
36
34
  subscribeToExistingCoValue,
37
35
  subscriptionsScopes,
38
36
  } from "../internal.js";
37
+ import { type Account } from "./account.js";
38
+ import { type Group } from "./group.js";
39
+ import { RegisteredSchemas } from "./registeredSchemas.js";
39
40
 
40
41
  type CoMapEdit<V> = {
41
42
  value?: V;
@@ -178,7 +179,7 @@ export class CoMap extends CoValueBase implements CoValue {
178
179
  by:
179
180
  rawEdit.by &&
180
181
  new Ref<Account>(rawEdit.by as ID<Account>, target._loadedAs, {
181
- ref: Account,
182
+ ref: RegisteredSchemas["Account"],
182
183
  optional: false,
183
184
  }).accessFrom(target, "_edits." + key + ".by"),
184
185
  madeAt: rawEdit.at,
@@ -742,3 +743,5 @@ const CoMapProxyHandler: ProxyHandler<CoMap> = {
742
743
  }
743
744
  },
744
745
  };
746
+
747
+ RegisteredSchemas["CoMap"] = CoMap;
@@ -1,16 +1,10 @@
1
1
  import { SessionID } from "cojson";
2
- import {
3
- Account,
4
- CoFeed,
5
- CoFeedEntry,
6
- CoList,
7
- ItemsSym,
8
- Ref,
9
- RefEncoded,
10
- UnCo,
11
- } from "../internal.js";
12
- import { CoKeys, CoMap } from "./coMap.js";
13
- import { CoValue, ID } from "./interfaces.js";
2
+ import { ItemsSym, type Ref, RefEncoded, UnCo } from "../internal.js";
3
+ import { type Account } from "./account.js";
4
+ import { type CoFeed, CoFeedEntry } from "./coFeed.js";
5
+ import { type CoList } from "./coList.js";
6
+ import { type CoKeys, type CoMap } from "./coMap.js";
7
+ import { type CoValue, type ID } from "./interfaces.js";
14
8
 
15
9
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
10
  export function fulfillsDepth(depth: any, value: CoValue): boolean {
@@ -1,4 +1,6 @@
1
- import { CoMap, FileStream, co, subscriptionsScopes } from "../../internal.js";
1
+ import { co, subscriptionsScopes } from "../../internal.js";
2
+ import { FileStream } from "../coFeed.js";
3
+ import { CoMap } from "../coMap.js";
2
4
 
3
5
  /** @category Media */
4
6
  export class ImageDefinition extends CoMap {
@@ -9,24 +9,19 @@ import type {
9
9
  Schema,
10
10
  } from "../internal.js";
11
11
  import {
12
- Account,
13
- AccountAndGroupProxyHandler,
14
- CoMap,
15
12
  CoValueBase,
16
13
  MembersSym,
17
14
  Ref,
18
- co,
19
15
  ensureCoValueLoaded,
20
- isControlledAccount,
21
16
  loadCoValue,
22
17
  subscribeToCoValue,
23
18
  subscribeToExistingCoValue,
24
19
  } from "../internal.js";
25
-
26
- /** @category Identity & Permissions */
27
- export class Profile extends CoMap {
28
- name = co.string;
29
- }
20
+ import { AccountAndGroupProxyHandler, isControlledAccount } from "./account.js";
21
+ import { type Account } from "./account.js";
22
+ import { type CoMap } from "./coMap.js";
23
+ import { type Profile } from "./profile.js";
24
+ import { RegisteredSchemas } from "./registeredSchemas.js";
30
25
 
31
26
  /** @category Identity & Permissions */
32
27
  export class Group extends CoValueBase implements CoValue {
@@ -51,7 +46,7 @@ export class Group extends CoValueBase implements CoValue {
51
46
  profile: "json" satisfies Schema,
52
47
  root: "json" satisfies Schema,
53
48
  [MembersSym]: {
54
- ref: () => Account,
49
+ ref: () => RegisteredSchemas["Account"],
55
50
  optional: false,
56
51
  } satisfies RefEncoded<Account>,
57
52
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -229,3 +224,5 @@ export class Group extends CoValueBase implements CoValue {
229
224
  return this._raw.core.waitForSync(options);
230
225
  }
231
226
  }
227
+
228
+ RegisteredSchemas["Group"] = Group;
@@ -0,0 +1,377 @@
1
+ import {
2
+ CoID,
3
+ InviteSecret,
4
+ RawAccount,
5
+ RawCoMap,
6
+ RawControlledAccount,
7
+ SessionID,
8
+ } from "cojson";
9
+ import { CoStreamItem, RawCoStream } from "cojson";
10
+ import { type Account } from "./account.js";
11
+ import { CoValue, CoValueClass, ID, loadCoValue } from "./interfaces.js";
12
+
13
+ export type InboxInvite = `${CoID<MessagesStream>}/${InviteSecret}`;
14
+ type TxKey = `${SessionID}/${number}`;
15
+
16
+ type MessagesStream = RawCoStream<CoID<InboxMessage<CoValue, any>>>;
17
+ type FailedMessagesStream = RawCoStream<{
18
+ errors: string[];
19
+ value: CoID<InboxMessage<CoValue, any>>;
20
+ }>;
21
+ type TxKeyStream = RawCoStream<TxKey>;
22
+ export type InboxRoot = RawCoMap<{
23
+ messages: CoID<MessagesStream>;
24
+ processed: CoID<TxKeyStream>;
25
+ failed: CoID<FailedMessagesStream>;
26
+ inviteLink: InboxInvite;
27
+ }>;
28
+
29
+ export function createInboxRoot(account: Account) {
30
+ if (!account.isMe) {
31
+ throw new Error("Account is not controlled");
32
+ }
33
+
34
+ const rawAccount = account._raw as RawControlledAccount;
35
+
36
+ const group = rawAccount.createGroup();
37
+ const messagesFeed = group.createStream<MessagesStream>();
38
+
39
+ const inboxRoot = rawAccount.createMap<InboxRoot>();
40
+ const processedFeed = rawAccount.createStream<TxKeyStream>();
41
+ const failedFeed = rawAccount.createStream<FailedMessagesStream>();
42
+
43
+ const inviteLink =
44
+ `${messagesFeed.id}/${group.createInvite("writeOnly")}` as const;
45
+
46
+ inboxRoot.set("messages", messagesFeed.id);
47
+ inboxRoot.set("processed", processedFeed.id);
48
+ inboxRoot.set("failed", failedFeed.id);
49
+
50
+ return {
51
+ id: inboxRoot.id,
52
+ inviteLink,
53
+ };
54
+ }
55
+
56
+ type InboxMessage<I extends CoValue, O extends CoValue | undefined> = RawCoMap<{
57
+ payload: ID<I>;
58
+ result: ID<O> | undefined;
59
+ processed: boolean;
60
+ error: string | undefined;
61
+ }>;
62
+
63
+ function createInboxMessage<I extends CoValue, O extends CoValue | undefined>(
64
+ payload: I,
65
+ inboxOwner: RawAccount,
66
+ ) {
67
+ const group = payload._raw.group;
68
+
69
+ if (group instanceof RawAccount) {
70
+ throw new Error("Inbox messages should be owned by a group");
71
+ }
72
+
73
+ group.addMember(inboxOwner, "writer");
74
+
75
+ const message = group.createMap<InboxMessage<I, O>>({
76
+ payload: payload.id,
77
+ result: undefined,
78
+ processed: false,
79
+ error: undefined,
80
+ });
81
+
82
+ return message;
83
+ }
84
+
85
+ export class Inbox {
86
+ account: Account;
87
+ messages: MessagesStream;
88
+ processed: TxKeyStream;
89
+ failed: FailedMessagesStream;
90
+ root: InboxRoot;
91
+ processing = new Set<`${SessionID}/${number}`>();
92
+
93
+ private constructor(
94
+ account: Account,
95
+ root: InboxRoot,
96
+ messages: MessagesStream,
97
+ processed: TxKeyStream,
98
+ failed: FailedMessagesStream,
99
+ ) {
100
+ this.account = account;
101
+ this.root = root;
102
+ this.messages = messages;
103
+ this.processed = processed;
104
+ this.failed = failed;
105
+ }
106
+
107
+ subscribe<I extends CoValue, O extends CoValue | undefined>(
108
+ Schema: CoValueClass<I>,
109
+ callback: (
110
+ message: I,
111
+ senderAccountID: ID<Account>,
112
+ ) => Promise<O | undefined | void>,
113
+ options: { retries?: number } = {},
114
+ ) {
115
+ const processed = new Set<`${SessionID}/${number}`>();
116
+ const failed = new Map<`${SessionID}/${number}`, string[]>();
117
+ const node = this.account._raw.core.node;
118
+
119
+ this.processed.subscribe((stream) => {
120
+ for (const items of Object.values(stream.items)) {
121
+ for (const item of items) {
122
+ processed.add(item.value as TxKey);
123
+ }
124
+ }
125
+ });
126
+
127
+ const { account } = this;
128
+ const { retries = 3 } = options;
129
+
130
+ let failTimer: ReturnType<typeof setTimeout> | number | undefined =
131
+ undefined;
132
+
133
+ const clearFailTimer = () => {
134
+ clearTimeout(failTimer);
135
+ failTimer = undefined;
136
+ };
137
+
138
+ const handleNewMessages = (stream: MessagesStream) => {
139
+ clearFailTimer(); // Stop the failure timers, we're going to process the failed entries anyway
140
+
141
+ for (const [sessionID, items] of Object.entries(stream.items) as [
142
+ SessionID,
143
+ CoStreamItem<CoID<InboxMessage<I, O>>>[],
144
+ ][]) {
145
+ const accountID = getAccountIDfromSessionID(sessionID);
146
+
147
+ if (!accountID) {
148
+ console.warn("Received message from unknown account", sessionID);
149
+ continue;
150
+ }
151
+
152
+ for (const item of items) {
153
+ const txKey = `${sessionID}/${item.tx.txIndex}` as const;
154
+
155
+ if (!processed.has(txKey) && !this.processing.has(txKey)) {
156
+ this.processing.add(txKey);
157
+
158
+ const id = item.value;
159
+
160
+ node
161
+ .load(id)
162
+ .then((message) => {
163
+ if (message === "unavailable") {
164
+ return Promise.reject(
165
+ new Error("Unable to load inbox message " + id),
166
+ );
167
+ }
168
+
169
+ return loadCoValue(
170
+ Schema,
171
+ message.get("payload") as ID<I>,
172
+ account,
173
+ [],
174
+ );
175
+ })
176
+ .then((value) => {
177
+ if (!value) {
178
+ return Promise.reject(
179
+ new Error("Unable to load inbox message " + id),
180
+ );
181
+ }
182
+
183
+ return callback(value, accountID);
184
+ })
185
+ .then((result) => {
186
+ const inboxMessage = node
187
+ .expectCoValueLoaded(item.value)
188
+ .getCurrentContent() as RawCoMap;
189
+
190
+ if (result) {
191
+ inboxMessage.set("result", result.id);
192
+ }
193
+
194
+ inboxMessage.set("processed", true);
195
+
196
+ this.processed.push(txKey);
197
+ this.processing.delete(txKey);
198
+ })
199
+ .catch((error) => {
200
+ console.error("Error processing inbox message", error);
201
+ this.processing.delete(txKey);
202
+ const errors = failed.get(txKey) ?? [];
203
+
204
+ const stringifiedError = String(error);
205
+ errors.push(stringifiedError);
206
+
207
+ const inboxMessage = node
208
+ .expectCoValueLoaded(item.value)
209
+ .getCurrentContent() as RawCoMap;
210
+
211
+ inboxMessage.set("error", stringifiedError);
212
+
213
+ if (errors.length > retries) {
214
+ inboxMessage.set("processed", true);
215
+ this.processed.push(txKey);
216
+ this.failed.push({ errors, value: item.value });
217
+ } else {
218
+ failed.set(txKey, errors);
219
+ if (!failTimer) {
220
+ failTimer = setTimeout(
221
+ () => handleNewMessages(stream),
222
+ 100,
223
+ );
224
+ }
225
+ }
226
+ });
227
+ }
228
+ }
229
+ }
230
+ };
231
+
232
+ return this.messages.subscribe(handleNewMessages);
233
+ }
234
+
235
+ static async load(account: Account) {
236
+ const profile = account.profile;
237
+
238
+ if (!profile) {
239
+ throw new Error("Account profile should already be loaded");
240
+ }
241
+
242
+ if (!profile.inbox) {
243
+ throw new Error("The account has not set up their inbox");
244
+ }
245
+
246
+ const node = account._raw.core.node;
247
+
248
+ const root = await node.load(profile.inbox as CoID<InboxRoot>);
249
+
250
+ if (root === "unavailable") {
251
+ throw new Error("Inbox not found");
252
+ }
253
+
254
+ const [messages, processed, failed] = await Promise.all([
255
+ node.load(root.get("messages")!),
256
+ node.load(root.get("processed")!),
257
+ node.load(root.get("failed")!),
258
+ ]);
259
+
260
+ if (
261
+ messages === "unavailable" ||
262
+ processed === "unavailable" ||
263
+ failed === "unavailable"
264
+ ) {
265
+ throw new Error("Inbox not found");
266
+ }
267
+
268
+ return new Inbox(account, root, messages, processed, failed);
269
+ }
270
+ }
271
+
272
+ export class InboxSender<I extends CoValue, O extends CoValue | undefined> {
273
+ currentAccount: Account;
274
+ owner: RawAccount;
275
+ messages: MessagesStream;
276
+
277
+ private constructor(
278
+ currentAccount: Account,
279
+ owner: RawAccount,
280
+ messages: MessagesStream,
281
+ ) {
282
+ this.currentAccount = currentAccount;
283
+ this.owner = owner;
284
+ this.messages = messages;
285
+ }
286
+
287
+ getOwnerAccount() {
288
+ return this.owner;
289
+ }
290
+
291
+ sendMessage(message: I): Promise<O extends CoValue ? ID<O> : undefined> {
292
+ const inboxMessage = createInboxMessage<I, O>(message, this.owner);
293
+
294
+ this.messages.push(inboxMessage.id);
295
+
296
+ return new Promise((resolve, reject) => {
297
+ inboxMessage.subscribe((message) => {
298
+ if (message.get("processed")) {
299
+ const error = message.get("error");
300
+ if (error) {
301
+ reject(new Error(error));
302
+ } else {
303
+ resolve(
304
+ message.get("result") as O extends CoValue ? ID<O> : undefined,
305
+ );
306
+ }
307
+ }
308
+ });
309
+ });
310
+ }
311
+
312
+ static async load<
313
+ I extends CoValue,
314
+ O extends CoValue | undefined = undefined,
315
+ >(inboxOwnerID: ID<Account>, currentAccount: Account) {
316
+ const node = currentAccount._raw.core.node;
317
+
318
+ const inboxOwnerRaw = await node.load(
319
+ inboxOwnerID as unknown as CoID<RawAccount>,
320
+ );
321
+
322
+ if (inboxOwnerRaw === "unavailable") {
323
+ throw new Error("Failed to load the inbox owner");
324
+ }
325
+
326
+ const inboxOwnerProfileRaw = await node.load(inboxOwnerRaw.get("profile")!);
327
+
328
+ if (inboxOwnerProfileRaw === "unavailable") {
329
+ throw new Error("Failed to load the inbox owner profile");
330
+ }
331
+
332
+ const inboxInvite = inboxOwnerProfileRaw.get("inboxInvite");
333
+
334
+ if (!inboxInvite) {
335
+ throw new Error("The user has not set up their inbox");
336
+ }
337
+
338
+ const id = await acceptInvite(inboxInvite as InboxInvite, currentAccount);
339
+
340
+ const messages = await node.load(id);
341
+
342
+ if (messages === "unavailable") {
343
+ throw new Error("Inbox not found");
344
+ }
345
+
346
+ return new InboxSender<I, O>(currentAccount, inboxOwnerRaw, messages);
347
+ }
348
+ }
349
+
350
+ async function acceptInvite(invite: string, account: Account) {
351
+ const id = invite.slice(0, invite.indexOf("/")) as CoID<MessagesStream>;
352
+
353
+ const inviteSecret = invite.slice(invite.indexOf("/") + 1) as InviteSecret;
354
+
355
+ if (!id?.startsWith("co_z") || !inviteSecret.startsWith("inviteSecret_")) {
356
+ throw new Error("Invalid inbox ticket");
357
+ }
358
+
359
+ if (!account.isMe) {
360
+ throw new Error("Account is not controlled");
361
+ }
362
+
363
+ await (account._raw as RawControlledAccount).acceptInvite(id, inviteSecret);
364
+
365
+ return id;
366
+ }
367
+
368
+ function getAccountIDfromSessionID(sessionID: SessionID) {
369
+ const until = sessionID.indexOf("_session");
370
+ const accountID = sessionID.slice(0, until);
371
+
372
+ if (accountID.startsWith("co_z")) {
373
+ return accountID as ID<Account>;
374
+ }
375
+
376
+ return;
377
+ }