jazz-tools 0.18.0 → 0.18.2

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 (46) hide show
  1. package/.turbo/turbo-build.log +44 -30
  2. package/CHANGELOG.md +20 -0
  3. package/dist/better-auth/auth/client.d.ts +29 -0
  4. package/dist/better-auth/auth/client.d.ts.map +1 -0
  5. package/dist/better-auth/auth/client.js +127 -0
  6. package/dist/better-auth/auth/client.js.map +1 -0
  7. package/dist/better-auth/auth/react.d.ts +2170 -0
  8. package/dist/better-auth/auth/react.d.ts.map +1 -0
  9. package/dist/better-auth/auth/react.js +40 -0
  10. package/dist/better-auth/auth/react.js.map +1 -0
  11. package/dist/better-auth/auth/server.d.ts +14 -0
  12. package/dist/better-auth/auth/server.d.ts.map +1 -0
  13. package/dist/better-auth/auth/server.js +198 -0
  14. package/dist/better-auth/auth/server.js.map +1 -0
  15. package/dist/better-auth/auth/tests/client.test.d.ts +2 -0
  16. package/dist/better-auth/auth/tests/client.test.d.ts.map +1 -0
  17. package/dist/better-auth/auth/tests/server.test.d.ts +2 -0
  18. package/dist/better-auth/auth/tests/server.test.d.ts.map +1 -0
  19. package/dist/{chunk-HJ3GTGY7.js → chunk-IERUTUXB.js} +18 -1
  20. package/dist/chunk-IERUTUXB.js.map +1 -0
  21. package/dist/index.js +1 -1
  22. package/dist/react-core/index.js +17 -0
  23. package/dist/react-core/index.js.map +1 -1
  24. package/dist/testing.js +1 -1
  25. package/dist/tools/coValues/account.d.ts +1 -0
  26. package/dist/tools/coValues/account.d.ts.map +1 -1
  27. package/dist/tools/coValues/coMap.d.ts +10 -0
  28. package/dist/tools/coValues/coMap.d.ts.map +1 -1
  29. package/dist/tools/implementation/zodSchema/zodCo.d.ts +1 -1
  30. package/dist/tools/testing.d.ts.map +1 -1
  31. package/package.json +23 -4
  32. package/src/better-auth/auth/client.ts +169 -0
  33. package/src/better-auth/auth/react.tsx +105 -0
  34. package/src/better-auth/auth/server.ts +250 -0
  35. package/src/better-auth/auth/tests/client.test.ts +249 -0
  36. package/src/better-auth/auth/tests/server.test.ts +226 -0
  37. package/src/tools/coValues/account.ts +5 -0
  38. package/src/tools/coValues/coMap.ts +14 -0
  39. package/src/tools/implementation/zodSchema/zodCo.ts +1 -1
  40. package/src/tools/tests/ContextManager.test.ts +2 -2
  41. package/src/tools/tests/account.test.ts +51 -0
  42. package/src/tools/tests/coMap.test.ts +99 -0
  43. package/src/tools/tests/patterns/notifications.test.ts +1 -1
  44. package/src/tools/tests/testing.test.ts +2 -2
  45. package/tsup.config.ts +9 -0
  46. package/dist/chunk-HJ3GTGY7.js.map +0 -1
@@ -0,0 +1,226 @@
1
+ import { betterAuth } from "better-auth";
2
+ import { memoryAdapter } from "better-auth/adapters/memory";
3
+ import { beforeEach, describe, expect, it, vi, type Mock } from "vitest";
4
+ import { jazzPlugin } from "../server.js";
5
+
6
+ describe("Better Auth - Signup and Login Tests", () => {
7
+ let auth: ReturnType<typeof betterAuth>;
8
+ let accountCreationSpy: Mock;
9
+ let verificationCreationSpy: Mock;
10
+
11
+ beforeEach(() => {
12
+ accountCreationSpy = vi.fn();
13
+ verificationCreationSpy = vi.fn();
14
+
15
+ // Create auth instance with in-memory database
16
+ auth = betterAuth({
17
+ database: memoryAdapter({
18
+ user: [],
19
+ session: [],
20
+ verification: [],
21
+ account: [],
22
+ }),
23
+ plugins: [jazzPlugin()],
24
+ emailAndPassword: {
25
+ enabled: true,
26
+ requireEmailVerification: false, // Disable for testing
27
+ },
28
+ socialProviders: {
29
+ github: {
30
+ clientId: "123",
31
+ clientSecret: "123",
32
+ },
33
+ },
34
+ databaseHooks: {
35
+ user: {
36
+ create: {
37
+ after: accountCreationSpy,
38
+ },
39
+ },
40
+ verification: {
41
+ create: {
42
+ after: verificationCreationSpy,
43
+ },
44
+ },
45
+ },
46
+ session: {
47
+ expiresIn: 60 * 60 * 24 * 7, // 7 days
48
+ },
49
+ });
50
+ });
51
+
52
+ describe("User Registration (Signup)", () => {
53
+ it("should successfully register a new user with email and password", async () => {
54
+ const userData = {
55
+ name: "test",
56
+ email: "test@example.com",
57
+ password: "securePassword123",
58
+ };
59
+
60
+ const jazzAuth = {
61
+ accountID: "123",
62
+ secretSeed: [1, 2, 3],
63
+ accountSecret: "123",
64
+ provider: "better-auth",
65
+ };
66
+
67
+ const result = await auth.api.signUpEmail({
68
+ body: userData,
69
+ headers: {
70
+ "x-jazz-auth": JSON.stringify(jazzAuth),
71
+ },
72
+ });
73
+
74
+ expect(result).toBeDefined();
75
+ expect(result).toMatchObject({
76
+ user: {
77
+ id: expect.any(String),
78
+ email: userData.email,
79
+ name: userData.name,
80
+ image: undefined,
81
+ emailVerified: false,
82
+ createdAt: expect.any(Date),
83
+ updatedAt: expect.any(Date),
84
+ },
85
+ jazzAuth: jazzAuth,
86
+ });
87
+
88
+ const res = await (await auth.$context).adapter.findOne({
89
+ model: "user",
90
+ where: [
91
+ {
92
+ field: "id",
93
+ value: result.user.id,
94
+ },
95
+ ],
96
+ });
97
+
98
+ expect(res).toMatchObject({
99
+ id: result.user.id,
100
+ accountID: "123",
101
+ encryptedCredentials: expect.any(String),
102
+ });
103
+ });
104
+
105
+ it("should fail to register user without account ID", async () => {
106
+ const userData = {
107
+ name: "test",
108
+ email: "email@email.it",
109
+ password: "securePassword123",
110
+ };
111
+
112
+ await expect(
113
+ auth.api.signUpEmail({
114
+ body: userData,
115
+ }),
116
+ ).rejects.toThrow("JazzAuth is required");
117
+
118
+ expect(accountCreationSpy).toHaveBeenCalledTimes(0);
119
+ });
120
+
121
+ it("should have AccountID in the registration hook", async () => {
122
+ const userData = {
123
+ name: "test",
124
+ email: "email@email.it",
125
+ password: "securePassword123",
126
+ };
127
+
128
+ const jazzAuth = {
129
+ accountID: "123",
130
+ secretSeed: [1, 2, 3],
131
+ accountSecret: "123",
132
+ provider: "better-auth",
133
+ };
134
+
135
+ await auth.api.signUpEmail({
136
+ body: userData,
137
+ headers: {
138
+ "x-jazz-auth": JSON.stringify(jazzAuth),
139
+ },
140
+ });
141
+
142
+ expect(accountCreationSpy).toHaveBeenCalledTimes(1);
143
+ expect(accountCreationSpy).toHaveBeenCalledWith(
144
+ // user
145
+ expect.objectContaining({ accountID: "123" }),
146
+ // context
147
+ expect.any(Object),
148
+ );
149
+ });
150
+ });
151
+
152
+ describe("User login (Signin)", () => {
153
+ it("should successfully login a new user with email and password", async () => {
154
+ const userData = {
155
+ name: "test",
156
+ email: "test@example.com",
157
+ password: "securePassword123",
158
+ };
159
+
160
+ const jazzAuth = {
161
+ accountID: "123",
162
+ secretSeed: [1, 2, 3],
163
+ accountSecret: "123",
164
+ provider: "better-auth",
165
+ };
166
+
167
+ await auth.api.signUpEmail({
168
+ body: userData,
169
+ headers: {
170
+ "x-jazz-auth": JSON.stringify(jazzAuth),
171
+ },
172
+ });
173
+
174
+ const result = await auth.api.signInEmail({
175
+ body: {
176
+ email: userData.email,
177
+ password: userData.password,
178
+ },
179
+ });
180
+
181
+ expect(result).toBeDefined();
182
+ expect(result).toMatchObject({
183
+ user: {
184
+ id: expect.any(String),
185
+ email: userData.email,
186
+ name: userData.name,
187
+ image: undefined,
188
+ emailVerified: false,
189
+ createdAt: expect.any(Date),
190
+ updatedAt: expect.any(Date),
191
+ },
192
+ jazzAuth: jazzAuth,
193
+ });
194
+ });
195
+ });
196
+
197
+ describe("Social Login", () => {
198
+ it("should store jazzAuth in verification table when using social provider", async () => {
199
+ await auth.api.signInSocial({
200
+ body: {
201
+ provider: "github",
202
+ callbackURL: "http://localhost:3000/api/auth/sign-in/social/callback",
203
+ },
204
+ headers: {
205
+ "x-jazz-auth": JSON.stringify({
206
+ accountID: "123",
207
+ secretSeed: [1, 2, 3],
208
+ accountSecret: "123",
209
+ }),
210
+ },
211
+ });
212
+
213
+ expect(verificationCreationSpy).toHaveBeenCalledTimes(1);
214
+ expect(verificationCreationSpy).toHaveBeenCalledWith(
215
+ expect.objectContaining({
216
+ value: expect.stringContaining('"accountID":"123"'),
217
+ }),
218
+ expect.any(Object),
219
+ );
220
+ });
221
+
222
+ it.todo(
223
+ "should create a new account with jazz auth when using social provider",
224
+ );
225
+ });
226
+ });
@@ -418,6 +418,11 @@ class AccountJazzApi<A extends Account> extends CoValueJazzApi<A> {
418
418
  }
419
419
  }
420
420
 
421
+ has(key: "root" | "profile"): boolean {
422
+ const entry = this.raw.getRaw(key);
423
+ return entry?.change !== undefined && entry.change.op !== "del";
424
+ }
425
+
421
426
  /**
422
427
  * Get the descriptor for a given key
423
428
  * @internal
@@ -571,6 +571,20 @@ class CoMapJazzApi<M extends CoMap> extends CoValueJazzApi<M> {
571
571
  return getCoValueOwner(this.coMap);
572
572
  }
573
573
 
574
+ /**
575
+ * Check if a key is defined in the CoMap.
576
+ *
577
+ * This check does not load the referenced value or validate permissions.
578
+ *
579
+ * @param key The key to check
580
+ * @returns True if the key is defined, false otherwise
581
+ * @category Content
582
+ */
583
+ has(key: CoKeys<M>): boolean {
584
+ const entry = this.raw.getRaw(key);
585
+ return entry?.change !== undefined && entry.change.op !== "del";
586
+ }
587
+
574
588
  /**
575
589
  * Set a value on the CoMap
576
590
  *
@@ -101,7 +101,7 @@ export const coMapDefiner = <Shape extends z.core.$ZodLooseShape>(
101
101
  * }),
102
102
  * }).withMigration(async (account) => {
103
103
  * // Migration logic for existing accounts
104
- * if (account.profile === undefined) {
104
+ * if (!account.$jazz.has("profile")) {
105
105
  * const group = Group.create();
106
106
  * account.$jazz.set("profile", co.profile().create(
107
107
  * { name: getRandomUsername() },
@@ -372,7 +372,7 @@ describe("ContextManager", () => {
372
372
  profile: co.profile(),
373
373
  })
374
374
  .withMigration(async (account) => {
375
- if (account.root === undefined) {
375
+ if (!account.$jazz.has("root")) {
376
376
  account.$jazz.set(
377
377
  "root",
378
378
  AccountRoot.create({
@@ -429,7 +429,7 @@ describe("ContextManager", () => {
429
429
  profile: co.profile(),
430
430
  })
431
431
  .withMigration(async (account) => {
432
- if (account.root === undefined) {
432
+ if (!account.$jazz.has("root")) {
433
433
  account.$jazz.set(
434
434
  "root",
435
435
  AccountRoot.create({
@@ -342,3 +342,54 @@ describe("root and profile", () => {
342
342
  expect(account.root.name).toBe("test 1");
343
343
  });
344
344
  });
345
+
346
+ describe("account.$jazz.has", () => {
347
+ test("should return true if the key is defined", async () => {
348
+ const account = await createJazzTestAccount({
349
+ creationProps: { name: "John" },
350
+ });
351
+
352
+ expect(account.$jazz.has("profile")).toBe(true);
353
+ expect(account.$jazz.has("root")).toBe(false);
354
+ });
355
+
356
+ test("should work as migration check", async () => {
357
+ const CustomProfile = co.profile({
358
+ name: z.string(),
359
+ email: z.string().optional(),
360
+ });
361
+
362
+ const CustomRoot = co.map({
363
+ settings: z.string(),
364
+ });
365
+
366
+ const CustomAccount = co
367
+ .account({
368
+ profile: CustomProfile,
369
+ root: CustomRoot,
370
+ })
371
+ .withMigration((me, creationProps) => {
372
+ if (!me.$jazz.has("profile")) {
373
+ me.$jazz.set("profile", {
374
+ name: creationProps?.name ?? "Anonymous",
375
+ email: "test@example.com",
376
+ });
377
+ }
378
+
379
+ if (!me.$jazz.has("root")) {
380
+ me.$jazz.set("root", { settings: "default" });
381
+ }
382
+ });
383
+
384
+ const account = await createJazzTestAccount({
385
+ AccountSchema: CustomAccount,
386
+ creationProps: { name: "Custom User" },
387
+ });
388
+
389
+ expect(account.$jazz.has("profile")).toBe(true);
390
+ expect(account.$jazz.has("root")).toBe(true);
391
+
392
+ expect(account.profile.email).toBe("test@example.com");
393
+ expect(account.root.settings).toBe("default");
394
+ });
395
+ });
@@ -744,6 +744,105 @@ describe("CoMap", async () => {
744
744
  });
745
745
  });
746
746
 
747
+ describe("has", () => {
748
+ test("should return true if the key is defined", () => {
749
+ const Person = co.map({
750
+ name: z.string(),
751
+ age: z.number().optional(),
752
+ });
753
+
754
+ const person = Person.create({ name: "John", age: 20 });
755
+
756
+ expect(person.$jazz.has("name")).toBe(true);
757
+ expect(person.$jazz.has("age")).toBe(true);
758
+ });
759
+
760
+ test("should return true if the key was set to undefined", () => {
761
+ const Person = co.map({
762
+ name: z.string(),
763
+ age: z.number().optional(),
764
+ });
765
+
766
+ const person = Person.create({ name: "John" });
767
+
768
+ person.$jazz.set("age", undefined);
769
+
770
+ expect(person.$jazz.has("age")).toBe(true);
771
+ });
772
+
773
+ test("should return false if the key is not defined", () => {
774
+ const Person = co.map({
775
+ name: z.string(),
776
+ age: z.number().optional(),
777
+ });
778
+
779
+ const person = Person.create({ name: "John" });
780
+
781
+ expect(person.$jazz.has("age")).toBe(false);
782
+ });
783
+
784
+ test("should return false if the key was deleted", () => {
785
+ const Person = co.map({
786
+ name: z.string(),
787
+ age: z.number().optional(),
788
+ });
789
+
790
+ const person = Person.create({ name: "John", age: 20 });
791
+
792
+ person.$jazz.delete("age");
793
+
794
+ expect(person.$jazz.has("age")).toBe(false);
795
+ });
796
+
797
+ test("should not load the referenced CoValue", async () => {
798
+ const Person = co.map({
799
+ name: co.plainText(),
800
+ });
801
+
802
+ const { clientAccount, serverAccount } = await setupTwoNodes();
803
+
804
+ const person = Person.create(
805
+ {
806
+ name: "John",
807
+ },
808
+ { owner: Group.create(serverAccount).makePublic() },
809
+ );
810
+
811
+ const loadedPerson = await Person.load(person.$jazz.id, {
812
+ resolve: true,
813
+ loadAs: clientAccount,
814
+ });
815
+
816
+ assert(loadedPerson);
817
+ expect(loadedPerson.$jazz.has("name")).toBe(true);
818
+ expect(loadedPerson.name).toBeNull();
819
+ });
820
+
821
+ test("should return true even if the viewer doesn't have access to the referenced CoValue", async () => {
822
+ const Person = co.map({
823
+ name: co.plainText(),
824
+ });
825
+
826
+ const person = Person.create(
827
+ // UserB has no access to name
828
+ { name: co.plainText().create("John", Group.create()) },
829
+ // UserB has access to person
830
+ { owner: Group.create().makePublic() },
831
+ );
832
+
833
+ const userB = await createJazzTestAccount();
834
+
835
+ const loadedPerson = await Person.load(person.$jazz.id, {
836
+ resolve: true,
837
+ loadAs: userB,
838
+ });
839
+
840
+ assert(loadedPerson);
841
+ expect(loadedPerson.$jazz.has("name")).toBe(true);
842
+ expect(loadedPerson.name).toBeNull();
843
+ });
844
+ });
845
+
747
846
  test("Enum of maps", () => {
748
847
  const ChildA = co.map({
749
848
  type: z.literal("a"),
@@ -17,7 +17,7 @@ const WorkerAccount = co
17
17
  profile: co.profile(),
18
18
  })
19
19
  .withMigration((account) => {
20
- if (account.root === undefined) {
20
+ if (!account.$jazz.has("root")) {
21
21
  account.$jazz.set("root", {
22
22
  notificationQueue: [],
23
23
  });
@@ -35,7 +35,7 @@ describe("Jazz Test Sync", () => {
35
35
  profile: co.profile(),
36
36
  })
37
37
  .withMigration((account) => {
38
- if (account.root === undefined) {
38
+ if (!account.$jazz.has("root")) {
39
39
  account.$jazz.set("root", { value: "ok" });
40
40
  }
41
41
  });
@@ -59,7 +59,7 @@ describe("Jazz Test Sync", () => {
59
59
  profile: co.profile(),
60
60
  })
61
61
  .withMigration((account) => {
62
- if (account.root === undefined) {
62
+ if (!account.$jazz.has("root")) {
63
63
  account.$jazz.set("root", { value: "ok" });
64
64
  }
65
65
  });
package/tsup.config.ts CHANGED
@@ -137,4 +137,13 @@ export default defineConfig([
137
137
  },
138
138
  outDir: "dist/worker",
139
139
  },
140
+ {
141
+ ...cfg,
142
+ entry: {
143
+ client: "src/better-auth/auth/client.ts",
144
+ server: "src/better-auth/auth/server.ts",
145
+ react: "src/better-auth/auth/react.tsx",
146
+ },
147
+ outDir: "dist/better-auth/auth",
148
+ },
140
149
  ]);