jazz-tools 0.18.15 → 0.18.17
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.
- package/.svelte-kit/__package__/media/image.svelte +104 -98
- package/.svelte-kit/__package__/media/image.svelte.d.ts.map +1 -1
- package/.svelte-kit/__package__/tests/media/image.svelte.test.js +16 -2
- package/.turbo/turbo-build.log +48 -38
- package/CHANGELOG.md +20 -0
- package/dist/better-auth/database-adapter/index.d.ts +50 -0
- package/dist/better-auth/database-adapter/index.d.ts.map +1 -0
- package/dist/better-auth/database-adapter/index.js +920 -0
- package/dist/better-auth/database-adapter/index.js.map +1 -0
- package/dist/better-auth/database-adapter/repository/account.d.ts +24 -0
- package/dist/better-auth/database-adapter/repository/account.d.ts.map +1 -0
- package/dist/better-auth/database-adapter/repository/generic.d.ts +45 -0
- package/dist/better-auth/database-adapter/repository/generic.d.ts.map +1 -0
- package/dist/better-auth/database-adapter/repository/index.d.ts +6 -0
- package/dist/better-auth/database-adapter/repository/index.d.ts.map +1 -0
- package/dist/better-auth/database-adapter/repository/session.d.ts +29 -0
- package/dist/better-auth/database-adapter/repository/session.d.ts.map +1 -0
- package/dist/better-auth/database-adapter/repository/user.d.ts +30 -0
- package/dist/better-auth/database-adapter/repository/user.d.ts.map +1 -0
- package/dist/better-auth/database-adapter/repository/verification.d.ts +18 -0
- package/dist/better-auth/database-adapter/repository/verification.d.ts.map +1 -0
- package/dist/better-auth/database-adapter/schema.d.ts +27 -0
- package/dist/better-auth/database-adapter/schema.d.ts.map +1 -0
- package/dist/better-auth/database-adapter/tests/index.test.d.ts +2 -0
- package/dist/better-auth/database-adapter/tests/index.test.d.ts.map +1 -0
- package/dist/better-auth/database-adapter/tests/repository/account.test.d.ts +2 -0
- package/dist/better-auth/database-adapter/tests/repository/account.test.d.ts.map +1 -0
- package/dist/better-auth/database-adapter/tests/repository/generic.test.d.ts +2 -0
- package/dist/better-auth/database-adapter/tests/repository/generic.test.d.ts.map +1 -0
- package/dist/better-auth/database-adapter/tests/repository/session.test.d.ts +2 -0
- package/dist/better-auth/database-adapter/tests/repository/session.test.d.ts.map +1 -0
- package/dist/better-auth/database-adapter/tests/repository/user.test.d.ts +2 -0
- package/dist/better-auth/database-adapter/tests/repository/user.test.d.ts.map +1 -0
- package/dist/better-auth/database-adapter/tests/repository/verification.test.d.ts +2 -0
- package/dist/better-auth/database-adapter/tests/repository/verification.test.d.ts.map +1 -0
- package/dist/better-auth/database-adapter/tests/sync-utils.d.ts +16 -0
- package/dist/better-auth/database-adapter/tests/sync-utils.d.ts.map +1 -0
- package/dist/better-auth/database-adapter/tests/utils.test.d.ts +2 -0
- package/dist/better-auth/database-adapter/tests/utils.test.d.ts.map +1 -0
- package/dist/better-auth/database-adapter/utils.d.ts +16 -0
- package/dist/better-auth/database-adapter/utils.d.ts.map +1 -0
- package/dist/{chunk-GRN6OAUX.js → chunk-OTWWOZMB.js} +73 -4
- package/dist/chunk-OTWWOZMB.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/react/index.js +2 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/media/image.d.ts.map +1 -1
- package/dist/react-native-core/index.js +3 -1
- package/dist/react-native-core/index.js.map +1 -1
- package/dist/react-native-core/media/image.d.ts.map +1 -1
- package/dist/svelte/media/image.svelte +104 -98
- package/dist/svelte/media/image.svelte.d.ts.map +1 -1
- package/dist/svelte/tests/media/image.svelte.test.js +16 -2
- package/dist/testing.js +1 -1
- package/dist/tools/implementation/refs.d.ts +1 -1
- package/dist/tools/implementation/refs.d.ts.map +1 -1
- package/dist/tools/subscribe/CoValueCoreSubscription.d.ts +4 -0
- package/dist/tools/subscribe/CoValueCoreSubscription.d.ts.map +1 -1
- package/dist/tools/subscribe/SubscriptionScope.d.ts +7 -0
- package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
- package/dist/tools/subscribe/index.d.ts.map +1 -1
- package/jazz-tools-0.18.6.tgz +0 -0
- package/package.json +10 -5
- package/src/better-auth/database-adapter/index.ts +228 -0
- package/src/better-auth/database-adapter/repository/account.ts +131 -0
- package/src/better-auth/database-adapter/repository/generic.ts +297 -0
- package/src/better-auth/database-adapter/repository/index.ts +5 -0
- package/src/better-auth/database-adapter/repository/session.ts +190 -0
- package/src/better-auth/database-adapter/repository/user.ts +158 -0
- package/src/better-auth/database-adapter/repository/verification.ts +37 -0
- package/src/better-auth/database-adapter/schema.ts +222 -0
- package/src/better-auth/database-adapter/tests/index.test.ts +690 -0
- package/src/better-auth/database-adapter/tests/repository/account.test.ts +149 -0
- package/src/better-auth/database-adapter/tests/repository/generic.test.ts +183 -0
- package/src/better-auth/database-adapter/tests/repository/session.test.ts +419 -0
- package/src/better-auth/database-adapter/tests/repository/user.test.ts +673 -0
- package/src/better-auth/database-adapter/tests/repository/verification.test.ts +101 -0
- package/src/better-auth/database-adapter/tests/sync-utils.ts +127 -0
- package/src/better-auth/database-adapter/tests/utils.test.ts +787 -0
- package/src/better-auth/database-adapter/utils.ts +178 -0
- package/src/react/media/image.tsx +2 -0
- package/src/react/tests/media/image.test.tsx +20 -2
- package/src/react-native-core/media/image.tsx +4 -1
- package/src/svelte/media/image.svelte +104 -98
- package/src/svelte/tests/media/image.svelte.test.ts +18 -2
- package/src/tools/implementation/refs.ts +27 -3
- package/src/tools/subscribe/CoValueCoreSubscription.ts +14 -0
- package/src/tools/subscribe/SubscriptionScope.ts +62 -1
- package/src/tools/subscribe/index.ts +8 -0
- package/tsup.config.ts +7 -0
- package/dist/chunk-GRN6OAUX.js.map +0 -1
@@ -0,0 +1,158 @@
|
|
1
|
+
import { CleanedWhere } from "better-auth/adapters";
|
2
|
+
import { co, z } from "jazz-tools";
|
3
|
+
import { JazzRepository } from "./generic";
|
4
|
+
import { isWhereBySingleField } from "../utils";
|
5
|
+
import type { TableItem } from "../schema";
|
6
|
+
|
7
|
+
const EmailIndex = co.map({ user: z.string().nullable() });
|
8
|
+
|
9
|
+
export class UserRepository extends JazzRepository {
|
10
|
+
/**
|
11
|
+
* Custom logic:
|
12
|
+
* - sessions are stored inside the user object
|
13
|
+
* - keep sync email index
|
14
|
+
*/
|
15
|
+
async create(
|
16
|
+
model: string,
|
17
|
+
data: Record<string, any>,
|
18
|
+
uniqueId?: string,
|
19
|
+
): Promise<TableItem> {
|
20
|
+
const SessionListSchema = this.databaseSchema.shape.tables.shape.session;
|
21
|
+
|
22
|
+
if (!SessionListSchema) {
|
23
|
+
throw new Error("Session list schema not found");
|
24
|
+
}
|
25
|
+
|
26
|
+
const userEmail = data[this.getEmailProperty()] as string;
|
27
|
+
|
28
|
+
const emailIndex = await this.loadEmailIndex(userEmail);
|
29
|
+
|
30
|
+
if (emailIndex?.user) {
|
31
|
+
throw new Error("Email already exists");
|
32
|
+
}
|
33
|
+
|
34
|
+
const user = await super.create(model, data, uniqueId);
|
35
|
+
|
36
|
+
await this.updateEmailIndex(userEmail, user.$jazz.id);
|
37
|
+
|
38
|
+
user.$jazz.set(
|
39
|
+
"sessions",
|
40
|
+
co.list(SessionListSchema).create([], user.$jazz.owner),
|
41
|
+
);
|
42
|
+
|
43
|
+
return user;
|
44
|
+
}
|
45
|
+
|
46
|
+
/**
|
47
|
+
* Custom logic:
|
48
|
+
* - if the email is in the where clause, find by email
|
49
|
+
*/
|
50
|
+
async findMany(
|
51
|
+
model: string,
|
52
|
+
where: CleanedWhere[] | undefined,
|
53
|
+
limit?: number,
|
54
|
+
sortBy?: { field: string; direction: "asc" | "desc" },
|
55
|
+
offset?: number,
|
56
|
+
): Promise<TableItem[]> {
|
57
|
+
if (isWhereBySingleField("email", where)) {
|
58
|
+
return this.findByEmail(where[0].value as string);
|
59
|
+
}
|
60
|
+
|
61
|
+
return super.findMany(model, where, limit, sortBy, offset);
|
62
|
+
}
|
63
|
+
|
64
|
+
private getEmailProperty(): string {
|
65
|
+
return this.betterAuthSchema.user?.fields.email?.fieldName || "email";
|
66
|
+
}
|
67
|
+
|
68
|
+
private async findByEmail(email: string): Promise<TableItem[]> {
|
69
|
+
const emailIndex = await this.loadEmailIndex(email);
|
70
|
+
|
71
|
+
const user = emailIndex?.user;
|
72
|
+
|
73
|
+
if (!user) {
|
74
|
+
return [];
|
75
|
+
}
|
76
|
+
|
77
|
+
return this.findById("user", [
|
78
|
+
{ field: "id", operator: "eq", value: user, connector: "AND" },
|
79
|
+
]).then((user) => (user ? [user] : []));
|
80
|
+
}
|
81
|
+
|
82
|
+
/**
|
83
|
+
* Custom logic:
|
84
|
+
* - if the email is changed, update the email index
|
85
|
+
*/
|
86
|
+
async update(
|
87
|
+
model: string,
|
88
|
+
where: CleanedWhere[],
|
89
|
+
update: Record<string, any>,
|
90
|
+
): Promise<TableItem[]> {
|
91
|
+
const nodes = await this.findMany(model, where);
|
92
|
+
if (nodes.length === 0) {
|
93
|
+
return [];
|
94
|
+
}
|
95
|
+
|
96
|
+
const newEmail = (update as Record<string, any>)[this.getEmailProperty()] as
|
97
|
+
| string
|
98
|
+
| undefined;
|
99
|
+
|
100
|
+
for (const node of nodes) {
|
101
|
+
const oldEmail = node.$jazz.raw.get(this.getEmailProperty()) as
|
102
|
+
| string
|
103
|
+
| undefined;
|
104
|
+
for (const [key, value] of Object.entries(
|
105
|
+
update as Record<string, any>,
|
106
|
+
)) {
|
107
|
+
node.$jazz.set(key, value);
|
108
|
+
}
|
109
|
+
|
110
|
+
// if the email is changed, update the email index
|
111
|
+
if (
|
112
|
+
oldEmail !== newEmail &&
|
113
|
+
oldEmail !== undefined &&
|
114
|
+
newEmail !== undefined
|
115
|
+
) {
|
116
|
+
await this.updateEmailIndex(oldEmail, null);
|
117
|
+
await this.updateEmailIndex(newEmail, node.$jazz.id);
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
return nodes;
|
122
|
+
}
|
123
|
+
|
124
|
+
async deleteValue(model: string, where: CleanedWhere[]): Promise<number> {
|
125
|
+
const nodes = await this.findMany(model, where);
|
126
|
+
|
127
|
+
const deleted = await super.deleteValue(model, where);
|
128
|
+
|
129
|
+
for (const node of nodes) {
|
130
|
+
const email = node.$jazz.raw.get(this.getEmailProperty()) as
|
131
|
+
| string
|
132
|
+
| undefined;
|
133
|
+
if (email) {
|
134
|
+
await this.updateEmailIndex(email, null);
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
return deleted;
|
139
|
+
}
|
140
|
+
|
141
|
+
private async loadEmailIndex(email: string) {
|
142
|
+
const emailIndex = await EmailIndex.loadUnique(email, this.owner.$jazz.id, {
|
143
|
+
loadAs: this.worker,
|
144
|
+
});
|
145
|
+
|
146
|
+
return emailIndex;
|
147
|
+
}
|
148
|
+
|
149
|
+
private async updateEmailIndex(email: string, userId: string | null) {
|
150
|
+
await EmailIndex.upsertUnique({
|
151
|
+
value: {
|
152
|
+
user: userId,
|
153
|
+
},
|
154
|
+
unique: email,
|
155
|
+
owner: this.owner,
|
156
|
+
});
|
157
|
+
}
|
158
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import { CleanedWhere } from "better-auth/adapters";
|
2
|
+
import { JazzRepository } from "./generic";
|
3
|
+
import { isWhereBySingleField } from "../utils";
|
4
|
+
import type { TableItem } from "../schema";
|
5
|
+
|
6
|
+
export class VerificationRepository extends JazzRepository {
|
7
|
+
/**
|
8
|
+
* Custom logic: property identifier is used as uniqueId
|
9
|
+
*/
|
10
|
+
async create(
|
11
|
+
model: string,
|
12
|
+
data: Record<string, any>,
|
13
|
+
uniqueId?: string,
|
14
|
+
): Promise<TableItem> {
|
15
|
+
return super.create(model, data, data["identifier"]);
|
16
|
+
}
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Custom logic: property identifier is used as uniqueId
|
20
|
+
* If we look for identifier, we use findByUnique instead of findMany
|
21
|
+
*/
|
22
|
+
async findMany(
|
23
|
+
model: string,
|
24
|
+
where: CleanedWhere[] | undefined,
|
25
|
+
limit?: number,
|
26
|
+
sortBy?: { field: string; direction: "asc" | "desc" },
|
27
|
+
offset?: number,
|
28
|
+
): Promise<TableItem[]> {
|
29
|
+
if (isWhereBySingleField("identifier", where)) {
|
30
|
+
return this.findByUnique(model, where).then((node) =>
|
31
|
+
node ? [node] : [],
|
32
|
+
);
|
33
|
+
}
|
34
|
+
|
35
|
+
return super.findMany(model, where, limit, sortBy, offset);
|
36
|
+
}
|
37
|
+
}
|
@@ -0,0 +1,222 @@
|
|
1
|
+
import { BetterAuthDbSchema, FieldAttribute } from "better-auth/db";
|
2
|
+
import { Group, co, z } from "jazz-tools";
|
3
|
+
|
4
|
+
type TableRow = co.Map<any>;
|
5
|
+
export type TableItem = co.loaded<TableRow>;
|
6
|
+
|
7
|
+
type Table = co.List<TableRow>;
|
8
|
+
export type Database = co.Map<{
|
9
|
+
group: typeof Group;
|
10
|
+
tables: co.Map<{
|
11
|
+
[key: string]: Table;
|
12
|
+
}>;
|
13
|
+
}>;
|
14
|
+
|
15
|
+
type WorkerAccount = co.Account<{
|
16
|
+
profile: co.Profile;
|
17
|
+
root: co.Map<any>;
|
18
|
+
}>;
|
19
|
+
|
20
|
+
type JazzSchema = {
|
21
|
+
WorkerAccount: WorkerAccount;
|
22
|
+
DatabaseRoot: Database;
|
23
|
+
betterAuthSchema: BetterAuthDbSchema;
|
24
|
+
loadDatabase: (
|
25
|
+
account: co.loaded<co.Account>,
|
26
|
+
options?: Parameters<Database["loadUnique"]>[2],
|
27
|
+
) => Promise<co.loaded<Database, { group: true }>>;
|
28
|
+
};
|
29
|
+
|
30
|
+
const DATABASE_ROOT_ID = "better-auth-root";
|
31
|
+
|
32
|
+
export function createJazzSchema(schema: BetterAuthDbSchema): JazzSchema {
|
33
|
+
const tablesSchema = generateSchemaFromBetterAuthSchema(schema);
|
34
|
+
|
35
|
+
const DatabaseRoot: Database = co.map({
|
36
|
+
group: Group,
|
37
|
+
tables: co.map(tablesSchema),
|
38
|
+
});
|
39
|
+
|
40
|
+
const WorkerAccount: WorkerAccount = co
|
41
|
+
.account({
|
42
|
+
profile: co.profile(),
|
43
|
+
root: co.map({}),
|
44
|
+
})
|
45
|
+
.withMigration(async (account) => {
|
46
|
+
const dbRoot = await DatabaseRoot.loadUnique(
|
47
|
+
DATABASE_ROOT_ID,
|
48
|
+
account.$jazz.id,
|
49
|
+
{
|
50
|
+
resolve: {
|
51
|
+
group: true,
|
52
|
+
tables: true,
|
53
|
+
},
|
54
|
+
loadAs: account,
|
55
|
+
},
|
56
|
+
);
|
57
|
+
|
58
|
+
if (!dbRoot) {
|
59
|
+
// Create a group for the first time
|
60
|
+
// it will be the owner of the all tables and data
|
61
|
+
const adminGroup = Group.create({ owner: account });
|
62
|
+
await DatabaseRoot.upsertUnique({
|
63
|
+
value: {
|
64
|
+
group: adminGroup,
|
65
|
+
// create empty tables for each model
|
66
|
+
tables: co
|
67
|
+
.map(tablesSchema)
|
68
|
+
.create(
|
69
|
+
Object.fromEntries(
|
70
|
+
Object.entries(tablesSchema).map(([key, value]) => [
|
71
|
+
key,
|
72
|
+
value.create([], adminGroup),
|
73
|
+
]),
|
74
|
+
),
|
75
|
+
adminGroup,
|
76
|
+
),
|
77
|
+
},
|
78
|
+
unique: DATABASE_ROOT_ID,
|
79
|
+
owner: account,
|
80
|
+
});
|
81
|
+
} else {
|
82
|
+
// partial migrations
|
83
|
+
for (const [key, value] of Object.entries(
|
84
|
+
DatabaseRoot.shape.tables.shape,
|
85
|
+
)) {
|
86
|
+
if (dbRoot.tables[key] === undefined) {
|
87
|
+
dbRoot.tables.$jazz.set(key, value.create([], dbRoot.group));
|
88
|
+
}
|
89
|
+
}
|
90
|
+
}
|
91
|
+
});
|
92
|
+
|
93
|
+
return {
|
94
|
+
WorkerAccount,
|
95
|
+
DatabaseRoot,
|
96
|
+
betterAuthSchema: schema,
|
97
|
+
async loadDatabase(account, options) {
|
98
|
+
if (
|
99
|
+
options?.resolve === false ||
|
100
|
+
(typeof options?.resolve === "object" &&
|
101
|
+
options?.resolve.group !== true)
|
102
|
+
) {
|
103
|
+
throw new Error("Group is required to load the database");
|
104
|
+
}
|
105
|
+
|
106
|
+
const db = (await DatabaseRoot.loadUnique(
|
107
|
+
DATABASE_ROOT_ID,
|
108
|
+
account.$jazz.id,
|
109
|
+
{
|
110
|
+
resolve: {
|
111
|
+
group: true,
|
112
|
+
tables: true,
|
113
|
+
},
|
114
|
+
loadAs: account,
|
115
|
+
...options,
|
116
|
+
},
|
117
|
+
)) as co.loaded<Database, { group: true }>;
|
118
|
+
|
119
|
+
if (!db) {
|
120
|
+
throw new Error("Database not found");
|
121
|
+
}
|
122
|
+
|
123
|
+
return db;
|
124
|
+
},
|
125
|
+
};
|
126
|
+
}
|
127
|
+
|
128
|
+
type ZodPrimitiveSchema =
|
129
|
+
| z.z.ZodString
|
130
|
+
| z.z.ZodNumber
|
131
|
+
| z.z.ZodBoolean
|
132
|
+
| z.z.ZodNull
|
133
|
+
| z.z.ZodDate
|
134
|
+
| z.z.ZodLiteral;
|
135
|
+
type ZodOptionalPrimitiveSchema = z.z.ZodOptional<ZodPrimitiveSchema>;
|
136
|
+
|
137
|
+
function generateSchemaFromBetterAuthSchema(schema: BetterAuthDbSchema) {
|
138
|
+
const tablesSchema: Record<string, Table> = {};
|
139
|
+
|
140
|
+
for (const [key, value] of Object.entries(schema)) {
|
141
|
+
const modelShape: Record<
|
142
|
+
string,
|
143
|
+
ZodPrimitiveSchema | ZodOptionalPrimitiveSchema
|
144
|
+
> = {
|
145
|
+
_deleted: z.boolean(),
|
146
|
+
};
|
147
|
+
|
148
|
+
for (const [fieldName, field] of Object.entries(value.fields)) {
|
149
|
+
modelShape[field.fieldName || fieldName] = convertFieldToCoValue(field);
|
150
|
+
}
|
151
|
+
|
152
|
+
const coMap = co.map(modelShape);
|
153
|
+
tablesSchema[key] = co.list(coMap);
|
154
|
+
}
|
155
|
+
|
156
|
+
if (tablesSchema["user"] && tablesSchema["session"]) {
|
157
|
+
tablesSchema["user"] = co.list(
|
158
|
+
co
|
159
|
+
.map({
|
160
|
+
...tablesSchema["user"].element.shape,
|
161
|
+
sessions: tablesSchema["session"],
|
162
|
+
})
|
163
|
+
.withMigration((user) => {
|
164
|
+
if (user.sessions === undefined) {
|
165
|
+
user.$jazz.set(
|
166
|
+
"sessions",
|
167
|
+
tablesSchema["session"]!.create([], user.$jazz.owner),
|
168
|
+
);
|
169
|
+
}
|
170
|
+
}),
|
171
|
+
);
|
172
|
+
} else {
|
173
|
+
throw new Error(
|
174
|
+
"Cannot find user and session tables, sessions will not be persisted",
|
175
|
+
);
|
176
|
+
}
|
177
|
+
|
178
|
+
return tablesSchema;
|
179
|
+
}
|
180
|
+
|
181
|
+
function convertFieldToCoValue(field: FieldAttribute) {
|
182
|
+
let zodType: ZodPrimitiveSchema | ZodOptionalPrimitiveSchema;
|
183
|
+
|
184
|
+
switch (field.type) {
|
185
|
+
case "string":
|
186
|
+
zodType = z.string();
|
187
|
+
break;
|
188
|
+
case "number":
|
189
|
+
zodType = z.number();
|
190
|
+
break;
|
191
|
+
case "boolean":
|
192
|
+
zodType = z.boolean();
|
193
|
+
break;
|
194
|
+
case "date":
|
195
|
+
zodType = z.date();
|
196
|
+
break;
|
197
|
+
default:
|
198
|
+
throw new Error(`Unsupported field type: ${field.type}`);
|
199
|
+
}
|
200
|
+
|
201
|
+
if (field.required === false) {
|
202
|
+
zodType = zodType.optional();
|
203
|
+
}
|
204
|
+
|
205
|
+
return zodType;
|
206
|
+
}
|
207
|
+
|
208
|
+
export function tableItem2Record(
|
209
|
+
tableItem: TableItem | null | undefined,
|
210
|
+
): Record<string, any> | null | undefined {
|
211
|
+
if (!tableItem) {
|
212
|
+
return tableItem;
|
213
|
+
}
|
214
|
+
|
215
|
+
// tableItem.toJSON() transforms Date objects to ISO strings
|
216
|
+
// by returning ...rest, we keep the objects
|
217
|
+
const { $jazz, ...rest } = tableItem;
|
218
|
+
return {
|
219
|
+
...rest,
|
220
|
+
id: $jazz.id,
|
221
|
+
};
|
222
|
+
}
|