jazz-tools 0.10.14 → 0.11.0

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 (150) hide show
  1. package/.turbo/turbo-build.log +11 -7
  2. package/CHANGELOG.md +31 -0
  3. package/dist/auth/AuthSecretStorage.d.ts +25 -0
  4. package/dist/auth/AuthSecretStorage.d.ts.map +1 -0
  5. package/dist/auth/DemoAuth.d.ts +27 -0
  6. package/dist/auth/DemoAuth.d.ts.map +1 -0
  7. package/dist/auth/InMemoryKVStore.d.ts +9 -0
  8. package/dist/auth/InMemoryKVStore.d.ts.map +1 -0
  9. package/dist/auth/KvStoreContext.d.ts +17 -0
  10. package/dist/auth/KvStoreContext.d.ts.map +1 -0
  11. package/dist/auth/PassphraseAuth.d.ts +35 -0
  12. package/dist/auth/PassphraseAuth.d.ts.map +1 -0
  13. package/dist/{chunk-5YDDEUNX.js → chunk-RTRX7HIO.js} +193 -81
  14. package/dist/chunk-RTRX7HIO.js.map +1 -0
  15. package/dist/coValues/account.d.ts +120 -0
  16. package/dist/coValues/account.d.ts.map +1 -0
  17. package/dist/coValues/coFeed.d.ts +361 -0
  18. package/dist/coValues/coFeed.d.ts.map +1 -0
  19. package/dist/coValues/coList.d.ts +221 -0
  20. package/dist/coValues/coList.d.ts.map +1 -0
  21. package/dist/coValues/coMap.d.ts +500 -0
  22. package/dist/coValues/coMap.d.ts.map +1 -0
  23. package/dist/coValues/coPlainText.d.ts +69 -0
  24. package/dist/coValues/coPlainText.d.ts.map +1 -0
  25. package/dist/coValues/coRichText.d.ts +259 -0
  26. package/dist/coValues/coRichText.d.ts.map +1 -0
  27. package/dist/coValues/deepLoading.d.ts +81 -0
  28. package/dist/coValues/deepLoading.d.ts.map +1 -0
  29. package/dist/coValues/extensions/imageDef.d.ts +17 -0
  30. package/dist/coValues/extensions/imageDef.d.ts.map +1 -0
  31. package/dist/coValues/group.d.ts +67 -0
  32. package/dist/coValues/group.d.ts.map +1 -0
  33. package/dist/coValues/inbox.d.ts +52 -0
  34. package/dist/coValues/inbox.d.ts.map +1 -0
  35. package/dist/coValues/interfaces.d.ts +97 -0
  36. package/dist/coValues/interfaces.d.ts.map +1 -0
  37. package/dist/coValues/profile.d.ts +28 -0
  38. package/dist/coValues/profile.d.ts.map +1 -0
  39. package/dist/coValues/registeredSchemas.d.ts +12 -0
  40. package/dist/coValues/registeredSchemas.d.ts.map +1 -0
  41. package/dist/coValues/schemaUnion.d.ts +79 -0
  42. package/dist/coValues/schemaUnion.d.ts.map +1 -0
  43. package/dist/exports.d.ts +27 -0
  44. package/dist/exports.d.ts.map +1 -0
  45. package/dist/implementation/ContextManager.d.ts +65 -0
  46. package/dist/implementation/ContextManager.d.ts.map +1 -0
  47. package/dist/implementation/activeAccountContext.d.ts +12 -0
  48. package/dist/implementation/activeAccountContext.d.ts.map +1 -0
  49. package/dist/implementation/anonymousJazzAgent.d.ts +7 -0
  50. package/dist/implementation/anonymousJazzAgent.d.ts.map +1 -0
  51. package/dist/implementation/createContext.d.ts +91 -0
  52. package/dist/implementation/createContext.d.ts.map +1 -0
  53. package/dist/implementation/devtoolsFormatters.d.ts +2 -0
  54. package/dist/implementation/devtoolsFormatters.d.ts.map +1 -0
  55. package/dist/implementation/errors.d.ts +2 -0
  56. package/dist/implementation/errors.d.ts.map +1 -0
  57. package/dist/implementation/inspect.d.ts +3 -0
  58. package/dist/implementation/inspect.d.ts.map +1 -0
  59. package/dist/implementation/invites.d.ts +23 -0
  60. package/dist/implementation/invites.d.ts.map +1 -0
  61. package/dist/implementation/refs.d.ts +21 -0
  62. package/dist/implementation/refs.d.ts.map +1 -0
  63. package/dist/implementation/schema.d.ts +72 -0
  64. package/dist/implementation/schema.d.ts.map +1 -0
  65. package/dist/implementation/subscriptionScope.d.ts +33 -0
  66. package/dist/implementation/subscriptionScope.d.ts.map +1 -0
  67. package/dist/implementation/symbols.d.ts +8 -0
  68. package/dist/implementation/symbols.d.ts.map +1 -0
  69. package/dist/index.d.ts +3 -0
  70. package/dist/index.d.ts.map +1 -0
  71. package/dist/index.js +1 -1
  72. package/dist/internal.d.ts +12 -0
  73. package/dist/internal.d.ts.map +1 -0
  74. package/dist/lib/cache.d.ts +6 -0
  75. package/dist/lib/cache.d.ts.map +1 -0
  76. package/dist/lib/cache.test.d.ts +2 -0
  77. package/dist/lib/cache.test.d.ts.map +1 -0
  78. package/dist/testing.d.ts +41 -0
  79. package/dist/testing.d.ts.map +1 -0
  80. package/dist/testing.js +11 -16
  81. package/dist/testing.js.map +1 -1
  82. package/dist/tests/AuthSecretStorage.test.d.ts +2 -0
  83. package/dist/tests/AuthSecretStorage.test.d.ts.map +1 -0
  84. package/dist/tests/ContextManager.test.d.ts +2 -0
  85. package/dist/tests/ContextManager.test.d.ts.map +1 -0
  86. package/dist/tests/DemoAuth.test.d.ts +2 -0
  87. package/dist/tests/DemoAuth.test.d.ts.map +1 -0
  88. package/dist/tests/PassphraseAuth.test.d.ts +2 -0
  89. package/dist/tests/PassphraseAuth.test.d.ts.map +1 -0
  90. package/dist/tests/account.test.d.ts +2 -0
  91. package/dist/tests/account.test.d.ts.map +1 -0
  92. package/dist/tests/coFeed.test.d.ts +2 -0
  93. package/dist/tests/coFeed.test.d.ts.map +1 -0
  94. package/dist/tests/coList.test.d.ts +2 -0
  95. package/dist/tests/coList.test.d.ts.map +1 -0
  96. package/dist/tests/coMap.test.d.ts +2 -0
  97. package/dist/tests/coMap.test.d.ts.map +1 -0
  98. package/dist/tests/coPlainText.test.d.ts +2 -0
  99. package/dist/tests/coPlainText.test.d.ts.map +1 -0
  100. package/dist/tests/coRichText.test.d.ts +2 -0
  101. package/dist/tests/coRichText.test.d.ts.map +1 -0
  102. package/dist/tests/createContext.test.d.ts +2 -0
  103. package/dist/tests/createContext.test.d.ts.map +1 -0
  104. package/dist/tests/deepLoading.test.d.ts +2 -0
  105. package/dist/tests/deepLoading.test.d.ts.map +1 -0
  106. package/dist/tests/fixtures.d.ts +2 -0
  107. package/dist/tests/fixtures.d.ts.map +1 -0
  108. package/dist/tests/groupsAndAccounts.test.d.ts +2 -0
  109. package/dist/tests/groupsAndAccounts.test.d.ts.map +1 -0
  110. package/dist/tests/inbox.test.d.ts +2 -0
  111. package/dist/tests/inbox.test.d.ts.map +1 -0
  112. package/dist/tests/interfaces.test.d.ts +2 -0
  113. package/dist/tests/interfaces.test.d.ts.map +1 -0
  114. package/dist/tests/invites.test.d.ts +2 -0
  115. package/dist/tests/invites.test.d.ts.map +1 -0
  116. package/dist/tests/schema.test.d.ts +2 -0
  117. package/dist/tests/schema.test.d.ts.map +1 -0
  118. package/dist/tests/schemaUnion.test.d.ts +2 -0
  119. package/dist/tests/schemaUnion.test.d.ts.map +1 -0
  120. package/dist/tests/subscribe.test.d.ts +2 -0
  121. package/dist/tests/subscribe.test.d.ts.map +1 -0
  122. package/dist/tests/testing.test.d.ts +2 -0
  123. package/dist/tests/testing.test.d.ts.map +1 -0
  124. package/dist/tests/utils.d.ts +21 -0
  125. package/dist/tests/utils.d.ts.map +1 -0
  126. package/dist/types.d.ts +52 -0
  127. package/dist/types.d.ts.map +1 -0
  128. package/package.json +8 -7
  129. package/src/coValues/account.ts +69 -11
  130. package/src/coValues/coMap.ts +2 -2
  131. package/src/coValues/coRichText.ts +42 -17
  132. package/src/coValues/group.ts +76 -31
  133. package/src/coValues/inbox.ts +10 -0
  134. package/src/coValues/interfaces.ts +1 -1
  135. package/src/coValues/profile.ts +35 -2
  136. package/src/implementation/ContextManager.ts +63 -15
  137. package/src/implementation/schema.ts +1 -3
  138. package/src/testing.ts +10 -16
  139. package/src/tests/AuthSecretStorage.test.ts +1 -2
  140. package/src/tests/ContextManager.test.ts +27 -14
  141. package/src/tests/PassphraseAuth.test.ts +7 -3
  142. package/src/tests/coMap.test.ts +20 -21
  143. package/src/tests/deepLoading.test.ts +8 -17
  144. package/src/tests/groupsAndAccounts.test.ts +429 -89
  145. package/src/tests/inbox.test.ts +24 -0
  146. package/src/tests/schema.test.ts +45 -5
  147. package/src/tests/utils.ts +7 -3
  148. package/src/types.ts +6 -0
  149. package/tsconfig.json +4 -1
  150. package/dist/chunk-5YDDEUNX.js.map +0 -1
@@ -611,26 +611,51 @@ export function splitNode(
611
611
  }
612
612
  }
613
613
 
614
+ /**
615
+ * Heading mark for rich text formatting.
616
+ */
617
+ export class Heading extends Mark {
618
+ tag = co.literal("heading");
619
+ level = co.number;
620
+ }
621
+
622
+ /**
623
+ * Paragraph mark for rich text formatting.
624
+ */
625
+ export class Paragraph extends Mark {
626
+ tag = co.literal("paragraph");
627
+ }
628
+
629
+ /**
630
+ * Link mark for rich text formatting.
631
+ */
632
+ export class Link extends Mark {
633
+ tag = co.literal("link");
634
+ url = co.string;
635
+ }
636
+
637
+ /**
638
+ * Strong (bold) mark for rich text formatting.
639
+ */
640
+ export class Strong extends Mark {
641
+ tag = co.literal("strong");
642
+ }
643
+
644
+ /**
645
+ * Emphasis (italic) mark for rich text formatting.
646
+ */
647
+ export class Em extends Mark {
648
+ tag = co.literal("em");
649
+ }
650
+
614
651
  /**
615
652
  * Collection of predefined mark types for common text formatting.
616
653
  * Includes marks for headings, paragraphs, links, and text styling.
617
654
  */
618
655
  export const Marks = {
619
- Heading: class Heading extends Mark {
620
- tag = co.literal("heading");
621
- level = co.number;
622
- },
623
- Paragraph: class Paragraph extends Mark {
624
- tag = co.literal("paragraph");
625
- },
626
- Link: class Link extends Mark {
627
- tag = co.literal("link");
628
- url = co.string;
629
- },
630
- Strong: class Strong extends Mark {
631
- tag = co.literal("strong");
632
- },
633
- Em: class Italic extends Mark {
634
- tag = co.literal("em");
635
- },
656
+ Heading,
657
+ Paragraph,
658
+ Link,
659
+ Strong,
660
+ Em,
636
661
  };
@@ -1,10 +1,12 @@
1
1
  import type {
2
2
  AccountRole,
3
+ AgentID,
3
4
  Everyone,
4
5
  RawAccountID,
5
6
  RawGroup,
6
7
  Role,
7
8
  } from "cojson";
9
+ import { activeAccountContext } from "../implementation/activeAccountContext.js";
8
10
  import type {
9
11
  CoValue,
10
12
  CoValueClass,
@@ -16,14 +18,15 @@ import type {
16
18
  } from "../internal.js";
17
19
  import {
18
20
  CoValueBase,
19
- MembersSym,
20
21
  Ref,
22
+ co,
21
23
  ensureCoValueLoaded,
22
24
  loadCoValueWithoutMe,
23
25
  parseGroupCreateOptions,
24
26
  subscribeToCoValueWithoutMe,
25
27
  subscribeToExistingCoValue,
26
28
  } from "../internal.js";
29
+ import { RegisteredAccount } from "../types.js";
27
30
  import { AccountAndGroupProxyHandler, isControlledAccount } from "./account.js";
28
31
  import { type Account } from "./account.js";
29
32
  import { type CoMap } from "./coMap.js";
@@ -44,7 +47,6 @@ export class Group extends CoValueBase implements CoValue {
44
47
  get _schema(): {
45
48
  profile: Schema;
46
49
  root: Schema;
47
- [MembersSym]: RefEncoded<Account>;
48
50
  } {
49
51
  return (this.constructor as typeof Group)._schema;
50
52
  }
@@ -52,10 +54,6 @@ export class Group extends CoValueBase implements CoValue {
52
54
  this._schema = {
53
55
  profile: "json" satisfies Schema,
54
56
  root: "json" satisfies Schema,
55
- [MembersSym]: {
56
- ref: () => RegisteredSchemas["Account"],
57
- optional: false,
58
- } satisfies RefEncoded<Account>,
59
57
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
58
  } as any;
61
59
  Object.defineProperty(this.prototype, "_schema", {
@@ -65,7 +63,6 @@ export class Group extends CoValueBase implements CoValue {
65
63
 
66
64
  declare profile: Profile | null;
67
65
  declare root: CoMap | null;
68
- declare [MembersSym]: Account | null;
69
66
 
70
67
  get _refs(): {
71
68
  profile: Ref<Profile> | undefined;
@@ -149,34 +146,73 @@ export class Group extends CoValueBase implements CoValue {
149
146
  return this._raw.removeMember(member === "everyone" ? member : member._raw);
150
147
  }
151
148
 
152
- get members() {
153
- return this._raw
154
- .keys()
155
- .filter((key) => {
156
- return key === "everyone" || key.startsWith("co_");
157
- })
158
- .map((id) => {
159
- const role = this._raw.get(id as Everyone | RawAccountID);
160
- const accountID =
161
- id === "everyone" ? undefined : (id as unknown as ID<Account>);
162
- const ref =
163
- accountID &&
164
- new Ref<NonNullable<this[MembersSym]>>(
165
- accountID,
166
- this._loadedAs,
167
- this._schema[MembersSym],
168
- );
169
- const accessRef = () => ref?.accessFrom(this, "members." + id);
170
-
171
- return {
172
- id: id as unknown as Everyone | ID<this[MembersSym]>,
149
+ get members(): Array<{
150
+ id: ID<RegisteredAccount>;
151
+ role: AccountRole;
152
+ ref: Ref<RegisteredAccount>;
153
+ account: RegisteredAccount;
154
+ }> {
155
+ const members = [];
156
+
157
+ const BaseAccountSchema =
158
+ (activeAccountContext.maybeGet()?.constructor as typeof Account) ||
159
+ RegisteredSchemas["Account"];
160
+ const refEncodedAccountSchema = {
161
+ ref: () => BaseAccountSchema,
162
+ optional: false,
163
+ } satisfies RefEncoded<RegisteredAccount>;
164
+
165
+ for (const accountID of this._raw.getAllMemberKeysSet()) {
166
+ if (!isAccountID(accountID)) continue;
167
+
168
+ const role = this._raw.roleOf(accountID);
169
+
170
+ if (
171
+ role === "admin" ||
172
+ role === "writer" ||
173
+ role === "reader" ||
174
+ role === "writeOnly"
175
+ ) {
176
+ const ref = new Ref<RegisteredAccount>(
177
+ accountID as unknown as ID<RegisteredAccount>,
178
+ this._loadedAs,
179
+ refEncodedAccountSchema,
180
+ );
181
+ const accessRef = () => ref.accessFrom(this, "members." + accountID);
182
+
183
+ if (!ref.syncLoad()) {
184
+ console.warn("Account not loaded", accountID);
185
+ }
186
+
187
+ members.push({
188
+ id: accountID as unknown as ID<Account>,
173
189
  role,
174
190
  ref,
175
191
  get account() {
176
- return accessRef();
192
+ // Accounts values are non-nullable because are loaded as dependencies
193
+ return accessRef() as RegisteredAccount;
177
194
  },
178
- };
179
- });
195
+ });
196
+ }
197
+ }
198
+
199
+ return members;
200
+ }
201
+
202
+ getRoleOf(member: Everyone | ID<Account> | "me") {
203
+ if (member === "me") {
204
+ return this._raw.roleOf(
205
+ activeAccountContext.get().id as unknown as RawAccountID,
206
+ );
207
+ }
208
+
209
+ return this._raw.roleOf(
210
+ member === "everyone" ? member : (member as unknown as RawAccountID),
211
+ );
212
+ }
213
+
214
+ getParentGroups(): Array<Group> {
215
+ return this._raw.getParentGroups().map(({ group }) => Group.fromRaw(group));
180
216
  }
181
217
 
182
218
  extend(
@@ -187,6 +223,11 @@ export class Group extends CoValueBase implements CoValue {
187
223
  return this;
188
224
  }
189
225
 
226
+ async revokeExtend(parent: Group) {
227
+ await this._raw.revokeExtend(parent._raw);
228
+ return this;
229
+ }
230
+
190
231
  /** @category Subscription & Loading */
191
232
  static load<C extends Group, Depth>(
192
233
  this: CoValueClass<C>,
@@ -268,3 +309,7 @@ export class Group extends CoValueBase implements CoValue {
268
309
  }
269
310
 
270
311
  RegisteredSchemas["Group"] = Group;
312
+
313
+ export function isAccountID(id: RawAccountID | AgentID): id is RawAccountID {
314
+ return id.startsWith("co_");
315
+ }
@@ -332,6 +332,16 @@ export class InboxSender<I extends CoValue, O extends CoValue | undefined> {
332
332
  throw new Error("Failed to load the inbox owner profile");
333
333
  }
334
334
 
335
+ if (
336
+ inboxOwnerProfileRaw.group.roleOf(currentAccount._raw.id) !== "reader" &&
337
+ inboxOwnerProfileRaw.group.roleOf(currentAccount._raw.id) !== "writer" &&
338
+ inboxOwnerProfileRaw.group.roleOf(currentAccount._raw.id) !== "admin"
339
+ ) {
340
+ throw new Error(
341
+ "Insufficient permissions to access the inbox, make sure its user profile is publicly readable.",
342
+ );
343
+ }
344
+
335
345
  const inboxInvite = inboxOwnerProfileRaw.get("inboxInvite");
336
346
 
337
347
  if (!inboxInvite) {
@@ -160,7 +160,7 @@ export function loadCoValueWithoutMe<V extends CoValue, Depth>(
160
160
  id: ID<CoValue>,
161
161
  asOrDepth: Account | AnonymousJazzAgent | (Depth & DepthsIn<V>),
162
162
  depth?: Depth & DepthsIn<V>,
163
- ) {
163
+ ): Promise<DeeplyLoaded<V, Depth> | undefined> {
164
164
  if (isAccountInstance(asOrDepth) || isAnonymousAgentInstance(asOrDepth)) {
165
165
  if (!depth) {
166
166
  throw new Error(
@@ -1,6 +1,8 @@
1
1
  import { CoID } from "cojson";
2
- import { co } from "../internal.js";
3
- import { CoMap } from "./coMap.js";
2
+ import { CoValueClass, co } from "../internal.js";
3
+ import { Account } from "./account.js";
4
+ import { CoMap, CoMapInit, Simplify } from "./coMap.js";
5
+ import { Group } from "./group.js";
4
6
  import { InboxInvite, InboxRoot } from "./inbox.js";
5
7
 
6
8
  /** @category Identity & Permissions */
@@ -8,4 +10,35 @@ export class Profile extends CoMap {
8
10
  name = co.string;
9
11
  inbox = co.optional.json<CoID<InboxRoot>>();
10
12
  inboxInvite = co.optional.json<InboxInvite>();
13
+
14
+ override get _owner(): Group {
15
+ return super._owner as Group;
16
+ }
17
+
18
+ /**
19
+ * Creates a new profile with the given initial values and owner.
20
+ *
21
+ * The owner (a Group) determines access rights to the Profile.
22
+ *
23
+ * @category Creation
24
+ */
25
+ static override create<M extends CoMap>(
26
+ this: CoValueClass<M>,
27
+ init: Simplify<CoMapInit<M>>,
28
+ options?:
29
+ | {
30
+ owner: Group;
31
+ }
32
+ | Group,
33
+ ) {
34
+ const owner =
35
+ options !== undefined && "owner" in options ? options.owner : options;
36
+
37
+ // We add some guardrails to ensure that the owner of a profile is a group
38
+ if ((owner as Group | Account | undefined)?._type === "Account") {
39
+ throw new Error("Profiles should be owned by a group");
40
+ }
41
+
42
+ return super.create<M>(init, options);
43
+ }
11
44
  }
@@ -43,7 +43,8 @@ export class JazzContextManager<
43
43
  protected context: PlatformSpecificContext<Acc> | undefined;
44
44
  protected props: P | undefined;
45
45
  protected authSecretStorage = new AuthSecretStorage();
46
- protected authenticating = false;
46
+ protected keepContextOpen = false;
47
+ protected contextPromise: Promise<void> | undefined;
47
48
 
48
49
  constructor() {
49
50
  KvStoreContext.getInstance().initialize(this.getKvStore());
@@ -54,6 +55,33 @@ export class JazzContextManager<
54
55
  }
55
56
 
56
57
  async createContext(props: P, authProps?: JazzContextManagerAuthProps) {
58
+ // We need to store the props here to block the double effect execution
59
+ // on React. Otherwise when calling propsChanged this.props is undefined.
60
+ this.props = props;
61
+
62
+ // Avoid race condition between the previous context and the new one
63
+ const { promise, resolve } = createResolvablePromise<void>();
64
+
65
+ const prevPromise = this.contextPromise;
66
+ this.contextPromise = promise;
67
+
68
+ await prevPromise;
69
+
70
+ try {
71
+ const result = await this.getNewContext(props, authProps);
72
+ await this.updateContext(props, result, authProps);
73
+
74
+ resolve();
75
+ } catch (error) {
76
+ resolve();
77
+ throw error;
78
+ }
79
+ }
80
+
81
+ async getNewContext(
82
+ props: P,
83
+ authProps?: JazzContextManagerAuthProps,
84
+ ): Promise<PlatformSpecificContext<Acc>> {
57
85
  props;
58
86
  authProps;
59
87
  throw new Error("Not implemented");
@@ -64,9 +92,9 @@ export class JazzContextManager<
64
92
  context: PlatformSpecificContext<Acc>,
65
93
  authProps?: JazzContextManagerAuthProps,
66
94
  ) {
67
- // When authenticating we don't want to close the previous context
95
+ // When keepContextOpen we don't want to close the previous context
68
96
  // because we might need to handle the onAnonymousAccountDiscarded callback
69
- if (!this.authenticating) {
97
+ if (!this.keepContextOpen) {
70
98
  this.context?.done();
71
99
  }
72
100
 
@@ -120,6 +148,18 @@ export class JazzContextManager<
120
148
  this.context.done();
121
149
  };
122
150
 
151
+ shouldMigrateAnonymousAccount = async () => {
152
+ if (!this.props?.onAnonymousAccountDiscarded) {
153
+ return false;
154
+ }
155
+
156
+ const prevCredentials = await this.authSecretStorage.get();
157
+ const wasAnonymous =
158
+ this.authSecretStorage.getIsAuthenticated(prevCredentials) === false;
159
+
160
+ return wasAnonymous;
161
+ };
162
+
123
163
  /**
124
164
  * Authenticates the user with the given credentials
125
165
  */
@@ -129,16 +169,15 @@ export class JazzContextManager<
129
169
  }
130
170
 
131
171
  const prevContext = this.context;
132
- const prevCredentials = await this.authSecretStorage.get();
133
- const wasAnonymous =
134
- this.authSecretStorage.getIsAuthenticated(prevCredentials) === false;
172
+ const migratingAnonymousAccount =
173
+ await this.shouldMigrateAnonymousAccount();
135
174
 
136
- this.authenticating = true;
175
+ this.keepContextOpen = migratingAnonymousAccount;
137
176
  await this.createContext(this.props, { credentials }).finally(() => {
138
- this.authenticating = false;
177
+ this.keepContextOpen = false;
139
178
  });
140
179
 
141
- if (wasAnonymous) {
180
+ if (migratingAnonymousAccount) {
142
181
  await this.handleAnonymousAccountMigration(prevContext);
143
182
  }
144
183
  };
@@ -152,21 +191,20 @@ export class JazzContextManager<
152
191
  }
153
192
 
154
193
  const prevContext = this.context;
155
- const prevCredentials = await this.authSecretStorage.get();
156
- const wasAnonymous =
157
- this.authSecretStorage.getIsAuthenticated(prevCredentials) === false;
194
+ const migratingAnonymousAccount =
195
+ await this.shouldMigrateAnonymousAccount();
158
196
 
159
- this.authenticating = true;
197
+ this.keepContextOpen = migratingAnonymousAccount;
160
198
  await this.createContext(this.props, {
161
199
  newAccountProps: {
162
200
  secret: accountSecret,
163
201
  creationProps,
164
202
  },
165
203
  }).finally(() => {
166
- this.authenticating = false;
204
+ this.keepContextOpen = false;
167
205
  });
168
206
 
169
- if (wasAnonymous) {
207
+ if (migratingAnonymousAccount) {
170
208
  await this.handleAnonymousAccountMigration(prevContext);
171
209
  }
172
210
 
@@ -235,3 +273,13 @@ export class JazzContextManager<
235
273
  }
236
274
  }
237
275
  }
276
+
277
+ function createResolvablePromise<T>() {
278
+ let resolve!: (value: T) => void;
279
+
280
+ const promise = new Promise<T>((res) => {
281
+ resolve = res;
282
+ });
283
+
284
+ return { promise, resolve };
285
+ }
@@ -6,7 +6,6 @@ import {
6
6
  CoValueFromRaw,
7
7
  ItemsSym,
8
8
  JazzToolsSymbol,
9
- MembersSym,
10
9
  SchemaInit,
11
10
  isCoValueClass,
12
11
  } from "../internal.js";
@@ -29,7 +28,7 @@ export type CoMarker = { readonly __co: unique symbol };
29
28
  export type co<T> = T | (T & CoMarker);
30
29
  export type IfCo<C, R> = C extends infer _A | infer B
31
30
  ? B extends CoMarker
32
- ? R extends JazzToolsSymbol // Exclude symbol properties like co.items or co.members from the refs/init types
31
+ ? R extends JazzToolsSymbol // Exclude symbol properties like co.items from the refs/init types
33
32
  ? never
34
33
  : R
35
34
  : never
@@ -100,7 +99,6 @@ export const co = {
100
99
  },
101
100
  ref,
102
101
  items: ItemsSym as ItemsSym,
103
- members: MembersSym as MembersSym,
104
102
  optional,
105
103
  };
106
104
 
package/src/testing.ts CHANGED
@@ -237,12 +237,10 @@ export class TestJazzContextManager<
237
237
  return context;
238
238
  }
239
239
 
240
- async createContext(
240
+ async getNewContext(
241
241
  props: TestJazzContextManagerProps<Acc>,
242
242
  authProps?: JazzContextManagerAuthProps,
243
243
  ) {
244
- this.props = props;
245
-
246
244
  if (!syncServer.current) {
247
245
  throw new Error(
248
246
  "You need to setup a test sync server with setupJazzTestSync to use the Auth functions",
@@ -260,20 +258,16 @@ export class TestJazzContextManager<
260
258
  AccountSchema: props.AccountSchema,
261
259
  });
262
260
 
263
- await this.updateContext(
264
- props,
265
- {
266
- me: context.account,
267
- node: context.node,
268
- done: () => {
269
- context.done();
270
- },
271
- logOut: () => {
272
- return context.logOut();
273
- },
261
+ return {
262
+ me: context.account,
263
+ node: context.node,
264
+ done: () => {
265
+ context.done();
274
266
  },
275
- authProps,
276
- );
267
+ logOut: () => {
268
+ return context.logOut();
269
+ },
270
+ };
277
271
  }
278
272
  }
279
273
 
@@ -1,11 +1,10 @@
1
1
  // @vitest-environment happy-dom
2
2
 
3
- import { Account } from "jazz-tools";
4
- import { ID } from "jazz-tools";
5
3
  import { beforeEach, describe, expect, it, vi } from "vitest";
6
4
  import { AuthSecretStorage } from "../auth/AuthSecretStorage";
7
5
  import { InMemoryKVStore } from "../auth/InMemoryKVStore.js";
8
6
  import KvStoreContext from "../auth/KvStoreContext";
7
+ import { Account, ID } from "../exports";
9
8
 
10
9
  const kvStore = new InMemoryKVStore();
11
10
  KvStoreContext.getInstance().initialize(kvStore);
@@ -35,7 +35,7 @@ class TestJazzContextManager<Acc extends Account> extends JazzContextManager<
35
35
  AccountSchema?: AccountClass<Acc>;
36
36
  }
37
37
  > {
38
- async createContext(
38
+ async getNewContext(
39
39
  props: JazzContextManagerBaseProps<Acc> & {
40
40
  defaultProfileName?: string;
41
41
  AccountSchema?: AccountClass<Acc>;
@@ -53,20 +53,16 @@ class TestJazzContextManager<Acc extends Account> extends JazzContextManager<
53
53
  AccountSchema: props.AccountSchema,
54
54
  });
55
55
 
56
- await this.updateContext(
57
- props,
58
- {
59
- me: context.account,
60
- node: context.node,
61
- done: () => {
62
- context.done();
63
- },
64
- logOut: async () => {
65
- await context.logOut();
66
- },
56
+ return {
57
+ me: context.account,
58
+ node: context.node,
59
+ done: () => {
60
+ context.done();
67
61
  },
68
- authProps,
69
- );
62
+ logOut: async () => {
63
+ await context.logOut();
64
+ },
65
+ };
70
66
  }
71
67
  }
72
68
 
@@ -127,6 +123,23 @@ describe("ContextManager", () => {
127
123
  expect(getCurrentValue().me.id).toBe(credentials.accountID);
128
124
  });
129
125
 
126
+ test("handles race conditions on the context creation", async () => {
127
+ const account = await createJazzTestAccount();
128
+
129
+ manager.createContext({});
130
+
131
+ const credentials = {
132
+ accountID: account.id,
133
+ accountSecret: account._raw.core.node.account.agentSecret,
134
+ provider: "test",
135
+ };
136
+
137
+ // Authenticate without waiting for the previous context to be created
138
+ await manager.authenticate(credentials);
139
+
140
+ expect(getCurrentValue().me.id).toBe(credentials.accountID);
141
+ });
142
+
130
143
  test("calls onLogOut callback when logging out", async () => {
131
144
  const onLogOut = vi.fn();
132
145
  await manager.createContext({ onLogOut });
@@ -2,15 +2,15 @@
2
2
 
3
3
  import { AgentSecret } from "cojson";
4
4
  import { PureJSCrypto } from "cojson/crypto/PureJSCrypto";
5
+ import { assert, beforeEach, describe, expect, it, vi } from "vitest";
6
+ import { PassphraseAuth } from "../auth/PassphraseAuth";
5
7
  import {
6
8
  Account,
7
9
  AuthSecretStorage,
8
10
  ID,
9
11
  InMemoryKVStore,
10
12
  KvStoreContext,
11
- } from "jazz-tools";
12
- import { assert, beforeEach, describe, expect, it, vi } from "vitest";
13
- import { PassphraseAuth } from "../auth/PassphraseAuth";
13
+ } from "../exports";
14
14
  import {
15
15
  TestJazzContextManager,
16
16
  createJazzTestAccount,
@@ -302,6 +302,10 @@ describe("PassphraseAuth with TestJazzContextManager", () => {
302
302
  // Verify account was created
303
303
  expect(accountId).toBeDefined();
304
304
 
305
+ await contextManager
306
+ .getCurrentValue()
307
+ ?.node.syncManager.waitForAllCoValuesSync();
308
+
305
309
  // Verify we can log in with the passphrase
306
310
  await contextManager.logOut();
307
311
  await passphraseAuth.logIn(passphrase);
@@ -10,7 +10,7 @@ import {
10
10
  createJazzContextFromExistingCredentials,
11
11
  isControlledAccount,
12
12
  } from "../index.js";
13
- import { setupTwoNodes, waitFor } from "./utils.js";
13
+ import { setupTwoNodes } from "./utils.js";
14
14
 
15
15
  const connectedPeers = cojsonInternals.connectedPeers;
16
16
 
@@ -119,6 +119,25 @@ describe("Simple CoMap operations", async () => {
119
119
  ).toThrow();
120
120
  });
121
121
 
122
+ test("testing toJSON on a CoMap with a Date field", () => {
123
+ const map = TestMap.create(
124
+ {
125
+ color: "red",
126
+ _height: 10,
127
+ birthday: new Date(),
128
+ },
129
+ { owner: me },
130
+ );
131
+
132
+ expect(map.toJSON()).toMatchObject({
133
+ color: "red",
134
+ _height: 10,
135
+ birthday: expect.any(String),
136
+ _type: "CoMap",
137
+ id: expect.any(String),
138
+ });
139
+ });
140
+
122
141
  test("setting optional date as undefined should not throw", () => {
123
142
  const map = TestMap.create(
124
143
  {
@@ -336,26 +355,6 @@ describe("Simple CoMap operations", async () => {
336
355
  expect(mapWithEnum.child?.value).toEqual(5);
337
356
  expect(mapWithEnum.child?.id).toBeDefined();
338
357
  });
339
-
340
- class SuperClassMap extends CoMap {
341
- name = co.string;
342
- }
343
-
344
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
345
- class SubClassMap extends SuperClassMap {
346
- name = co.literal("specificString");
347
- value = co.number;
348
- extra = co.ref(TestMap);
349
- }
350
-
351
- class GenericMapWithLoose<out T extends string = string> extends CoMap {
352
- name = co.json<T>();
353
- }
354
-
355
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
356
- const loose: GenericMapWithLoose<string> = {} as GenericMapWithLoose<
357
- "a" | "b"
358
- >;
359
358
  });
360
359
 
361
360
  describe("CoMap resolution", async () => {