jazz-tools 0.18.23 → 0.18.25

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 (116) hide show
  1. package/.svelte-kit/__package__/Provider.svelte +2 -0
  2. package/.svelte-kit/__package__/Provider.svelte.d.ts.map +1 -1
  3. package/.turbo/turbo-build.log +65 -65
  4. package/CHANGELOG.md +25 -0
  5. package/dist/browser/index.js +9 -9
  6. package/dist/browser/index.js.map +1 -1
  7. package/dist/{chunk-D5L6ES2M.js → chunk-DOCEAUVD.js} +72 -35
  8. package/dist/chunk-DOCEAUVD.js.map +1 -0
  9. package/dist/{chunk-BOMSRY5H.js → chunk-M2HGBOXS.js} +2 -2
  10. package/dist/chunk-M2HGBOXS.js.map +1 -0
  11. package/dist/index.js +2 -2
  12. package/dist/inspector/{custom-element-XDJT5T57.js → custom-element-A7UAELEG.js} +3 -1
  13. package/dist/inspector/{custom-element-XDJT5T57.js.map → custom-element-A7UAELEG.js.map} +1 -1
  14. package/dist/inspector/index.js +2 -0
  15. package/dist/inspector/index.js.map +1 -1
  16. package/dist/inspector/register-custom-element.js +1 -1
  17. package/dist/inspector/viewer/use-open-inspector.d.ts.map +1 -1
  18. package/dist/inspector/viewer/use-page-path.d.ts.map +1 -1
  19. package/dist/react/hooks.d.ts +1 -1
  20. package/dist/react/hooks.d.ts.map +1 -1
  21. package/dist/react/index.d.ts +2 -1
  22. package/dist/react/index.d.ts.map +1 -1
  23. package/dist/react/index.js +17 -3
  24. package/dist/react/index.js.map +1 -1
  25. package/dist/react/provider.d.ts +2 -1
  26. package/dist/react/provider.d.ts.map +1 -1
  27. package/dist/react/ssr.js +1 -1
  28. package/dist/react/ssr.js.map +1 -1
  29. package/dist/react-core/hooks.d.ts +13 -0
  30. package/dist/react-core/hooks.d.ts.map +1 -1
  31. package/dist/react-core/index.d.ts +2 -0
  32. package/dist/react-core/index.d.ts.map +1 -1
  33. package/dist/react-core/index.js +104 -0
  34. package/dist/react-core/index.js.map +1 -1
  35. package/dist/react-core/subscription-provider.d.ts +27 -0
  36. package/dist/react-core/subscription-provider.d.ts.map +1 -0
  37. package/dist/react-core/tests/subscription.bench.d.ts +2 -0
  38. package/dist/react-core/tests/subscription.bench.d.ts.map +1 -0
  39. package/dist/react-core/tests/useSubscriptionSelector.test.d.ts +2 -0
  40. package/dist/react-core/tests/useSubscriptionSelector.test.d.ts.map +1 -0
  41. package/dist/react-core/types.d.ts +10 -0
  42. package/dist/react-core/types.d.ts.map +1 -0
  43. package/dist/react-native-core/hooks.d.ts +1 -1
  44. package/dist/react-native-core/hooks.d.ts.map +1 -1
  45. package/dist/react-native-core/index.d.ts +1 -0
  46. package/dist/react-native-core/index.d.ts.map +1 -1
  47. package/dist/react-native-core/index.js +25 -12
  48. package/dist/react-native-core/index.js.map +1 -1
  49. package/dist/react-native-core/provider.d.ts +2 -1
  50. package/dist/react-native-core/provider.d.ts.map +1 -1
  51. package/dist/svelte/Provider.svelte +2 -0
  52. package/dist/svelte/Provider.svelte.d.ts.map +1 -1
  53. package/dist/testing.js +4 -4
  54. package/dist/testing.js.map +1 -1
  55. package/dist/tools/auth/AuthSecretStorage.d.ts +3 -1
  56. package/dist/tools/auth/AuthSecretStorage.d.ts.map +1 -1
  57. package/dist/tools/coValues/account.d.ts +3 -3
  58. package/dist/tools/coValues/account.d.ts.map +1 -1
  59. package/dist/tools/coValues/group.d.ts +20 -1
  60. package/dist/tools/coValues/group.d.ts.map +1 -1
  61. package/dist/tools/coValues/interfaces.d.ts.map +1 -1
  62. package/dist/tools/implementation/ContextManager.d.ts +1 -0
  63. package/dist/tools/implementation/ContextManager.d.ts.map +1 -1
  64. package/dist/tools/implementation/createContext.d.ts +7 -7
  65. package/dist/tools/implementation/createContext.d.ts.map +1 -1
  66. package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts +3 -8
  67. package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts.map +1 -1
  68. package/dist/tools/implementation/zodSchema/schemaTypes/GroupSchema.d.ts +5 -0
  69. package/dist/tools/implementation/zodSchema/schemaTypes/GroupSchema.d.ts.map +1 -1
  70. package/dist/tools/implementation/zodSchema/zodCo.d.ts.map +1 -1
  71. package/dist/tools/ssr.js +1 -1
  72. package/dist/tools/testing.d.ts.map +1 -1
  73. package/dist/worker/index.js +3 -3
  74. package/dist/worker/index.js.map +1 -1
  75. package/package.json +5 -4
  76. package/src/better-auth/database-adapter/tests/sync-utils.ts +1 -1
  77. package/src/browser/createBrowserContext.ts +9 -9
  78. package/src/browser/tests/utils.ts +2 -2
  79. package/src/inspector/viewer/use-open-inspector.ts +1 -0
  80. package/src/inspector/viewer/use-page-path.ts +1 -0
  81. package/src/react/hooks.tsx +3 -0
  82. package/src/react/index.ts +9 -0
  83. package/src/react/provider.tsx +3 -0
  84. package/src/react-core/hooks.ts +37 -5
  85. package/src/react-core/index.ts +2 -0
  86. package/src/react-core/subscription-provider.tsx +144 -0
  87. package/src/react-core/tests/subscription.bench.tsx +319 -0
  88. package/src/react-core/tests/useSubscriptionSelector.test.ts +250 -0
  89. package/src/react-core/types.ts +19 -0
  90. package/src/react-native-core/hooks.tsx +3 -0
  91. package/src/react-native-core/index.ts +6 -0
  92. package/src/react-native-core/platform.ts +9 -9
  93. package/src/react-native-core/provider.tsx +3 -1
  94. package/src/svelte/Provider.svelte +2 -0
  95. package/src/tools/auth/AuthSecretStorage.ts +16 -9
  96. package/src/tools/coValues/account.ts +5 -5
  97. package/src/tools/coValues/group.ts +33 -0
  98. package/src/tools/coValues/interfaces.ts +4 -1
  99. package/src/tools/implementation/ContextManager.ts +7 -3
  100. package/src/tools/implementation/createContext.ts +12 -12
  101. package/src/tools/implementation/zodSchema/schemaTypes/AccountSchema.ts +5 -5
  102. package/src/tools/implementation/zodSchema/schemaTypes/GroupSchema.ts +7 -1
  103. package/src/tools/ssr/ssr.ts +1 -1
  104. package/src/tools/testing.ts +3 -3
  105. package/src/tools/tests/AuthSecretStorage.test.ts +59 -1
  106. package/src/tools/tests/ContextManager.test.ts +11 -1
  107. package/src/tools/tests/account.test.ts +16 -0
  108. package/src/tools/tests/coPlainText.test.ts +2 -2
  109. package/src/tools/tests/createContext.test.ts +19 -19
  110. package/src/tools/tests/deepLoading.test.ts +2 -2
  111. package/src/tools/tests/group.test.ts +59 -0
  112. package/src/tools/tests/inbox.test.ts +2 -2
  113. package/src/tools/tests/utils.ts +3 -3
  114. package/src/worker/index.ts +3 -3
  115. package/dist/chunk-BOMSRY5H.js.map +0 -1
  116. package/dist/chunk-D5L6ES2M.js.map +0 -1
@@ -19,10 +19,12 @@
19
19
  let props: JazzContextManagerProps<S> & {
20
20
  children?: Snippet;
21
21
  enableSSR?: boolean;
22
+ authSecretStorageKey?: string;
22
23
  } = $props();
23
24
 
24
25
  const contextManager = new JazzBrowserContextManager<S>({
25
26
  useAnonymousFallback: props.enableSSR,
27
+ authSecretStorageKey: props.authSecretStorageKey,
26
28
  });
27
29
 
28
30
  const ctx = $state<JazzContext<InstanceOfSchema<S>>>({ current: undefined });
@@ -3,6 +3,7 @@ import type { Account } from "../coValues/account.js";
3
3
  import type { ID } from "../internal.js";
4
4
  import { AuthCredentials } from "../types.js";
5
5
  import KvStoreContext from "./KvStoreContext.js";
6
+ import { z } from "zod/v4";
6
7
 
7
8
  const STORAGE_KEY = "jazz-logged-in-secret";
8
9
 
@@ -24,20 +25,26 @@ export class AuthSecretStorage {
24
25
  private listeners: Set<(isAuthenticated: boolean) => void>;
25
26
  public isAuthenticated: boolean;
26
27
 
27
- constructor() {
28
+ constructor(private storageKey: string = STORAGE_KEY) {
29
+ z.string().nonempty().parse(storageKey);
30
+
28
31
  this.listeners = new Set();
29
32
  this.isAuthenticated = false;
30
33
  }
31
34
 
35
+ getStorageKey(): string {
36
+ return this.storageKey;
37
+ }
38
+
32
39
  async migrate() {
33
40
  const kvStore = KvStoreContext.getInstance().getStorage();
34
41
 
35
- if (!(await kvStore.get(STORAGE_KEY))) {
42
+ if (!(await kvStore.get(this.storageKey))) {
36
43
  const demoAuthSecret = await kvStore.get("demo-auth-logged-in-secret");
37
44
  if (demoAuthSecret) {
38
45
  const parsed = JSON.parse(demoAuthSecret);
39
46
  await kvStore.set(
40
- STORAGE_KEY,
47
+ this.storageKey,
41
48
  JSON.stringify({
42
49
  accountID: parsed.accountID,
43
50
  accountSecret: parsed.accountSecret,
@@ -51,7 +58,7 @@ export class AuthSecretStorage {
51
58
  if (clerkAuthSecret) {
52
59
  const parsed = JSON.parse(clerkAuthSecret);
53
60
  await kvStore.set(
54
- STORAGE_KEY,
61
+ this.storageKey,
55
62
  JSON.stringify({
56
63
  accountID: parsed.accountID,
57
64
  accountSecret: parsed.secret,
@@ -62,14 +69,14 @@ export class AuthSecretStorage {
62
69
  }
63
70
  }
64
71
 
65
- const value = await kvStore.get(STORAGE_KEY);
72
+ const value = await kvStore.get(this.storageKey);
66
73
 
67
74
  if (value) {
68
75
  const parsed = JSON.parse(value);
69
76
 
70
77
  if ("secret" in parsed) {
71
78
  await kvStore.set(
72
- STORAGE_KEY,
79
+ this.storageKey,
73
80
  JSON.stringify({
74
81
  accountID: parsed.accountID,
75
82
  secretSeed: parsed.secretSeed,
@@ -83,7 +90,7 @@ export class AuthSecretStorage {
83
90
 
84
91
  async get(): Promise<AuthCredentials | null> {
85
92
  const kvStore = KvStoreContext.getInstance().getStorage();
86
- const data = await kvStore.get(STORAGE_KEY);
93
+ const data = await kvStore.get(this.storageKey);
87
94
 
88
95
  if (!data) return null;
89
96
 
@@ -106,7 +113,7 @@ export class AuthSecretStorage {
106
113
  async setWithoutNotify(payload: AuthSetPayload) {
107
114
  const kvStore = KvStoreContext.getInstance().getStorage();
108
115
  await kvStore.set(
109
- STORAGE_KEY,
116
+ this.storageKey,
110
117
  JSON.stringify({
111
118
  accountID: payload.accountID,
112
119
  secretSeed: payload.secretSeed
@@ -148,7 +155,7 @@ export class AuthSecretStorage {
148
155
 
149
156
  async clearWithoutNotify() {
150
157
  const kvStore = KvStoreContext.getInstance().getStorage();
151
- await kvStore.delete(STORAGE_KEY);
158
+ await kvStore.delete(this.storageKey);
152
159
  }
153
160
 
154
161
  async clear() {
@@ -120,13 +120,13 @@ export class Account extends CoValueBase implements CoValue {
120
120
  *
121
121
  * @param valueID The ID of the `CoValue` or `Group` to accept the invite to.
122
122
  * @param inviteSecret The secret of the invite to accept.
123
- * @param coValueClass The class of the `CoValue` or `Group` to accept the invite to.
123
+ * @param coValueClass [Group] The class of the `CoValue` or `Group` to accept the invite to.
124
124
  * @returns The loaded `CoValue` or `Group`.
125
125
  */
126
126
  async acceptInvite<S extends CoValueClassOrSchema>(
127
127
  valueID: string,
128
128
  inviteSecret: InviteSecret,
129
- coValueClass: S,
129
+ coValueClass?: S,
130
130
  ): Promise<Resolved<InstanceOrPrimitiveOfSchema<S>, true> | null> {
131
131
  if (!this.$jazz.isLocalNodeOwner) {
132
132
  throw new Error("Only a controlled account can accept invites");
@@ -138,7 +138,7 @@ export class Account extends CoValueBase implements CoValue {
138
138
  );
139
139
 
140
140
  return loadCoValue(
141
- coValueClassFromCoValueClassOrSchema(coValueClass),
141
+ coValueClassFromCoValueClassOrSchema(coValueClass ?? Group),
142
142
  valueID,
143
143
  {
144
144
  loadAs: this,
@@ -212,7 +212,7 @@ export class Account extends CoValueBase implements CoValue {
212
212
  options: {
213
213
  creationProps: { name: string };
214
214
  initialAgentSecret?: AgentSecret;
215
- peersToLoadFrom?: Peer[];
215
+ peers?: Peer[];
216
216
  crypto: CryptoProvider;
217
217
  },
218
218
  ): Promise<A> {
@@ -256,7 +256,7 @@ export class Account extends CoValueBase implements CoValue {
256
256
  const account = await this.create<A>({
257
257
  creationProps: options.creationProps,
258
258
  crypto: as.$jazz.localNode.crypto,
259
- peersToLoadFrom: [connectedPeers[0]],
259
+ peers: [connectedPeers[0]],
260
260
  });
261
261
 
262
262
  await account.$jazz.waitForAllCoValuesSync();
@@ -3,6 +3,7 @@ import {
3
3
  type AccountRole,
4
4
  type AgentID,
5
5
  type Everyone,
6
+ type InviteSecret,
6
7
  type RawAccountID,
7
8
  type RawGroup,
8
9
  type Role,
@@ -299,6 +300,29 @@ export class Group extends CoValueBase implements CoValue {
299
300
  const { options, listener } = parseSubscribeRestArgs(args);
300
301
  return subscribeToCoValueWithoutMe<G, R>(this, id, options, listener);
301
302
  }
303
+
304
+ /** @category Invites
305
+ * Creates a group invite
306
+ * @param id The ID of the group to create an invite for
307
+ * @param options Optional configuration
308
+ * @param options.role The role to grant to the accepter of the invite. Defaults to 'reader'
309
+ * @param options.loadAs The account to use when loading the group. Defaults to the current account
310
+ * @returns An invite secret, (a string starting with "inviteSecret_"). Can be
311
+ * accepted using `Account.acceptInvite()`
312
+ */
313
+ static async createInvite<G extends Group>(
314
+ this: CoValueClass<G>,
315
+ id: ID<G>,
316
+ options?: { role?: AccountRole; loadAs?: Account },
317
+ ): Promise<InviteSecret> {
318
+ const group = await loadCoValueWithoutMe(this, id, {
319
+ loadAs: options?.loadAs,
320
+ });
321
+ if (!group) {
322
+ throw new Error(`Group with id ${id} not found`);
323
+ }
324
+ return group.$jazz.createInvite(options?.role ?? "reader");
325
+ }
302
326
  }
303
327
 
304
328
  export class GroupJazzApi<G extends Group> extends CoValueJazzApi<G> {
@@ -350,6 +374,15 @@ export class GroupJazzApi<G extends Group> extends CoValueJazzApi<G> {
350
374
  return subscribeToExistingCoValue(this.group, options, listener);
351
375
  }
352
376
 
377
+ /**
378
+ * Create an invite to this group
379
+ *
380
+ * @category Invites
381
+ */
382
+ createInvite(role: AccountRole = "reader"): InviteSecret {
383
+ return this.raw.createInvite(role);
384
+ }
385
+
353
386
  /**
354
387
  * Wait for the `Group` to be uploaded to the other peers.
355
388
  *
@@ -301,7 +301,10 @@ export function subscribeToCoValue<
301
301
  if (value.type === "unavailable") {
302
302
  options.onUnavailable?.();
303
303
 
304
- console.error(value.toString());
304
+ // Don't log unavailable errors when `loadUnique` or `upsertUnique` are used
305
+ if (!options.skipRetry) {
306
+ console.error(value.toString());
307
+ }
305
308
  } else if (value.type === "unauthorized") {
306
309
  options.onUnauthorized?.();
307
310
 
@@ -45,7 +45,7 @@ type PlatformSpecificContext<Acc extends Account> =
45
45
 
46
46
  function getAnonymousFallback() {
47
47
  const context = createAnonymousJazzContext({
48
- peersToLoadFrom: [],
48
+ peers: [],
49
49
  crypto: new PureJSCrypto(),
50
50
  });
51
51
 
@@ -71,13 +71,17 @@ export class JazzContextManager<
71
71
  protected value: JazzContextType<Acc> | undefined;
72
72
  protected context: PlatformSpecificContext<Acc> | undefined;
73
73
  protected props: P | undefined;
74
- protected authSecretStorage = new AuthSecretStorage();
74
+ protected authSecretStorage;
75
75
  protected keepContextOpen = false;
76
76
  contextPromise: Promise<void> | undefined;
77
77
  protected authenticatingAccountID: string | null = null;
78
78
 
79
- constructor(opts?: { useAnonymousFallback?: boolean }) {
79
+ constructor(opts?: {
80
+ useAnonymousFallback?: boolean;
81
+ authSecretStorageKey?: string;
82
+ }) {
80
83
  KvStoreContext.getInstance().initialize(this.getKvStore());
84
+ this.authSecretStorage = new AuthSecretStorage(opts?.authSecretStorageKey);
81
85
 
82
86
  if (opts?.useAnonymousFallback) {
83
87
  this.value = getAnonymousFallback();
@@ -90,7 +90,7 @@ export async function createJazzContextFromExistingCredentials<
90
90
  | CoreAccountSchema,
91
91
  >({
92
92
  credentials,
93
- peersToLoadFrom,
93
+ peers,
94
94
  crypto,
95
95
  storage,
96
96
  AccountSchema: PropsAccountSchema,
@@ -99,7 +99,7 @@ export async function createJazzContextFromExistingCredentials<
99
99
  asActiveAccount,
100
100
  }: {
101
101
  credentials: Credentials;
102
- peersToLoadFrom: Peer[];
102
+ peers: Peer[];
103
103
  crypto: CryptoProvider;
104
104
  AccountSchema?: S;
105
105
  sessionProvider: SessionProvider;
@@ -122,7 +122,7 @@ export async function createJazzContextFromExistingCredentials<
122
122
  accountID: credentials.accountID as unknown as CoID<RawAccount>,
123
123
  accountSecret: credentials.secret,
124
124
  sessionID: sessionID,
125
- peersToLoadFrom: peersToLoadFrom,
125
+ peers: peers,
126
126
  crypto: crypto,
127
127
  storage,
128
128
  migration: async (rawAccount, _node, creationProps) => {
@@ -162,7 +162,7 @@ export async function createJazzContextForNewAccount<
162
162
  >({
163
163
  creationProps,
164
164
  initialAgentSecret,
165
- peersToLoadFrom,
165
+ peers,
166
166
  crypto,
167
167
  AccountSchema: PropsAccountSchema,
168
168
  onLogOut,
@@ -170,7 +170,7 @@ export async function createJazzContextForNewAccount<
170
170
  }: {
171
171
  creationProps: { name: string };
172
172
  initialAgentSecret?: AgentSecret;
173
- peersToLoadFrom: Peer[];
173
+ peers: Peer[];
174
174
  crypto: CryptoProvider;
175
175
  AccountSchema?: S;
176
176
  onLogOut?: () => Promise<void>;
@@ -184,7 +184,7 @@ export async function createJazzContextForNewAccount<
184
184
 
185
185
  const { node } = await LocalNode.withNewlyCreatedAccount({
186
186
  creationProps,
187
- peersToLoadFrom,
187
+ peers,
188
188
  crypto,
189
189
  initialAgentSecret,
190
190
  storage,
@@ -219,7 +219,7 @@ export async function createJazzContext<
219
219
  >(options: {
220
220
  credentials?: AuthCredentials;
221
221
  newAccountProps?: NewAccountProps;
222
- peersToLoadFrom: Peer[];
222
+ peers: Peer[];
223
223
  crypto: CryptoProvider;
224
224
  defaultProfileName?: string;
225
225
  AccountSchema?: S;
@@ -243,7 +243,7 @@ export async function createJazzContext<
243
243
  accountID: credentials.accountID,
244
244
  secret: credentials.accountSecret,
245
245
  },
246
- peersToLoadFrom: options.peersToLoadFrom,
246
+ peers: options.peers,
247
247
  crypto,
248
248
  AccountSchema: options.AccountSchema,
249
249
  sessionProvider: options.sessionProvider,
@@ -267,7 +267,7 @@ export async function createJazzContext<
267
267
  context = await createJazzContextForNewAccount({
268
268
  creationProps,
269
269
  initialAgentSecret,
270
- peersToLoadFrom: options.peersToLoadFrom,
270
+ peers: options.peers,
271
271
  crypto,
272
272
  AccountSchema: options.AccountSchema,
273
273
  onLogOut: async () => {
@@ -293,11 +293,11 @@ export async function createJazzContext<
293
293
  }
294
294
 
295
295
  export function createAnonymousJazzContext({
296
- peersToLoadFrom,
296
+ peers,
297
297
  crypto,
298
298
  storage,
299
299
  }: {
300
- peersToLoadFrom: Peer[];
300
+ peers: Peer[];
301
301
  crypto: CryptoProvider;
302
302
  storage?: StorageAPI;
303
303
  }): JazzContextWithAgent {
@@ -309,7 +309,7 @@ export function createAnonymousJazzContext({
309
309
  crypto,
310
310
  );
311
311
 
312
- for (const peer of peersToLoadFrom) {
312
+ for (const peer of peers) {
313
313
  node.syncManager.addPeer(peer);
314
314
  }
315
315
 
@@ -1,10 +1,10 @@
1
- import { CryptoProvider } from "cojson";
2
1
  import {
3
2
  Account,
4
3
  AccountCreationProps,
5
4
  BranchDefinition,
6
5
  Group,
7
6
  RefsToResolveStrict,
7
+ Simplify,
8
8
  } from "../../../internal.js";
9
9
  import { AnonymousJazzAgent } from "../../anonymousJazzAgent.js";
10
10
  import { InstanceOrPrimitiveOfSchema } from "../typeConverters/InstanceOrPrimitiveOfSchema.js";
@@ -48,10 +48,9 @@ export interface AccountSchema<
48
48
  > {
49
49
  builtin: "Account";
50
50
 
51
- create: (options: {
52
- creationProps?: { name: string };
53
- crypto?: CryptoProvider;
54
- }) => Promise<AccountInstance<Shape>>;
51
+ create: (
52
+ options: Simplify<Parameters<(typeof Account)["create"]>[0]>,
53
+ ) => Promise<AccountInstance<Shape>>;
55
54
 
56
55
  load: <R extends ResolveQuery<AccountSchema<Shape>>>(
57
56
  id: string,
@@ -61,6 +60,7 @@ export interface AccountSchema<
61
60
  },
62
61
  ) => Promise<Loaded<AccountSchema<Shape>, R> | null>;
63
62
 
63
+ /** @internal */
64
64
  createAs: (
65
65
  as: Account,
66
66
  options: {
@@ -14,6 +14,7 @@ import {
14
14
  import { CoreCoValueSchema } from "./CoValueSchema.js";
15
15
  import { coOptionalDefiner } from "../zodCo.js";
16
16
  import { CoOptionalSchema } from "./CoOptionalSchema.js";
17
+ import type { AccountRole, InviteSecret } from "cojson";
17
18
 
18
19
  export interface CoreGroupSchema extends CoreCoValueSchema {
19
20
  builtin: "Group";
@@ -51,7 +52,12 @@ export class GroupSchema implements CoreGroupSchema {
51
52
  ): Promise<Group | null> {
52
53
  return Group.load(id, options);
53
54
  }
54
-
55
+ createInvite<G extends Group>(
56
+ id: ID<G>,
57
+ options?: { role?: AccountRole; loadAs?: Account },
58
+ ): Promise<InviteSecret> {
59
+ return Group.createInvite(id, options);
60
+ }
55
61
  subscribe<G extends Group, const R extends RefsToResolve<G>>(
56
62
  id: ID<G>,
57
63
  listener: (value: Resolved<G, R>, unsubscribe: () => void) => void,
@@ -5,7 +5,7 @@ import { createAnonymousJazzContext } from "jazz-tools";
5
5
  export function createSSRJazzAgent(opts: { peer: string }) {
6
6
  const ssrNode = createAnonymousJazzContext({
7
7
  crypto: new PureJSCrypto(),
8
- peersToLoadFrom: [],
8
+ peers: [],
9
9
  });
10
10
 
11
11
  const wsPeer = new WebSocketPeerWithReconnection({
@@ -112,7 +112,7 @@ export async function createJazzTestAccount<
112
112
  },
113
113
  initialAgentSecret: crypto.agentSecretFromSecretSeed(secretSeed),
114
114
  crypto,
115
- peersToLoadFrom: peers,
115
+ peers: peers,
116
116
  migration: async (rawAccount, _node, creationProps) => {
117
117
  if (isMigrationActive) {
118
118
  throw new Error(
@@ -185,7 +185,7 @@ export function runWithoutActiveAccount<Result>(
185
185
  export async function createJazzTestGuest() {
186
186
  const ctx = await createAnonymousJazzContext({
187
187
  crypto: await PureJSCrypto.create(),
188
- peersToLoadFrom: [],
188
+ peers: [],
189
189
  });
190
190
 
191
191
  return {
@@ -318,7 +318,7 @@ export class TestJazzContextManager<
318
318
  credentials: authProps?.credentials,
319
319
  defaultProfileName: props.defaultProfileName,
320
320
  newAccountProps: authProps?.newAccountProps,
321
- peersToLoadFrom: [getPeerConnectedToTestSyncServer()],
321
+ peers: [getPeerConnectedToTestSyncServer()],
322
322
  crypto: await TestJSCrypto.create(),
323
323
  sessionProvider: randomSessionProvider,
324
324
  authSecretStorage: this.getAuthSecretStorage(),
@@ -1,6 +1,6 @@
1
1
  // @vitest-environment happy-dom
2
2
 
3
- import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { afterAll, beforeEach, describe, expect, it, vi } from "vitest";
4
4
  import { AuthSecretStorage } from "../auth/AuthSecretStorage";
5
5
  import { InMemoryKVStore } from "../auth/InMemoryKVStore.js";
6
6
  import KvStoreContext from "../auth/KvStoreContext";
@@ -428,4 +428,62 @@ describe("AuthSecretStorage", () => {
428
428
  });
429
429
  });
430
430
  });
431
+
432
+ describe("configurable secret storage key", () => {
433
+ const KEY = "test-auth-secret";
434
+ beforeEach(() => {
435
+ kvStore.clearAll();
436
+ authSecretStorage = new AuthSecretStorage(KEY);
437
+ });
438
+
439
+ afterAll(() => {
440
+ // Restore the default storage key
441
+ authSecretStorage = new AuthSecretStorage();
442
+ });
443
+
444
+ it("should throw an error if the storage key is empty", () => {
445
+ expect(() => new AuthSecretStorage("")).toThrow(
446
+ "Too small: expected string to have >=1 characters",
447
+ );
448
+ });
449
+
450
+ it("should use the configured storage key on get", async () => {
451
+ const credentials = {
452
+ accountID: "test123",
453
+ secretSeed: [1, 2, 3],
454
+ accountSecret: "secret123",
455
+ provider: "anonymous",
456
+ };
457
+ await kvStore.set(KEY, JSON.stringify(credentials));
458
+
459
+ const result = await authSecretStorage.get();
460
+
461
+ expect(result).toEqual({
462
+ accountID: "test123",
463
+ secretSeed: new Uint8Array([1, 2, 3]),
464
+ accountSecret: "secret123",
465
+ provider: "anonymous",
466
+ });
467
+ });
468
+
469
+ it("should use the configured storage key on set", async () => {
470
+ const payload = {
471
+ accountID: "test123" as ID<Account>,
472
+ secretSeed: new Uint8Array([1, 2, 3]),
473
+ accountSecret:
474
+ "secret123" as `sealerSecret_z${string}/signerSecret_z${string}`,
475
+ provider: "passphrase",
476
+ };
477
+
478
+ await authSecretStorage.set(payload);
479
+
480
+ const stored = JSON.parse((await kvStore.get(KEY))!);
481
+ expect(stored).toEqual({
482
+ accountID: "test123",
483
+ secretSeed: [1, 2, 3],
484
+ accountSecret: "secret123",
485
+ provider: "passphrase",
486
+ });
487
+ });
488
+ });
431
489
  });
@@ -56,7 +56,7 @@ class TestJazzContextManager<Acc extends Account> extends JazzContextManager<
56
56
  credentials: authProps?.credentials,
57
57
  defaultProfileName: props.defaultProfileName,
58
58
  newAccountProps: authProps?.newAccountProps,
59
- peersToLoadFrom: [getPeerConnectedToTestSyncServer()],
59
+ peers: [getPeerConnectedToTestSyncServer()],
60
60
  crypto: Crypto,
61
61
  sessionProvider: randomSessionProvider,
62
62
  authSecretStorage: this.getAuthSecretStorage(),
@@ -541,6 +541,16 @@ describe("ContextManager", () => {
541
541
  ).rejects.toThrow("Props required");
542
542
  });
543
543
 
544
+ describe("configurable storage key", () => {
545
+ test("uses the configured storage key", async () => {
546
+ const KEY = "test-auth-secret";
547
+ const manager = new TestJazzContextManager<Account>({
548
+ authSecretStorageKey: KEY,
549
+ });
550
+ expect(manager.getAuthSecretStorage().getStorageKey()).toBe(KEY);
551
+ });
552
+ });
553
+
544
554
  describe("Race condition handling", () => {
545
555
  test("prevents concurrent authentication attempts", async () => {
546
556
  const account = await createJazzTestAccount();
@@ -409,3 +409,19 @@ describe("account.toJSON", () => {
409
409
  });
410
410
  });
411
411
  });
412
+
413
+ describe("accepting invites", () => {
414
+ test("accepting an invite to a Group", async () => {
415
+ const account = await createJazzTestAccount({
416
+ isCurrentActiveAccount: true,
417
+ });
418
+ const group = co.group().create();
419
+ const invite = group.$jazz.createInvite("reader");
420
+ const newAccount = await createJazzTestAccount({
421
+ isCurrentActiveAccount: true,
422
+ });
423
+ expect(group.getRoleOf(newAccount.$jazz.id)).toBeUndefined();
424
+ await newAccount.acceptInvite(group.$jazz.id, invite);
425
+ expect(group.getRoleOf(newAccount.$jazz.id)).toBe("reader");
426
+ });
427
+ });
@@ -205,7 +205,7 @@ describe("CoPlainText", () => {
205
205
  secret: me.$jazz.localNode.getCurrentAgent().agentSecret,
206
206
  },
207
207
  sessionProvider: randomSessionProvider,
208
- peersToLoadFrom: [initialAsPeer],
208
+ peers: [initialAsPeer],
209
209
  crypto: Crypto,
210
210
  asActiveAccount: true,
211
211
  });
@@ -237,7 +237,7 @@ describe("CoPlainText", () => {
237
237
  secret: me.$jazz.localNode.getCurrentAgent().agentSecret,
238
238
  },
239
239
  sessionProvider: randomSessionProvider,
240
- peersToLoadFrom: [initialAsPeer],
240
+ peers: [initialAsPeer],
241
241
  crypto: Crypto,
242
242
  asActiveAccount: true,
243
243
  });