jazz-tools 0.18.15 → 0.18.16

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 (58) hide show
  1. package/.turbo/turbo-build.log +45 -35
  2. package/CHANGELOG.md +9 -0
  3. package/dist/better-auth/database-adapter/index.d.ts +50 -0
  4. package/dist/better-auth/database-adapter/index.d.ts.map +1 -0
  5. package/dist/better-auth/database-adapter/index.js +920 -0
  6. package/dist/better-auth/database-adapter/index.js.map +1 -0
  7. package/dist/better-auth/database-adapter/repository/account.d.ts +24 -0
  8. package/dist/better-auth/database-adapter/repository/account.d.ts.map +1 -0
  9. package/dist/better-auth/database-adapter/repository/generic.d.ts +45 -0
  10. package/dist/better-auth/database-adapter/repository/generic.d.ts.map +1 -0
  11. package/dist/better-auth/database-adapter/repository/index.d.ts +6 -0
  12. package/dist/better-auth/database-adapter/repository/index.d.ts.map +1 -0
  13. package/dist/better-auth/database-adapter/repository/session.d.ts +29 -0
  14. package/dist/better-auth/database-adapter/repository/session.d.ts.map +1 -0
  15. package/dist/better-auth/database-adapter/repository/user.d.ts +30 -0
  16. package/dist/better-auth/database-adapter/repository/user.d.ts.map +1 -0
  17. package/dist/better-auth/database-adapter/repository/verification.d.ts +18 -0
  18. package/dist/better-auth/database-adapter/repository/verification.d.ts.map +1 -0
  19. package/dist/better-auth/database-adapter/schema.d.ts +27 -0
  20. package/dist/better-auth/database-adapter/schema.d.ts.map +1 -0
  21. package/dist/better-auth/database-adapter/tests/index.test.d.ts +2 -0
  22. package/dist/better-auth/database-adapter/tests/index.test.d.ts.map +1 -0
  23. package/dist/better-auth/database-adapter/tests/repository/account.test.d.ts +2 -0
  24. package/dist/better-auth/database-adapter/tests/repository/account.test.d.ts.map +1 -0
  25. package/dist/better-auth/database-adapter/tests/repository/generic.test.d.ts +2 -0
  26. package/dist/better-auth/database-adapter/tests/repository/generic.test.d.ts.map +1 -0
  27. package/dist/better-auth/database-adapter/tests/repository/session.test.d.ts +2 -0
  28. package/dist/better-auth/database-adapter/tests/repository/session.test.d.ts.map +1 -0
  29. package/dist/better-auth/database-adapter/tests/repository/user.test.d.ts +2 -0
  30. package/dist/better-auth/database-adapter/tests/repository/user.test.d.ts.map +1 -0
  31. package/dist/better-auth/database-adapter/tests/repository/verification.test.d.ts +2 -0
  32. package/dist/better-auth/database-adapter/tests/repository/verification.test.d.ts.map +1 -0
  33. package/dist/better-auth/database-adapter/tests/sync-utils.d.ts +16 -0
  34. package/dist/better-auth/database-adapter/tests/sync-utils.d.ts.map +1 -0
  35. package/dist/better-auth/database-adapter/tests/utils.test.d.ts +2 -0
  36. package/dist/better-auth/database-adapter/tests/utils.test.d.ts.map +1 -0
  37. package/dist/better-auth/database-adapter/utils.d.ts +16 -0
  38. package/dist/better-auth/database-adapter/utils.d.ts.map +1 -0
  39. package/jazz-tools-0.18.6.tgz +0 -0
  40. package/package.json +10 -5
  41. package/src/better-auth/database-adapter/index.ts +228 -0
  42. package/src/better-auth/database-adapter/repository/account.ts +131 -0
  43. package/src/better-auth/database-adapter/repository/generic.ts +297 -0
  44. package/src/better-auth/database-adapter/repository/index.ts +5 -0
  45. package/src/better-auth/database-adapter/repository/session.ts +190 -0
  46. package/src/better-auth/database-adapter/repository/user.ts +158 -0
  47. package/src/better-auth/database-adapter/repository/verification.ts +37 -0
  48. package/src/better-auth/database-adapter/schema.ts +222 -0
  49. package/src/better-auth/database-adapter/tests/index.test.ts +690 -0
  50. package/src/better-auth/database-adapter/tests/repository/account.test.ts +149 -0
  51. package/src/better-auth/database-adapter/tests/repository/generic.test.ts +183 -0
  52. package/src/better-auth/database-adapter/tests/repository/session.test.ts +419 -0
  53. package/src/better-auth/database-adapter/tests/repository/user.test.ts +673 -0
  54. package/src/better-auth/database-adapter/tests/repository/verification.test.ts +101 -0
  55. package/src/better-auth/database-adapter/tests/sync-utils.ts +127 -0
  56. package/src/better-auth/database-adapter/tests/utils.test.ts +787 -0
  57. package/src/better-auth/database-adapter/utils.ts +178 -0
  58. package/tsup.config.ts +7 -0
@@ -0,0 +1,228 @@
1
+ import { type AdapterDebugLogs, createAdapter } from "better-auth/adapters";
2
+ import type { Account } from "jazz-tools";
3
+ import { startWorker } from "jazz-tools/worker";
4
+ import {
5
+ JazzRepository,
6
+ UserRepository,
7
+ SessionRepository,
8
+ VerificationRepository,
9
+ AccountRepository,
10
+ } from "./repository";
11
+ import { createJazzSchema, tableItem2Record } from "./schema.js";
12
+
13
+ export interface JazzAdapterConfig {
14
+ /**
15
+ * Helps you debug issues with the adapter.
16
+ */
17
+ debugLogs?: AdapterDebugLogs;
18
+ /**
19
+ * The sync server to use.
20
+ */
21
+ syncServer: string;
22
+ /**
23
+ * The worker account ID to use.
24
+ */
25
+ accountID: string;
26
+ /**
27
+ * The worker account secret to use.
28
+ */
29
+ accountSecret: string;
30
+ }
31
+
32
+ /**
33
+ * Creates a Better Auth database adapter that integrates with Jazz framework.
34
+ *
35
+ * This adapter provides a seamless integration between Better Auth and Jazz,
36
+ * allowing you to use Jazz as database for for Better Auth's authentication system.
37
+ *
38
+ * @param config - Configuration object for the Jazz Better Auth adapter
39
+ * @param config.syncServer - The Jazz sync server URL to connect to (e.g., "wss://your-sync-server.com")
40
+ * @param config.accountID - The worker account ID for the Jazz worker that will handle auth operations
41
+ * @param config.accountSecret - The worker account secret for authenticating with the Jazz sync server
42
+ * @param config.debugLogs - Optional debug logging configuration to help troubleshoot adapter issues
43
+ *
44
+ * @returns A Better Auth adapter instance configured to work with Jazz
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * import { JazzBetterAuthDatabaseAdapter } from "jazz-tools/better-auth/database-adapter";
49
+ * import { createAuth } from "better-auth";
50
+ *
51
+ * const auth = createAuth({
52
+ * adapter: JazzBetterAuthDatabaseAdapter({
53
+ * syncServer: "wss://your-jazz-sync-server.com",
54
+ * accountID: "auth-worker-account-id",
55
+ * accountSecret: "your-worker-account-secret",
56
+ * }),
57
+ * // ... other auth configuration
58
+ * });
59
+ * ```
60
+ */
61
+ export const JazzBetterAuthDatabaseAdapter = (
62
+ config: JazzAdapterConfig,
63
+ ): ReturnType<typeof createAdapter> =>
64
+ createAdapter({
65
+ config: {
66
+ adapterId: "jazz-tools-adapter", // A unique identifier for the adapter.
67
+ adapterName: "Jazz Tools Adapter", // The name of the adapter.
68
+ debugLogs: config.debugLogs ?? false, // Whether to enable debug logs.
69
+ supportsJSON: true, // Whether the database supports JSON. (Default: false)
70
+ supportsDates: true, // Whether the database supports dates. (Default: true)
71
+ supportsBooleans: true, // Whether the database supports booleans. (Default: true)
72
+ supportsNumericIds: false, // Whether the database supports auto-incrementing numeric IDs. (Default: true)
73
+ disableIdGeneration: true,
74
+ },
75
+ adapter: ({ schema }) => {
76
+ const JazzSchema = createJazzSchema(schema);
77
+
78
+ let worker: Account | undefined = undefined;
79
+
80
+ async function getWorker() {
81
+ if (worker) {
82
+ return worker;
83
+ }
84
+
85
+ const result = await startWorker({
86
+ AccountSchema: JazzSchema.WorkerAccount,
87
+ syncServer: config.syncServer,
88
+ accountID: config.accountID,
89
+ accountSecret: config.accountSecret,
90
+ skipInboxLoad: true,
91
+ asActiveAccount: false,
92
+ });
93
+
94
+ worker = result.worker;
95
+
96
+ return worker;
97
+ }
98
+
99
+ async function initRepository(
100
+ model: string,
101
+ ensureSync: boolean = false,
102
+ ): Promise<JazzRepository> {
103
+ let Repository: typeof JazzRepository | undefined = undefined;
104
+ switch (model) {
105
+ case "user":
106
+ Repository = UserRepository;
107
+ break;
108
+ case "session":
109
+ Repository = SessionRepository;
110
+ break;
111
+ case "verification":
112
+ Repository = VerificationRepository;
113
+ break;
114
+ case "account":
115
+ Repository = AccountRepository;
116
+ break;
117
+ }
118
+
119
+ if (!Repository) {
120
+ Repository = JazzRepository;
121
+ }
122
+
123
+ const worker = await getWorker();
124
+ const database = await JazzSchema.loadDatabase(worker);
125
+
126
+ const repository = new Repository(
127
+ JazzSchema.DatabaseRoot,
128
+ database,
129
+ worker,
130
+ schema,
131
+ ensureSync,
132
+ );
133
+
134
+ return repository;
135
+ }
136
+
137
+ return {
138
+ create: async ({ data, model, select }): Promise<any> => {
139
+ // console.log("create", { data, model, select });
140
+ const repository = await initRepository(model, true);
141
+
142
+ const created = await repository.create(model, data);
143
+
144
+ await repository.ensureSync();
145
+
146
+ return tableItem2Record(created);
147
+ },
148
+ update: async ({ model, where, update }): Promise<any> => {
149
+ // console.log("update", { model, where, update });
150
+ const repository = await initRepository(model, true);
151
+
152
+ const updated = await repository.update(
153
+ model,
154
+ where,
155
+ update as Record<string, any>,
156
+ );
157
+
158
+ if (updated.length === 0) {
159
+ return null;
160
+ }
161
+
162
+ await repository.ensureSync();
163
+
164
+ return tableItem2Record(updated[0]!);
165
+ },
166
+ updateMany: async ({ model, where, update }) => {
167
+ // console.log("updateMany", { model, where, update });
168
+ const repository = await initRepository(model, true);
169
+
170
+ const updated = await repository.update(model, where, update);
171
+
172
+ await repository.ensureSync();
173
+
174
+ return updated.length;
175
+ },
176
+ delete: async ({ model, where }) => {
177
+ // console.log("delete", { model, where });
178
+ const repository = await initRepository(model, true);
179
+
180
+ await repository.deleteValue(model, where);
181
+
182
+ await repository.ensureSync();
183
+ },
184
+ findOne: async ({ model, where }): Promise<any> => {
185
+ // console.log("findOne", { model, where });
186
+ const repository = await initRepository(model);
187
+
188
+ const item = await repository.findOne(model, where);
189
+
190
+ return tableItem2Record(item);
191
+ },
192
+ findMany: async ({
193
+ model,
194
+ where,
195
+ limit,
196
+ sortBy,
197
+ offset,
198
+ }): Promise<any[]> => {
199
+ const repository = await initRepository(model);
200
+
201
+ const items = await repository.findMany(
202
+ model,
203
+ where,
204
+ limit,
205
+ sortBy,
206
+ offset,
207
+ );
208
+
209
+ return items.map(tableItem2Record);
210
+ },
211
+ deleteMany: async ({ model, where }) => {
212
+ const repository = await initRepository(model, true);
213
+
214
+ const deleted = await repository.deleteValue(model, where);
215
+
216
+ await repository.ensureSync();
217
+
218
+ return deleted;
219
+ },
220
+ count: async ({ model, where }) => {
221
+ // console.log("count", { model, where });
222
+ const repository = await initRepository(model);
223
+
224
+ return repository.count(model, where);
225
+ },
226
+ };
227
+ },
228
+ });
@@ -0,0 +1,131 @@
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
+ /**
8
+ * This list stores an array of covalues ID
9
+ * mapped by the accountID property.
10
+ * Because accountId is not unique, and it is used on every social login
11
+ */
12
+ const AccountIdIndex = co.list(z.string());
13
+
14
+ export class AccountRepository extends JazzRepository {
15
+ /**
16
+ * Custom logic:
17
+ * - keep sync accountId index
18
+ */
19
+ async create(
20
+ model: string,
21
+ data: Record<string, any>,
22
+ uniqueId?: string,
23
+ ): Promise<TableItem> {
24
+ const account = await super.create(model, data, uniqueId);
25
+
26
+ await this.updateAccountIdIndex(
27
+ account[this.getAccountIdProperty()],
28
+ account.$jazz.id,
29
+ );
30
+
31
+ return account;
32
+ }
33
+
34
+ /**
35
+ * Custom logic:
36
+ * - if the accountId is in the where clause, get the ids from the index
37
+ */
38
+ async findMany(
39
+ model: string,
40
+ where: CleanedWhere[] | undefined,
41
+ limit?: number,
42
+ sortBy?: { field: string; direction: "asc" | "desc" },
43
+ offset?: number,
44
+ ): Promise<TableItem[]> {
45
+ if (isWhereBySingleField(this.getAccountIdProperty(), where)) {
46
+ const accountIdIndex = await this.getAccountIdIndex(where[0].value);
47
+
48
+ const ids = accountIdIndex ?? [];
49
+
50
+ if (ids.length === 0) {
51
+ return [];
52
+ }
53
+
54
+ // except for accountId clashing from different social providers,
55
+ // ids should contain a single id, max two
56
+ const results = await Promise.all(
57
+ ids.map((id) =>
58
+ super.findById(model, [
59
+ { field: "id", operator: "eq", value: id, connector: "AND" },
60
+ ]),
61
+ ),
62
+ );
63
+
64
+ return results.filter((value) => value !== null);
65
+ }
66
+
67
+ return super.findMany(model, where, limit, sortBy, offset);
68
+ }
69
+
70
+ async deleteValue(model: string, where: CleanedWhere[]): Promise<number> {
71
+ const nodes = await this.findMany(model, where);
72
+
73
+ const deleted = await super.deleteValue(model, where);
74
+
75
+ for (const node of nodes) {
76
+ const accountId = node.$jazz.raw.get(this.getAccountIdProperty()) as
77
+ | string
78
+ | undefined;
79
+
80
+ if (accountId) {
81
+ await this.deleteAccountIdIndex(accountId, node.$jazz.id);
82
+ }
83
+ }
84
+
85
+ return deleted;
86
+ }
87
+
88
+ private async getAccountIdIndex(accountId: string) {
89
+ const indexUnique = this.getAccountIdProperty() + "_" + accountId;
90
+
91
+ const accountIdIndex = await AccountIdIndex.loadUnique(
92
+ indexUnique,
93
+ this.owner.$jazz.id,
94
+ {
95
+ loadAs: this.worker,
96
+ },
97
+ );
98
+
99
+ return accountIdIndex;
100
+ }
101
+
102
+ private async updateAccountIdIndex(accountId: string, entityId: string) {
103
+ const accountIdIndex = await this.getAccountIdIndex(accountId);
104
+
105
+ const ids = accountIdIndex ?? [];
106
+
107
+ await AccountIdIndex.upsertUnique({
108
+ value: [...ids, entityId],
109
+ unique: this.getAccountIdProperty() + "_" + accountId,
110
+ owner: this.owner,
111
+ });
112
+ }
113
+
114
+ private async deleteAccountIdIndex(accountId: string, entityId: string) {
115
+ const accountIdIndex = await this.getAccountIdIndex(accountId);
116
+
117
+ const ids = accountIdIndex ?? [];
118
+
119
+ await AccountIdIndex.upsertUnique({
120
+ value: ids.filter((id) => id !== entityId),
121
+ unique: this.getAccountIdProperty() + "_" + accountId,
122
+ owner: this.owner,
123
+ });
124
+ }
125
+
126
+ private getAccountIdProperty(): string {
127
+ return (
128
+ this.betterAuthSchema.account?.fields.accountId?.fieldName || "accountId"
129
+ );
130
+ }
131
+ }
@@ -0,0 +1,297 @@
1
+ import { CleanedWhere } from "better-auth/adapters";
2
+ import { BetterAuthDbSchema } from "better-auth/db";
3
+ import { Account, CoList, CoMap, Group, co } from "jazz-tools";
4
+ import type { Database, TableItem } from "../schema.js";
5
+ import {
6
+ filterListByWhere,
7
+ isWhereBySingleField,
8
+ paginateList,
9
+ sortListByField,
10
+ } from "../utils.js";
11
+
12
+ export class JazzRepository {
13
+ protected databaseSchema: Database;
14
+ protected databaseRoot: co.loaded<Database, { group: true }>;
15
+ protected worker: Account;
16
+ protected owner: Group;
17
+ protected betterAuthSchema: BetterAuthDbSchema;
18
+
19
+ private coValuesTracker:
20
+ | {
21
+ done: () => Set<`co_z${string}`>;
22
+ }
23
+ | undefined = undefined;
24
+
25
+ constructor(
26
+ databaseSchema: Database,
27
+ databaseRoot: co.loaded<Database, { group: true }>,
28
+ worker: Account,
29
+ betterAuthSchema: BetterAuthDbSchema = {},
30
+ ensureSync: boolean = false,
31
+ ) {
32
+ this.databaseSchema = databaseSchema;
33
+ this.databaseRoot = databaseRoot;
34
+ this.worker = worker;
35
+ this.owner = databaseRoot.group;
36
+ this.betterAuthSchema = betterAuthSchema;
37
+
38
+ if (ensureSync)
39
+ this.coValuesTracker =
40
+ worker.$jazz.raw.core.node.syncManager.trackDirtyCoValues();
41
+ }
42
+
43
+ ensureSync() {
44
+ if (!this.coValuesTracker)
45
+ throw new Error("Repository wasn't initialized with ensureSync option");
46
+
47
+ return Promise.all(
48
+ Array.from(this.coValuesTracker.done(), (id) =>
49
+ this.worker.$jazz.raw.core.node.syncManager.waitForSync(id),
50
+ ),
51
+ );
52
+ }
53
+
54
+ async create(
55
+ model: string,
56
+ data: Record<string, any>,
57
+ uniqueId?: string,
58
+ ): Promise<TableItem> {
59
+ const schema = this.getSchema(model);
60
+
61
+ const resolved = await this.databaseRoot.$jazz.ensureLoaded({
62
+ resolve: {
63
+ tables: {
64
+ [model]: true,
65
+ },
66
+ },
67
+ });
68
+
69
+ const list = resolved.tables?.[model] as unknown as CoList<CoMap>;
70
+
71
+ if (!uniqueId) {
72
+ // Use the same owner of the table.
73
+ const node = schema.create(data, {
74
+ owner: list.$jazz.owner,
75
+ });
76
+
77
+ list.$jazz.push(node);
78
+
79
+ return node;
80
+ }
81
+
82
+ // If we have a unique id, we must check for soft deleted items first
83
+ const existingNode = (await schema.loadUnique(
84
+ uniqueId,
85
+ list.$jazz.owner.$jazz.id,
86
+ {
87
+ loadAs: this.worker,
88
+ },
89
+ )) as CoMap | null;
90
+
91
+ // if the entity exists and is not soft deleted, we must throw an error
92
+ if (existingNode && existingNode.$jazz?.raw.get("_deleted") !== true) {
93
+ throw new Error("Entity already exists");
94
+ }
95
+
96
+ // create the entity or update the deleted one
97
+ const node = await schema.upsertUnique({
98
+ value: {
99
+ ...data,
100
+ _deleted: false,
101
+ },
102
+ owner: list.$jazz.owner,
103
+ unique: uniqueId,
104
+ });
105
+
106
+ if (!node) {
107
+ throw new Error("Unable to create entity");
108
+ }
109
+
110
+ list.$jazz.push(node);
111
+
112
+ return node;
113
+ }
114
+
115
+ async findOne(
116
+ model: string,
117
+ where: CleanedWhere[],
118
+ ): Promise<TableItem | null> {
119
+ return this.findMany(model, where, 1).then((users) => users?.at(0) ?? null);
120
+ }
121
+
122
+ async findById(
123
+ model: string,
124
+ where: [{ field: "id"; operator: "eq"; value: string; connector: "AND" }],
125
+ ): Promise<TableItem | null> {
126
+ const id = where[0]!.value;
127
+
128
+ if (!id.startsWith("co_")) {
129
+ return null;
130
+ }
131
+
132
+ const node = await this.getSchema(model).load(id, { loadAs: this.worker });
133
+
134
+ if (!node) {
135
+ return null;
136
+ }
137
+
138
+ if (node.$jazz.raw.get("_deleted")) {
139
+ return null;
140
+ }
141
+
142
+ return node;
143
+ }
144
+
145
+ async findByUnique(
146
+ model: string,
147
+ where: [{ field: string; operator: "eq"; value: string; connector: "AND" }],
148
+ ): Promise<TableItem | null> {
149
+ const value = where[0]!.value;
150
+
151
+ const node = await this.getSchema(model).loadUnique(
152
+ value,
153
+ this.owner.$jazz.id,
154
+ {
155
+ loadAs: this.worker,
156
+ },
157
+ );
158
+
159
+ if (!node) {
160
+ return null;
161
+ }
162
+
163
+ if (node.$jazz.raw.get("_deleted")) {
164
+ return null;
165
+ }
166
+
167
+ return node;
168
+ }
169
+
170
+ async findMany(
171
+ model: string,
172
+ where: CleanedWhere[] | undefined,
173
+ limit?: number,
174
+ sortBy?: { field: string; direction: "asc" | "desc" },
175
+ offset?: number,
176
+ ): Promise<TableItem[]> {
177
+ // ensure schema exists
178
+ this.getSchema(model);
179
+
180
+ if (isWhereBySingleField("id", where)) {
181
+ return this.findById(model, where).then((node) => (node ? [node] : []));
182
+ }
183
+
184
+ const resolvedRoot = await this.databaseRoot.$jazz.ensureLoaded({
185
+ resolve: {
186
+ tables: {
187
+ [model]: {
188
+ $each: true,
189
+ },
190
+ },
191
+ },
192
+ });
193
+
194
+ const list = resolvedRoot.tables?.[model] as CoList<CoMap> | undefined;
195
+
196
+ if (!list) {
197
+ return [];
198
+ }
199
+
200
+ return this.filterSortPaginateList(list, where, limit, sortBy, offset);
201
+ }
202
+
203
+ async update(
204
+ model: string,
205
+ where: CleanedWhere[],
206
+ update: Record<string, any>,
207
+ ): Promise<TableItem[]> {
208
+ const nodes = await this.findMany(model, where);
209
+
210
+ if (nodes.length === 0) {
211
+ return [];
212
+ }
213
+
214
+ for (const node of nodes) {
215
+ for (const [key, value] of Object.entries(
216
+ update as Record<string, any>,
217
+ )) {
218
+ node.$jazz.set(key, value);
219
+ }
220
+ }
221
+
222
+ return nodes;
223
+ }
224
+
225
+ async deleteValue(model: string, where: CleanedWhere[]): Promise<number> {
226
+ const items = await this.findMany(model, where);
227
+
228
+ if (items.length === 0) {
229
+ return 0;
230
+ }
231
+
232
+ const resolved = await this.databaseRoot.$jazz.ensureLoaded({
233
+ resolve: {
234
+ tables: {
235
+ [model]: {
236
+ $each: true,
237
+ },
238
+ },
239
+ },
240
+ });
241
+
242
+ if (!resolved) {
243
+ throw new Error("Unable to load values");
244
+ }
245
+
246
+ const list = resolved?.tables?.[model] as unknown as CoList<CoMap>;
247
+
248
+ for (const toBeDeleted of items) {
249
+ // Get entries without trigger the shallow load
250
+ const index = [...list.entries()].findIndex(
251
+ ([_, value]) => value && value.$jazz.id === toBeDeleted.$jazz.id,
252
+ );
253
+
254
+ toBeDeleted.$jazz.set("_deleted", true);
255
+
256
+ if (index !== -1) {
257
+ list.$jazz.remove(index);
258
+ }
259
+ }
260
+
261
+ return items.length;
262
+ }
263
+
264
+ async count(
265
+ model: string,
266
+ where: CleanedWhere[] | undefined,
267
+ ): Promise<number> {
268
+ return this.findMany(model, where).then((values) => values.length);
269
+ }
270
+
271
+ protected getSchema(model: string) {
272
+ const schema = this.databaseSchema.shape.tables.shape[model]?.element;
273
+ if (!schema) {
274
+ throw new Error(`Schema for model "${model}" not found`);
275
+ }
276
+ return schema;
277
+ }
278
+
279
+ protected filterSortPaginateList<T extends TableItem>(
280
+ list: CoList<CoMap | null>,
281
+ where: CleanedWhere[] | undefined,
282
+ limit?: number,
283
+ sortBy?: { field: string; direction: "asc" | "desc" },
284
+ offset?: number,
285
+ ): T[] {
286
+ // ignore nullable values and soft deleted items
287
+ return [
288
+ list.filter(
289
+ (item) => item !== null && item.$jazz.raw.get("_deleted") !== true,
290
+ ),
291
+ ]
292
+ .map((list) => filterListByWhere(list, where))
293
+ .map((list) => sortListByField(list, sortBy))
294
+ .map((list) => paginateList(list, limit, offset))
295
+ .at(0)! as T[];
296
+ }
297
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./generic.js";
2
+ export * from "./user.js";
3
+ export * from "./session.js";
4
+ export * from "./verification.js";
5
+ export * from "./account.js";