jazz-tools 0.19.12 → 0.19.13

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 (119) hide show
  1. package/.turbo/turbo-build.log +50 -50
  2. package/CHANGELOG.md +10 -0
  3. package/dist/browser/createBrowserContext.d.ts +1 -5
  4. package/dist/browser/createBrowserContext.d.ts.map +1 -1
  5. package/dist/browser/index.js +124 -47
  6. package/dist/browser/index.js.map +1 -1
  7. package/dist/browser/provideBrowserLockSession/BrowserSessionProvider.d.ts +12 -0
  8. package/dist/browser/provideBrowserLockSession/BrowserSessionProvider.d.ts.map +1 -0
  9. package/dist/browser/provideBrowserLockSession/BrowserSessionProvider.test.d.ts +2 -0
  10. package/dist/browser/provideBrowserLockSession/BrowserSessionProvider.test.d.ts.map +1 -0
  11. package/dist/browser/provideBrowserLockSession/SessionIDStorage.d.ts +6 -0
  12. package/dist/browser/provideBrowserLockSession/SessionIDStorage.d.ts.map +1 -0
  13. package/dist/browser/provideBrowserLockSession/index.d.ts +4 -0
  14. package/dist/browser/provideBrowserLockSession/index.d.ts.map +1 -0
  15. package/dist/{chunk-AGF4HEDH.js → chunk-GAPMDNJY.js} +437 -82
  16. package/dist/chunk-GAPMDNJY.js.map +1 -0
  17. package/dist/index.js +5 -3
  18. package/dist/index.js.map +1 -1
  19. package/dist/react-native/index.js +41 -12
  20. package/dist/react-native/index.js.map +1 -1
  21. package/dist/react-native-core/ReactNativeSessionProvider.d.ts +11 -0
  22. package/dist/react-native-core/ReactNativeSessionProvider.d.ts.map +1 -0
  23. package/dist/react-native-core/index.js +41 -12
  24. package/dist/react-native-core/index.js.map +1 -1
  25. package/dist/react-native-core/platform.d.ts +2 -8
  26. package/dist/react-native-core/platform.d.ts.map +1 -1
  27. package/dist/react-native-core/tests/ReactNativeSessionProvider.test.d.ts +2 -0
  28. package/dist/react-native-core/tests/ReactNativeSessionProvider.test.d.ts.map +1 -0
  29. package/dist/testing.js +4 -3
  30. package/dist/testing.js.map +1 -1
  31. package/dist/tools/coValues/account.d.ts.map +1 -1
  32. package/dist/tools/coValues/coFeed.d.ts +2 -2
  33. package/dist/tools/coValues/coFeed.d.ts.map +1 -1
  34. package/dist/tools/coValues/coList.d.ts +1 -2
  35. package/dist/tools/coValues/coList.d.ts.map +1 -1
  36. package/dist/tools/coValues/coMap.d.ts.map +1 -1
  37. package/dist/tools/coValues/coVector.d.ts.map +1 -1
  38. package/dist/tools/coValues/group.d.ts +5 -1
  39. package/dist/tools/coValues/group.d.ts.map +1 -1
  40. package/dist/tools/coValues/interfaces.d.ts +2 -1
  41. package/dist/tools/coValues/interfaces.d.ts.map +1 -1
  42. package/dist/tools/exports.d.ts +2 -2
  43. package/dist/tools/exports.d.ts.map +1 -1
  44. package/dist/tools/implementation/createContext.d.ts +21 -11
  45. package/dist/tools/implementation/createContext.d.ts.map +1 -1
  46. package/dist/tools/implementation/schema.d.ts +14 -6
  47. package/dist/tools/implementation/schema.d.ts.map +1 -1
  48. package/dist/tools/implementation/schemaUtils.d.ts +1 -1
  49. package/dist/tools/implementation/schemaUtils.d.ts.map +1 -1
  50. package/dist/tools/implementation/zodSchema/runtimeConverters/schemaFieldToCoFieldDef.d.ts.map +1 -1
  51. package/dist/tools/implementation/zodSchema/schemaPermissions.d.ts +99 -0
  52. package/dist/tools/implementation/zodSchema/schemaPermissions.d.ts.map +1 -0
  53. package/dist/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.d.ts +11 -0
  54. package/dist/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.d.ts.map +1 -1
  55. package/dist/tools/implementation/zodSchema/schemaTypes/CoListSchema.d.ts +11 -0
  56. package/dist/tools/implementation/zodSchema/schemaTypes/CoListSchema.d.ts.map +1 -1
  57. package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts +15 -1
  58. package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts.map +1 -1
  59. package/dist/tools/implementation/zodSchema/schemaTypes/CoRecordSchema.d.ts +10 -0
  60. package/dist/tools/implementation/zodSchema/schemaTypes/CoRecordSchema.d.ts.map +1 -1
  61. package/dist/tools/implementation/zodSchema/schemaTypes/CoVectorSchema.d.ts +9 -0
  62. package/dist/tools/implementation/zodSchema/schemaTypes/CoVectorSchema.d.ts.map +1 -1
  63. package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts +13 -1
  64. package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts.map +1 -1
  65. package/dist/tools/implementation/zodSchema/schemaTypes/PlainTextSchema.d.ts +10 -0
  66. package/dist/tools/implementation/zodSchema/schemaTypes/PlainTextSchema.d.ts.map +1 -1
  67. package/dist/tools/implementation/zodSchema/schemaTypes/RichTextSchema.d.ts +6 -0
  68. package/dist/tools/implementation/zodSchema/schemaTypes/RichTextSchema.d.ts.map +1 -1
  69. package/dist/tools/implementation/zodSchema/unionUtils.d.ts +12 -1
  70. package/dist/tools/implementation/zodSchema/unionUtils.d.ts.map +1 -1
  71. package/dist/tools/internal.d.ts +1 -0
  72. package/dist/tools/internal.d.ts.map +1 -1
  73. package/dist/tools/testing.d.ts.map +1 -1
  74. package/dist/tools/tests/schema.withPermissions.test.d.ts +2 -0
  75. package/dist/tools/tests/schema.withPermissions.test.d.ts.map +1 -0
  76. package/dist/worker/index.js +2 -2
  77. package/dist/worker/index.js.map +1 -1
  78. package/package.json +4 -4
  79. package/src/browser/createBrowserContext.ts +3 -62
  80. package/src/browser/provideBrowserLockSession/BrowserSessionProvider.test.ts +406 -0
  81. package/src/browser/provideBrowserLockSession/BrowserSessionProvider.ts +132 -0
  82. package/src/browser/provideBrowserLockSession/SessionIDStorage.ts +33 -0
  83. package/src/browser/provideBrowserLockSession/index.ts +11 -0
  84. package/src/react-native-core/ReactNativeSessionProvider.ts +52 -0
  85. package/src/react-native-core/platform.ts +5 -30
  86. package/src/react-native-core/tests/ReactNativeSessionProvider.test.ts +124 -0
  87. package/src/tools/coValues/account.ts +4 -0
  88. package/src/tools/coValues/coFeed.ts +8 -3
  89. package/src/tools/coValues/coList.ts +6 -3
  90. package/src/tools/coValues/coMap.ts +10 -0
  91. package/src/tools/coValues/coVector.ts +2 -1
  92. package/src/tools/coValues/group.ts +6 -4
  93. package/src/tools/coValues/interfaces.ts +19 -7
  94. package/src/tools/exports.ts +3 -1
  95. package/src/tools/implementation/createContext.ts +43 -15
  96. package/src/tools/implementation/schema.ts +23 -13
  97. package/src/tools/implementation/schemaUtils.ts +1 -1
  98. package/src/tools/implementation/zodSchema/runtimeConverters/schemaFieldToCoFieldDef.ts +105 -4
  99. package/src/tools/implementation/zodSchema/schemaPermissions.ts +188 -0
  100. package/src/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.ts +46 -3
  101. package/src/tools/implementation/zodSchema/schemaTypes/CoListSchema.ts +46 -3
  102. package/src/tools/implementation/zodSchema/schemaTypes/CoMapSchema.ts +50 -13
  103. package/src/tools/implementation/zodSchema/schemaTypes/CoRecordSchema.ts +14 -0
  104. package/src/tools/implementation/zodSchema/schemaTypes/CoVectorSchema.ts +24 -1
  105. package/src/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.ts +51 -4
  106. package/src/tools/implementation/zodSchema/schemaTypes/PlainTextSchema.ts +25 -1
  107. package/src/tools/implementation/zodSchema/schemaTypes/RichTextSchema.ts +21 -1
  108. package/src/tools/implementation/zodSchema/unionUtils.ts +72 -20
  109. package/src/tools/internal.ts +1 -0
  110. package/src/tools/testing.ts +3 -1
  111. package/src/tools/tests/ContextManager.test.ts +2 -1
  112. package/src/tools/tests/coPlainText.test.ts +2 -2
  113. package/src/tools/tests/createContext.test.ts +79 -1
  114. package/src/tools/tests/deepLoading.test.ts +25 -2
  115. package/src/tools/tests/schema.resolved.test.ts +10 -0
  116. package/src/tools/tests/schema.withPermissions.test.ts +859 -0
  117. package/src/tools/tests/utils.ts +2 -2
  118. package/src/worker/index.ts +2 -2
  119. package/dist/chunk-AGF4HEDH.js.map +0 -1
@@ -0,0 +1,132 @@
1
+ import { AgentID, CryptoProvider, RawAccountID, SessionID } from "cojson";
2
+ import { ID, Account, SessionProvider } from "jazz-tools";
3
+ import { SessionIDStorage } from "./SessionIDStorage";
4
+
5
+ export class BrowserSessionProvider implements SessionProvider {
6
+ async acquireSession(
7
+ accountID: ID<Account> | AgentID,
8
+ crypto: CryptoProvider,
9
+ ): Promise<{ sessionID: SessionID; sessionDone: () => void }> {
10
+ const { sessionPromise, resolveSession } = createSessionLockPromise();
11
+
12
+ // Get the list of sessions for the account, to try to acquire an existing session
13
+ const sessionsList = SessionIDStorage.getSessionsList(accountID);
14
+
15
+ for (const [index, sessionID] of sessionsList.entries()) {
16
+ const sessionAcquired = await tryToAcquireSession(
17
+ sessionID,
18
+ sessionPromise,
19
+ );
20
+
21
+ if (sessionAcquired) {
22
+ console.log("Using existing session", sessionID, "at index", index); // This log is used in the e2e tests to verify the correctness of the feature
23
+ return {
24
+ sessionID,
25
+ sessionDone: resolveSession,
26
+ };
27
+ }
28
+ }
29
+
30
+ const newSessionID = crypto.newRandomSessionID(
31
+ accountID as RawAccountID | AgentID,
32
+ );
33
+
34
+ // Acquire exclusively the session to store the new session ID for reuse in future sessions
35
+ await lockAndStoreSession(accountID, newSessionID, sessionPromise);
36
+
37
+ console.log("Created new session", newSessionID); // This log is used in the e2e tests to verify the correctness of the feature
38
+
39
+ return {
40
+ sessionID: newSessionID,
41
+ sessionDone: resolveSession,
42
+ };
43
+ }
44
+
45
+ async persistSession(
46
+ accountID: ID<Account> | AgentID,
47
+ sessionID: SessionID,
48
+ ): Promise<{ sessionDone: () => void }> {
49
+ const { sessionPromise, resolveSession } = createSessionLockPromise();
50
+
51
+ // Store the session id for future use and lock it until the session is done
52
+ await lockAndStoreSession(accountID, sessionID, sessionPromise);
53
+
54
+ console.log("Stored new session", sessionID);
55
+
56
+ return { sessionDone: resolveSession };
57
+ }
58
+ }
59
+
60
+ async function lockAndStoreSession(
61
+ accountID: ID<Account> | AgentID,
62
+ sessionID: SessionID,
63
+ sessionPromise: Promise<void>,
64
+ ) {
65
+ const sessionAcquired = await tryToAcquireSession(sessionID, sessionPromise);
66
+
67
+ if (!sessionAcquired) {
68
+ // This should never happen because the session has been randomly generated
69
+ throw new Error("Couldn't get lock on new session");
70
+ }
71
+
72
+ // We don't need to wait for this to finish, we only need to acquire the lock on the new session
73
+ storeSessionID(accountID, sessionID);
74
+ }
75
+
76
+ function tryToAcquireSession(
77
+ sessionID: SessionID,
78
+ sessionDonePromise: Promise<void>,
79
+ ) {
80
+ return new Promise<boolean>((resolve) => {
81
+ // Acquire exclusively the session if available
82
+ navigator.locks.request(
83
+ `load_session_${sessionID}`,
84
+ { mode: "exclusive", ifAvailable: true },
85
+ async (lock) => {
86
+ if (!lock) {
87
+ resolve(false); // Session already in use
88
+ return;
89
+ }
90
+
91
+ resolve(true); // Session is available
92
+
93
+ // Return the promise to lock the session until sessionDone is called
94
+ return sessionDonePromise;
95
+ },
96
+ );
97
+ });
98
+ }
99
+
100
+ function storeSessionID(
101
+ accountID: ID<Account> | AgentID,
102
+ sessionID: SessionID,
103
+ ) {
104
+ return navigator.locks.request(
105
+ `store_session_${accountID}`,
106
+ { mode: "exclusive" },
107
+ async (lock) => {
108
+ if (!lock) {
109
+ console.error("Couldn't get lock to store session ID", accountID);
110
+ }
111
+
112
+ const sessionsList = SessionIDStorage.getSessionsList(accountID);
113
+ SessionIDStorage.storeSessionID(
114
+ accountID,
115
+ sessionID,
116
+ sessionsList.length,
117
+ );
118
+ },
119
+ );
120
+ }
121
+
122
+ function createSessionLockPromise() {
123
+ let resolveSession: () => void;
124
+ const sessionPromise = new Promise<void>((resolve) => {
125
+ resolveSession = resolve;
126
+ });
127
+
128
+ return {
129
+ sessionPromise,
130
+ resolveSession: resolveSession!,
131
+ };
132
+ }
@@ -0,0 +1,33 @@
1
+ import { SessionID } from "cojson";
2
+
3
+ function getSessionKey(accountID: string, index: number) {
4
+ return accountID + "_" + index;
5
+ }
6
+
7
+ export class SessionIDStorage {
8
+ static getSessionsList(accountID: string) {
9
+ let sessionsList: SessionID[] = [];
10
+ let i = 0;
11
+ let lastSessionID: SessionID | null;
12
+
13
+ do {
14
+ lastSessionID = localStorage.getItem(
15
+ getSessionKey(accountID, i),
16
+ ) as SessionID | null;
17
+ if (lastSessionID) {
18
+ sessionsList.push(lastSessionID);
19
+ }
20
+ i++;
21
+ } while (lastSessionID);
22
+
23
+ return sessionsList;
24
+ }
25
+
26
+ static storeSessionID(
27
+ accountID: string,
28
+ sessionID: SessionID,
29
+ index: number,
30
+ ) {
31
+ localStorage.setItem(getSessionKey(accountID, index), sessionID);
32
+ }
33
+ }
@@ -0,0 +1,11 @@
1
+ import { MockSessionProvider } from "jazz-tools";
2
+ import { BrowserSessionProvider } from "./BrowserSessionProvider";
3
+
4
+ export function getBrowserLockSessionProvider() {
5
+ if (typeof navigator === "undefined" || !navigator.locks?.request) {
6
+ // Fallback to random session ID for each tab session
7
+ return new MockSessionProvider();
8
+ }
9
+
10
+ return new BrowserSessionProvider();
11
+ }
@@ -0,0 +1,52 @@
1
+ import {
2
+ CryptoProvider,
3
+ KvStoreContext,
4
+ SessionID,
5
+ SessionProvider,
6
+ } from "jazz-tools";
7
+ import { AgentID, RawAccountID } from "cojson";
8
+
9
+ export class ReactNativeSessionProvider implements SessionProvider {
10
+ async acquireSession(
11
+ accountID: string,
12
+ crypto: CryptoProvider,
13
+ ): Promise<{ sessionID: SessionID; sessionDone: () => void }> {
14
+ const kvStore = KvStoreContext.getInstance().getStorage();
15
+ const existingSession = await kvStore.get(accountID as string);
16
+
17
+ if (existingSession) {
18
+ console.log("Using existing session", existingSession);
19
+ return Promise.resolve({
20
+ sessionID: existingSession as SessionID,
21
+ sessionDone: () => {},
22
+ });
23
+ }
24
+
25
+ // We need to provide this for backwards compatibility with the old session provider
26
+ // With the current session provider we should never get here because:
27
+ // - New accounts provide their session and go through the persistSession method
28
+ // - Existing accounts should already have a session
29
+ const newSessionID = crypto.newRandomSessionID(
30
+ accountID as RawAccountID | AgentID,
31
+ );
32
+ await kvStore.set(accountID, newSessionID);
33
+
34
+ console.error("Created new session", newSessionID);
35
+
36
+ return Promise.resolve({
37
+ sessionID: newSessionID,
38
+ sessionDone: () => {},
39
+ });
40
+ }
41
+
42
+ async persistSession(
43
+ accountID: string,
44
+ sessionID: SessionID,
45
+ ): Promise<{ sessionDone: () => void }> {
46
+ const kvStore = KvStoreContext.getInstance().getStorage();
47
+ await kvStore.set(accountID, sessionID);
48
+ return Promise.resolve({
49
+ sessionDone: () => {},
50
+ });
51
+ }
52
+ }
@@ -1,25 +1,22 @@
1
1
  import NetInfo from "@react-native-community/netinfo";
2
- import { LocalNode, Peer, RawAccountID, getSqliteStorageAsync } from "cojson";
2
+ import { LocalNode, Peer, getSqliteStorageAsync } from "cojson";
3
3
  import { PureJSCrypto } from "cojson/dist/crypto/PureJSCrypto"; // Importing from dist to not rely on the exports field
4
4
  import {
5
5
  Account,
6
6
  AccountClass,
7
- AgentID,
8
7
  AnyAccountSchema,
9
8
  AuthCredentials,
10
9
  AuthSecretStorage,
11
10
  CoValue,
12
11
  CoValueFromRaw,
13
- CryptoProvider,
14
- ID,
15
12
  NewAccountProps,
16
- SessionID,
17
13
  SyncConfig,
18
14
  createInviteLink as baseCreateInviteLink,
19
15
  createAnonymousJazzContext,
20
16
  createJazzContext,
21
17
  } from "jazz-tools";
22
18
  import { KvStore, KvStoreContext } from "./storage/kv-store-context.js";
19
+ import { ReactNativeSessionProvider } from "./ReactNativeSessionProvider.js";
23
20
 
24
21
  import { SQLiteDatabaseDriverAsync } from "cojson";
25
22
  import { WebSocketPeerWithReconnection } from "cojson-transport-ws";
@@ -200,6 +197,8 @@ export async function createJazzReactNativeContext<
200
197
  handleAuthUpdate(authSecretStorage.isAuthenticated);
201
198
  }
202
199
 
200
+ const sessionProvider = new ReactNativeSessionProvider();
201
+
203
202
  const context = await createJazzContext({
204
203
  credentials: options.credentials,
205
204
  newAccountProps: options.newAccountProps,
@@ -207,7 +206,7 @@ export async function createJazzReactNativeContext<
207
206
  crypto,
208
207
  defaultProfileName: options.defaultProfileName,
209
208
  AccountSchema: options.AccountSchema,
210
- sessionProvider: provideLockSession,
209
+ sessionProvider,
211
210
  authSecretStorage: options.authSecretStorage,
212
211
  storage,
213
212
  });
@@ -233,30 +232,6 @@ export async function createJazzReactNativeContext<
233
232
  };
234
233
  }
235
234
 
236
- /** @category Auth Providers */
237
- export type SessionProvider = (
238
- accountID: ID<Account> | AgentID,
239
- ) => Promise<SessionID>;
240
-
241
- export async function provideLockSession(
242
- accountID: ID<Account> | AgentID,
243
- crypto: CryptoProvider,
244
- ) {
245
- const sessionDone = () => {};
246
-
247
- const kvStore = KvStoreContext.getInstance().getStorage();
248
-
249
- const sessionID =
250
- ((await kvStore.get(accountID)) as SessionID) ||
251
- crypto.newRandomSessionID(accountID as RawAccountID | AgentID);
252
- await kvStore.set(accountID, sessionID);
253
-
254
- return Promise.resolve({
255
- sessionID,
256
- sessionDone,
257
- });
258
- }
259
-
260
235
  /** @category Invite Links */
261
236
  export function createInviteLink<C extends CoValue>(
262
237
  value: C,
@@ -0,0 +1,124 @@
1
+ import { WasmCrypto } from "cojson/crypto/WasmCrypto";
2
+ import { SessionID } from "cojson";
3
+ import { beforeEach, describe, expect, test } from "vitest";
4
+ import { InMemoryKVStore } from "jazz-tools";
5
+ import { KvStoreContext, type KvStore } from "jazz-tools";
6
+ import { ReactNativeSessionProvider } from "../ReactNativeSessionProvider.js";
7
+ import { createJazzTestAccount } from "jazz-tools/testing";
8
+ import type { CryptoProvider } from "jazz-tools";
9
+
10
+ // Initialize KV store for tests
11
+ const kvStore = new InMemoryKVStore() as KvStore;
12
+ KvStoreContext.getInstance().initialize(kvStore);
13
+
14
+ const Crypto = await WasmCrypto.create();
15
+
16
+ describe("ReactNativeSessionProvider", () => {
17
+ let sessionProvider: ReactNativeSessionProvider;
18
+ let account: Awaited<ReturnType<typeof createJazzTestAccount>>;
19
+
20
+ beforeEach(async () => {
21
+ // Clear KV store
22
+ kvStore.clearAll();
23
+
24
+ // Create new session provider instance
25
+ sessionProvider = new ReactNativeSessionProvider();
26
+
27
+ // Create test account
28
+ account = await createJazzTestAccount({
29
+ isCurrentActiveAccount: true,
30
+ });
31
+ });
32
+
33
+ describe("acquireSession", () => {
34
+ test("creates new session when none exists", async () => {
35
+ const accountID = account.$jazz.id;
36
+
37
+ // Verify no session exists
38
+ const existingSessionBefore = await kvStore.get(accountID);
39
+ expect(existingSessionBefore).toBeNull();
40
+
41
+ // Acquire session
42
+ const result = await sessionProvider.acquireSession(
43
+ accountID,
44
+ Crypto as CryptoProvider,
45
+ );
46
+
47
+ // Verify a new session ID is generated
48
+ expect(result.sessionID).toBeDefined();
49
+
50
+ // Verify the session is stored in KvStore
51
+ const storedSession = await kvStore.get(accountID);
52
+ expect(storedSession).toBeDefined();
53
+ expect(storedSession).toBe(result.sessionID);
54
+ });
55
+
56
+ test("returns existing session when one exists", async () => {
57
+ const accountID = account.$jazz.id;
58
+ const existingSessionID = "existing-session-id" as SessionID;
59
+
60
+ // Pre-populate KvStore with a session ID
61
+ await kvStore.set(accountID, existingSessionID);
62
+
63
+ // Verify session exists before calling acquireSession
64
+ const sessionBefore = await kvStore.get(accountID);
65
+ expect(sessionBefore).toBe(existingSessionID);
66
+
67
+ // Acquire session
68
+ const result = await sessionProvider.acquireSession(
69
+ accountID,
70
+ Crypto as CryptoProvider,
71
+ );
72
+
73
+ // Verify the existing session ID is returned (not a new one)
74
+ expect(result.sessionID).toBe(existingSessionID);
75
+
76
+ // Verify no new session is created (same value still in store)
77
+ const sessionAfter = await kvStore.get(accountID);
78
+ expect(sessionAfter).toBe(existingSessionID);
79
+ expect(sessionAfter).toBe(result.sessionID);
80
+ });
81
+ });
82
+
83
+ describe("persistSession", () => {
84
+ test("stores session ID correctly", async () => {
85
+ const accountID = account.$jazz.id;
86
+ const sessionID = "test-session-id" as SessionID;
87
+
88
+ // Verify no session exists before
89
+ const sessionBefore = await kvStore.get(accountID);
90
+ expect(sessionBefore).toBeNull();
91
+
92
+ // Persist session
93
+ await sessionProvider.persistSession(accountID, sessionID);
94
+
95
+ // Verify the session ID is stored in KvStore
96
+ const storedSession = await kvStore.get(accountID);
97
+ expect(storedSession).toBeDefined();
98
+
99
+ // Verify the stored value matches the provided session ID
100
+ expect(storedSession).toBe(sessionID);
101
+ });
102
+
103
+ test("overwrites existing session", async () => {
104
+ const accountID = account.$jazz.id;
105
+ const initialSessionID = "initial-session-id" as SessionID;
106
+ const newSessionID = "new-session-id" as SessionID;
107
+
108
+ // Store an initial session ID
109
+ await kvStore.set(accountID, initialSessionID);
110
+
111
+ // Verify initial session is stored
112
+ const sessionBefore = await kvStore.get(accountID);
113
+ expect(sessionBefore).toBe(initialSessionID);
114
+
115
+ // Persist a different session ID
116
+ await sessionProvider.persistSession(accountID, newSessionID);
117
+
118
+ // Verify the new session ID replaces the old one
119
+ const sessionAfter = await kvStore.get(accountID);
120
+ expect(sessionAfter).toBe(newSessionID);
121
+ expect(sessionAfter).not.toBe(initialSessionID);
122
+ });
123
+ });
124
+ });
@@ -466,10 +466,14 @@ class AccountJazzApi<A extends Account> extends CoValueJazzApi<A> {
466
466
  | undefined;
467
467
  if (!refId) {
468
468
  const descriptor = this.schema[key];
469
+ const newOwnerStrategy = descriptor.permissions?.newInlineOwnerStrategy;
470
+ const onCreate = descriptor.permissions?.onCreate;
469
471
  const coValue = instantiateRefEncodedWithInit(
470
472
  descriptor,
471
473
  value,
472
474
  accountOrGroupToGroup(this.account),
475
+ newOwnerStrategy,
476
+ onCreate,
473
477
  );
474
478
  refId = coValue.$jazz.id as CoID<RawCoMap>;
475
479
  }
@@ -32,8 +32,6 @@ import {
32
32
  SubscribeRestArgs,
33
33
  TypeSym,
34
34
  BranchDefinition,
35
- } from "../internal.js";
36
- import {
37
35
  Account,
38
36
  CoValueBase,
39
37
  CoValueJazzApi,
@@ -379,10 +377,15 @@ export class CoFeedJazzApi<F extends CoFeed> extends CoValueJazzApi<F> {
379
377
  } else if (isRefEncoded(itemDescriptor)) {
380
378
  let refId = (item as unknown as CoValue).$jazz?.id;
381
379
  if (!refId) {
380
+ const newOwnerStrategy =
381
+ itemDescriptor.permissions?.newInlineOwnerStrategy;
382
+ const onCreate = itemDescriptor.permissions?.onCreate;
382
383
  const coValue = instantiateRefEncodedWithInit(
383
384
  itemDescriptor,
384
385
  item,
385
386
  this.owner,
387
+ newOwnerStrategy,
388
+ onCreate,
386
389
  );
387
390
  refId = coValue.$jazz.id;
388
391
  }
@@ -720,6 +723,7 @@ export class FileStream extends CoValueBase implements CoValue {
720
723
  *
721
724
  * @param options - Configuration options for the new FileStream
722
725
  * @param options.owner - The Account or Group that will own this FileStream and control access rights
726
+ * @param schemaConfiguration - Internal schema configuration
723
727
  *
724
728
  * @example
725
729
  * ```typescript
@@ -743,7 +747,8 @@ export class FileStream extends CoValueBase implements CoValue {
743
747
  this: CoValueClass<S>,
744
748
  options?: { owner?: Account | Group } | Account | Group,
745
749
  ) {
746
- return new this(parseCoValueCreateOptions(options));
750
+ const { owner } = parseCoValueCreateOptions(options);
751
+ return new this({ owner });
747
752
  }
748
753
 
749
754
  getMetadata(): BinaryStreamInfo | undefined {
@@ -26,8 +26,6 @@ import {
26
26
  getIdFromHeader,
27
27
  internalLoadUnique,
28
28
  CoValueLoadingState,
29
- } from "../internal.js";
30
- import {
31
29
  AnonymousJazzAgent,
32
30
  ItemsSym,
33
31
  Ref,
@@ -172,8 +170,8 @@ export class CoList<out Item = any>
172
170
  | Account
173
171
  | Group,
174
172
  ) {
175
- const { owner, uniqueness } = parseCoValueCreateOptions(options);
176
173
  const instance = new this();
174
+ const { owner, uniqueness } = parseCoValueCreateOptions(options);
177
175
 
178
176
  Object.defineProperties(instance, {
179
177
  $jazz: {
@@ -918,10 +916,15 @@ function toRawItems<Item>(
918
916
  }
919
917
  let refId = (value as unknown as CoValue).$jazz?.id;
920
918
  if (!refId) {
919
+ const newOwnerStrategy =
920
+ itemDescriptor.permissions?.newInlineOwnerStrategy;
921
+ const onCreate = itemDescriptor.permissions?.onCreate;
921
922
  const coValue = instantiateRefEncodedWithInit(
922
923
  itemDescriptor,
923
924
  value,
924
925
  owner,
926
+ newOwnerStrategy,
927
+ onCreate,
925
928
  );
926
929
  refId = coValue.$jazz.id;
927
930
  }
@@ -298,10 +298,15 @@ export class CoMap extends CoValueBase implements CoValue {
298
298
  if (initValue != null) {
299
299
  let refId = (initValue as unknown as CoValue).$jazz?.id;
300
300
  if (!refId) {
301
+ const newOwnerStrategy =
302
+ descriptor.permissions?.newInlineOwnerStrategy;
303
+ const onCreate = descriptor.permissions?.onCreate;
301
304
  const coValue = instantiateRefEncodedWithInit(
302
305
  descriptor,
303
306
  initValue,
304
307
  owner,
308
+ newOwnerStrategy,
309
+ onCreate,
305
310
  );
306
311
  refId = coValue.$jazz.id;
307
312
  }
@@ -610,10 +615,15 @@ class CoMapJazzApi<M extends CoMap> extends CoValueJazzApi<M> {
610
615
  this.raw.set(key, null);
611
616
  } else {
612
617
  if (!refId) {
618
+ const newOwnerStrategy =
619
+ descriptor.permissions?.newInlineOwnerStrategy;
620
+ const onCreate = descriptor.permissions?.onCreate;
613
621
  const coValue = instantiateRefEncodedWithInit(
614
622
  descriptor,
615
623
  value,
616
624
  this.owner,
625
+ newOwnerStrategy,
626
+ onCreate,
617
627
  );
618
628
  refId = coValue.$jazz.id;
619
629
  }
@@ -129,7 +129,8 @@ export class CoVector
129
129
  );
130
130
  }
131
131
 
132
- const coVector = new this(parseCoValueCreateOptions(options));
132
+ const { owner } = parseCoValueCreateOptions(options);
133
+ const coVector = new this({ owner });
133
134
  coVector.setVectorData(vectorAsFloat32Array);
134
135
 
135
136
  const byteArray = CoVector.toByteArray(vectorAsFloat32Array);
@@ -48,6 +48,11 @@ type GroupMember = {
48
48
  account: Account;
49
49
  };
50
50
 
51
+ /**
52
+ * Roles that can be granted to a group member.
53
+ */
54
+ export type GroupRole = "reader" | "writer" | "admin" | "manager";
55
+
51
56
  /** @category Identity & Permissions */
52
57
  export class Group extends CoValueBase implements CoValue {
53
58
  declare [TypeSym]: "Group";
@@ -107,10 +112,7 @@ export class Group extends CoValueBase implements CoValue {
107
112
  * @param member The group that will gain access to this group.
108
113
  * @param role The role all members of the parent group should have in this group.
109
114
  */
110
- addMember(
111
- member: Group,
112
- role?: "reader" | "writer" | "admin" | "manager" | "inherit",
113
- ): void;
115
+ addMember(member: Group, role?: GroupRole | "inherit"): void;
114
116
  addMember(
115
117
  member: Group | Account,
116
118
  role: "reader" | "writer" | "admin" | "manager",
@@ -11,10 +11,11 @@ import {
11
11
  CoValueClassOrSchema,
12
12
  CoValueLoadingState,
13
13
  NotLoadedCoValueState,
14
- type Group,
14
+ Group,
15
15
  Loaded,
16
16
  Inaccessible,
17
17
  MaybeLoaded,
18
+ OnCreateCallback,
18
19
  Settled,
19
20
  RefsToResolve,
20
21
  RefsToResolveStrict,
@@ -424,6 +425,7 @@ export function parseCoValueCreateOptions(
424
425
  | {
425
426
  owner?: Account | Group;
426
427
  unique?: CoValueUniqueness["uniqueness"];
428
+ onCreate?: OnCreateCallback;
427
429
  }
428
430
  | Account
429
431
  | Group
@@ -432,16 +434,22 @@ export function parseCoValueCreateOptions(
432
434
  owner: Group;
433
435
  uniqueness?: CoValueUniqueness;
434
436
  } {
437
+ const onCreate =
438
+ options && "onCreate" in options ? options.onCreate : undefined;
435
439
  const Group = RegisteredSchemas["Group"];
436
-
437
440
  if (!options) {
438
- return { owner: Group.create(), uniqueness: undefined };
441
+ const owner = Group.create();
442
+ onCreate?.(owner);
443
+ return { owner, uniqueness: undefined };
439
444
  }
440
445
 
441
446
  if (TypeSym in options) {
442
447
  if (options[TypeSym] === "Account") {
443
- return { owner: accountOrGroupToGroup(options), uniqueness: undefined };
448
+ const owner = accountOrGroupToGroup(options);
449
+ onCreate?.(owner);
450
+ return { owner, uniqueness: undefined };
444
451
  } else if (options[TypeSym] === "Group") {
452
+ onCreate?.(options);
445
453
  return { owner: options, uniqueness: undefined };
446
454
  }
447
455
  }
@@ -450,10 +458,14 @@ export function parseCoValueCreateOptions(
450
458
  ? { uniqueness: options.unique }
451
459
  : undefined;
452
460
 
461
+ const owner = options.owner
462
+ ? accountOrGroupToGroup(options.owner)
463
+ : Group.create();
464
+
465
+ onCreate?.(owner);
466
+
453
467
  const opts = {
454
- owner: options.owner
455
- ? accountOrGroupToGroup(options.owner)
456
- : Group.create(),
468
+ owner,
457
469
  uniqueness,
458
470
  };
459
471
  return opts;
@@ -66,6 +66,7 @@ export {
66
66
  createUnloadedCoValue,
67
67
  unstable_loadUnique,
68
68
  getUnloadedCoValueWithoutId,
69
+ setDefaultSchemaPermissions,
69
70
  } from "./internal.js";
70
71
 
71
72
  export {
@@ -100,7 +101,8 @@ export {
100
101
  createJazzContextFromExistingCredentials,
101
102
  createJazzContextForNewAccount,
102
103
  createJazzContext,
103
- randomSessionProvider,
104
+ SessionProvider,
105
+ MockSessionProvider,
104
106
  type AuthResult,
105
107
  type Credentials,
106
108
  type JazzContextWithAccount,