better-auth-instantdb 1.3.0

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 (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +216 -0
  3. package/dist/adapter/create-schema.d.ts +9 -0
  4. package/dist/adapter/create-schema.js +178 -0
  5. package/dist/adapter/instant-adapter.d.ts +25 -0
  6. package/dist/adapter/instant-adapter.js +273 -0
  7. package/dist/client-plugin.d.ts +2143 -0
  8. package/dist/client-plugin.js +21 -0
  9. package/dist/create-schema.d.ts +25 -0
  10. package/dist/create-schema.js +115 -0
  11. package/dist/create-schema.js.map +1 -0
  12. package/dist/index.d.mts +18 -0
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +2 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/index.mjs +160 -0
  17. package/dist/instant-adapter.d.ts +26 -0
  18. package/dist/instant-adapter.js +214 -0
  19. package/dist/instant-adapter.js.map +1 -0
  20. package/dist/instant-auth.d.ts +3 -0
  21. package/dist/instant-auth.js +9 -0
  22. package/dist/lib/instant-auth.d.ts +3 -0
  23. package/dist/lib/instant-auth.js +9 -0
  24. package/dist/lib/utils.d.ts +12 -0
  25. package/dist/lib/utils.js +22 -0
  26. package/dist/metafile-cjs.json +1 -0
  27. package/dist/metafile-esm.json +1 -0
  28. package/dist/react/client-plugin.d.ts +2143 -0
  29. package/dist/react/client-plugin.js +21 -0
  30. package/dist/react/index.d.ts +4 -0
  31. package/dist/react/index.js +4 -0
  32. package/dist/react/instant-auth.d.ts +7 -0
  33. package/dist/react/instant-auth.js +5 -0
  34. package/dist/react/react.d.ts +2 -0
  35. package/dist/react/react.js +2 -0
  36. package/dist/react/types.d.ts +6 -0
  37. package/dist/react/types.js +1 -0
  38. package/dist/react/use-hydrated.d.ts +1 -0
  39. package/dist/react/use-hydrated.js +7 -0
  40. package/dist/react/use-instant-auth.d.ts +8 -0
  41. package/dist/react/use-instant-auth.js +13 -0
  42. package/dist/react/use-instant-session.d.ts +32 -0
  43. package/dist/react/use-instant-session.js +25 -0
  44. package/dist/react/use-persistent-session.d.ts +27 -0
  45. package/dist/react/use-persistent-session.js +49 -0
  46. package/dist/react/use-session.d.ts +0 -0
  47. package/dist/react/use-session.js +1 -0
  48. package/dist/react/with-instant.d.ts +3 -0
  49. package/dist/react/with-instant.js +47 -0
  50. package/dist/react.d.ts +2 -0
  51. package/dist/react.js +2 -0
  52. package/dist/shared/instant-auth.d.ts +4 -0
  53. package/dist/shared/instant-auth.js +9 -0
  54. package/dist/utils.d.ts +6 -0
  55. package/dist/utils.js +9 -0
  56. package/package.json +70 -0
  57. package/src/adapter/create-schema.ts +232 -0
  58. package/src/adapter/instant-adapter.ts +422 -0
  59. package/src/index.ts +2 -0
  60. package/src/lib/utils.ts +24 -0
  61. package/src/react/index.ts +4 -0
  62. package/src/react/instant-auth.tsx +17 -0
  63. package/src/react/types.ts +9 -0
  64. package/src/react/use-hydrated.ts +13 -0
  65. package/src/react/use-instant-auth.ts +28 -0
  66. package/src/react/use-instant-session.ts +46 -0
  67. package/src/react/use-persistent-session.ts +64 -0
  68. package/src/shared/instant-auth.ts +18 -0
@@ -0,0 +1,21 @@
1
+ export const instantDBPluginClient = ({ authClient }) => {
2
+ // Store the original useSession hook
3
+ const originalUseSession = authClient.useSession;
4
+ // Override useSession with custom implementation using Object.defineProperty
5
+ // This ensures it works even if useSession is a getter or non-writable property
6
+ Object.defineProperty(authClient, "useSession", {
7
+ value: () => {
8
+ // Call the original hook to get session data
9
+ const sessionResult = originalUseSession.call(authClient);
10
+ // You can add custom logic here
11
+ // For example, sync with InstantDB, transform data, etc.
12
+ console.log("useSession override called!", sessionResult);
13
+ // Return the session result (or modified version)
14
+ return sessionResult;
15
+ },
16
+ writable: true,
17
+ configurable: true,
18
+ enumerable: true
19
+ });
20
+ return authClient;
21
+ };
@@ -0,0 +1,4 @@
1
+ export * from "./instant-auth";
2
+ export * from "./use-instant-auth";
3
+ export * from "./use-instant-session";
4
+ export * from "./use-persistent-session";
@@ -0,0 +1,4 @@
1
+ export * from "./instant-auth";
2
+ export * from "./use-instant-auth";
3
+ export * from "./use-instant-session";
4
+ export * from "./use-persistent-session";
@@ -0,0 +1,7 @@
1
+ import type { InstantReactWebDatabase } from "@instantdb/react";
2
+ import type { MinimalAuthClient, SessionResult } from "./types";
3
+ export declare function InstantAuth<TSessionResult extends SessionResult>({ db, authClient, persistent }: {
4
+ db: InstantReactWebDatabase<any, any>;
5
+ authClient: MinimalAuthClient<TSessionResult>;
6
+ persistent?: boolean;
7
+ }): null;
@@ -0,0 +1,5 @@
1
+ import { useInstantAuth } from "./use-instant-auth";
2
+ export function InstantAuth({ db, authClient, persistent }) {
3
+ useInstantAuth({ db, authClient, persistent });
4
+ return null;
5
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./react/instant-auth";
2
+ export * from "./react/use-instant-auth";
@@ -0,0 +1,2 @@
1
+ export * from "./react/instant-auth";
2
+ export * from "./react/use-instant-auth";
@@ -0,0 +1,6 @@
1
+ import type { createAuthClient } from "better-auth/react";
2
+ export type SessionResult = ReturnType<AuthClient["useSession"]>;
3
+ export type MinimalAuthClient<TSessionResult extends SessionResult> = {
4
+ useSession: () => TSessionResult;
5
+ };
6
+ export type AuthClient = ReturnType<typeof createAuthClient>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare function useHydrated(): boolean;
@@ -0,0 +1,7 @@
1
+ import { useSyncExternalStore } from "react";
2
+ function subscribe() {
3
+ return () => { };
4
+ }
5
+ export function useHydrated() {
6
+ return useSyncExternalStore(subscribe, () => true, () => false);
7
+ }
@@ -0,0 +1,8 @@
1
+ import type { InstantReactWebDatabase } from "@instantdb/react";
2
+ import type { MinimalAuthClient, SessionResult } from "./types";
3
+ export interface InstantAuthProps<TSessionResult extends SessionResult> {
4
+ db: InstantReactWebDatabase<any, any>;
5
+ authClient: MinimalAuthClient<TSessionResult>;
6
+ persistent?: boolean;
7
+ }
8
+ export declare function useInstantAuth<TSessionResult extends SessionResult>({ db, authClient, persistent }: InstantAuthProps<TSessionResult>): void;
@@ -0,0 +1,13 @@
1
+ import { useEffect } from "react";
2
+ import { instantAuth } from "../shared/instant-auth";
3
+ import { usePersistentSession } from "./use-persistent-session";
4
+ export function useInstantAuth({ db, authClient, persistent }) {
5
+ const { isPending, data } = persistent
6
+ ? usePersistentSession(authClient)
7
+ : authClient.useSession();
8
+ useEffect(() => {
9
+ if (isPending)
10
+ return;
11
+ instantAuth(db, data === null || data === void 0 ? void 0 : data.session);
12
+ }, [db, isPending, data === null || data === void 0 ? void 0 : data.session]);
13
+ }
@@ -0,0 +1,32 @@
1
+ import type { BetterFetchError } from "better-auth/react";
2
+ import type { SessionResult } from "./types";
3
+ import type { InstantAuthProps } from "./use-instant-auth";
4
+ export declare function useInstantSession<TSessionResult extends SessionResult>({ db, authClient, persistent }: InstantAuthProps<TSessionResult>): {
5
+ refetch: (queryParams?: {
6
+ query?: import("better-auth/types").SessionQueryParams;
7
+ }) => void;
8
+ data: {
9
+ user: {
10
+ id: string;
11
+ createdAt: Date;
12
+ updatedAt: Date;
13
+ email: string;
14
+ emailVerified: boolean;
15
+ name: string;
16
+ image?: string | null | undefined;
17
+ };
18
+ session: {
19
+ id: string;
20
+ createdAt: Date;
21
+ updatedAt: Date;
22
+ userId: string;
23
+ expiresAt: Date;
24
+ token: string;
25
+ ipAddress?: string | null | undefined;
26
+ userAgent?: string | null | undefined;
27
+ };
28
+ } | null;
29
+ isPending: boolean;
30
+ isRefetching: boolean;
31
+ error: BetterFetchError;
32
+ };
@@ -0,0 +1,25 @@
1
+ import { usePersistentSession } from "./use-persistent-session";
2
+ export function useInstantSession({ db, authClient, persistent }) {
3
+ var _a, _b;
4
+ const { data: sessionData, isPending, error, isRefetching, ...rest } = persistent ? usePersistentSession(authClient) : authClient.useSession();
5
+ const { user: authUser, error: authError } = db.useAuth();
6
+ const authPending = sessionData && !authUser && !authError;
7
+ const { data } = db.useQuery(authUser
8
+ ? {
9
+ $users: { $: { where: { id: authUser.id } } }
10
+ }
11
+ : null);
12
+ if ((_a = data === null || data === void 0 ? void 0 : data.$users) === null || _a === void 0 ? void 0 : _a.length) {
13
+ const user = data.$users[0];
14
+ if (((_b = sessionData === null || sessionData === void 0 ? void 0 : sessionData.user) === null || _b === void 0 ? void 0 : _b.id) === user.id) {
15
+ sessionData.user = user;
16
+ }
17
+ }
18
+ return {
19
+ data: !authError && !authPending ? sessionData : null,
20
+ isPending: authPending || isPending,
21
+ isRefetching: authPending || isRefetching,
22
+ error: authError || error,
23
+ ...rest
24
+ };
25
+ }
@@ -0,0 +1,27 @@
1
+ import type { MinimalAuthClient, SessionResult } from "./types";
2
+ export declare function usePersistentSession<TSessionResult extends SessionResult, TAuthClient extends MinimalAuthClient<TSessionResult>>(authClient: TAuthClient): TSessionResult | ({
3
+ data: {
4
+ user: {
5
+ id: string;
6
+ createdAt: Date;
7
+ updatedAt: Date;
8
+ email: string;
9
+ emailVerified: boolean;
10
+ name: string;
11
+ image?: string | null | undefined;
12
+ };
13
+ session: {
14
+ id: string;
15
+ createdAt: Date;
16
+ updatedAt: Date;
17
+ userId: string;
18
+ expiresAt: Date;
19
+ token: string;
20
+ ipAddress?: string | null | undefined;
21
+ userAgent?: string | null | undefined;
22
+ };
23
+ } | null;
24
+ isPending: boolean;
25
+ isRefetching: boolean;
26
+ error: import("better-auth/react").BetterFetchError | null;
27
+ } & Omit<TSessionResult, "data" | "error" | "isPending" | "isRefetching">);
@@ -0,0 +1,49 @@
1
+ import { useEffect } from "react";
2
+ import { useHydrated } from "./use-hydrated";
3
+ let lastPersisted;
4
+ let restoredData;
5
+ export function usePersistentSession(authClient) {
6
+ const { data, isPending, isRefetching, error, ...rest } = authClient.useSession();
7
+ const hydrated = useHydrated();
8
+ useEffect(() => {
9
+ if (isPending)
10
+ return;
11
+ const persistSession = () => {
12
+ if (!data || (lastPersisted === null || lastPersisted === void 0 ? void 0 : lastPersisted.session.id) === (data === null || data === void 0 ? void 0 : data.session.id))
13
+ return;
14
+ lastPersisted = data;
15
+ localStorage.setItem("ba-instant-session", JSON.stringify(data));
16
+ };
17
+ const unpersistSession = () => {
18
+ if (data || error || (lastPersisted === null && restoredData === null))
19
+ return;
20
+ localStorage.removeItem("ba-instant-session");
21
+ lastPersisted = null;
22
+ restoredData = null;
23
+ };
24
+ persistSession();
25
+ unpersistSession();
26
+ }, [data, isPending, error]);
27
+ if (hydrated && !data) {
28
+ if (restoredData === undefined) {
29
+ const persisted = localStorage.getItem("ba-instant-session");
30
+ if (persisted) {
31
+ const data = JSON.parse(persisted);
32
+ restoredData = data;
33
+ }
34
+ else {
35
+ restoredData = null;
36
+ }
37
+ }
38
+ if (restoredData) {
39
+ return {
40
+ data: restoredData,
41
+ isPending: false,
42
+ isRefetching: false,
43
+ error: null,
44
+ ...rest
45
+ };
46
+ }
47
+ }
48
+ return { data, isPending, isRefetching, error, ...rest };
49
+ }
File without changes
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,3 @@
1
+ import type { InstantReactWebDatabase } from "@instantdb/react";
2
+ import type { createAuthClient } from "better-auth/react";
3
+ export declare const withInstant: <T extends ReturnType<typeof createAuthClient>>(db: InstantReactWebDatabase<any>, authClient: T) => T;
@@ -0,0 +1,47 @@
1
+ let authLoading = false;
2
+ let auth = null;
3
+ export const withInstant = (
4
+ // biome-ignore lint/suspicious/noExplicitAny: any thing goes
5
+ db, authClient) => {
6
+ if (typeof window === "undefined")
7
+ return authClient;
8
+ db.core.subscribeAuth((authResult) => {
9
+ auth = authResult;
10
+ });
11
+ authClient.$store.atoms.session.subscribe(async (value, oldValue) => {
12
+ const { isPending, data, error } = value;
13
+ if (isPending || error) {
14
+ const cachedSessionResult = localStorage.getItem("cached-session-result");
15
+ if (cachedSessionResult && !cachedSessionResult.error) {
16
+ authClient.$store.atoms.session.set(JSON.parse(cachedSessionResult));
17
+ }
18
+ return;
19
+ }
20
+ while (authLoading) {
21
+ await new Promise((resolve) => requestAnimationFrame(resolve));
22
+ }
23
+ const user = auth === null || auth === void 0 ? void 0 : auth.user;
24
+ if ((user === null || user === void 0 ? void 0 : user.id) === (data === null || data === void 0 ? void 0 : data.user.id))
25
+ return;
26
+ if (user && !data) {
27
+ authLoading = true;
28
+ console.log("signing out");
29
+ await db.auth.signOut();
30
+ authLoading = false;
31
+ }
32
+ if (data) {
33
+ authLoading = true;
34
+ authClient.$store.atoms.session.set(oldValue);
35
+ console.log("signing in with token", data.session.token);
36
+ await db.auth.signInWithToken(data.session.token);
37
+ authClient.$store.atoms.session.set(value);
38
+ authLoading = false;
39
+ }
40
+ localStorage.setItem("cached-session-result", JSON.stringify(value));
41
+ });
42
+ const cachedSessionResult = localStorage.getItem("cached-session-result");
43
+ if (cachedSessionResult) {
44
+ authClient.$store.atoms.session.set(JSON.parse(cachedSessionResult));
45
+ }
46
+ return authClient;
47
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./react/instant-auth";
2
+ export * from "./react/use-instant-auth";
package/dist/react.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./react/instant-auth";
2
+ export * from "./react/use-instant-auth";
@@ -0,0 +1,4 @@
1
+ import type { InstantCoreDatabase } from "@instantdb/core";
2
+ import type { InstantReactWebDatabase } from "@instantdb/react";
3
+ import type { Session } from "better-auth";
4
+ export declare function instantAuth(db: InstantCoreDatabase<any, any> | InstantReactWebDatabase<any, any>, session?: Session): Promise<void>;
@@ -0,0 +1,9 @@
1
+ export async function instantAuth(db, session) {
2
+ const user = await db.getAuth();
3
+ if (session && (user === null || user === void 0 ? void 0 : user.id) !== (session === null || session === void 0 ? void 0 : session.userId)) {
4
+ db.auth.signInWithToken(session.token);
5
+ }
6
+ if (!session && user) {
7
+ db.auth.signOut();
8
+ }
9
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Pretty prints an object.
3
+ * @param object - The object to pretty print.
4
+ * @returns The pretty printed object.
5
+ */
6
+ export declare function prettyPrint(object: unknown): string;
package/dist/utils.js ADDED
@@ -0,0 +1,9 @@
1
+ import util from "node:util";
2
+ /**
3
+ * Pretty prints an object.
4
+ * @param object - The object to pretty print.
5
+ * @returns The pretty printed object.
6
+ */
7
+ export function prettyPrint(object) {
8
+ return util.inspect(object, { colors: true, depth: null });
9
+ }
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "better-auth-instantdb",
3
+ "homepage": "https://github.com/daveycodez/better-auth-instantdb",
4
+ "version": "1.3.0",
5
+ "description": "Better Auth InstantDB Adapter",
6
+ "type": "module",
7
+ "scripts": {
8
+ "dev": "tsc --watch",
9
+ "build": "tsc"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ },
16
+ "./react": {
17
+ "types": "./dist/react/index.d.ts",
18
+ "import": "./dist/react/index.js"
19
+ }
20
+ },
21
+ "files": [
22
+ "src",
23
+ "dist"
24
+ ],
25
+ "keywords": [
26
+ "typescript",
27
+ "instantdb",
28
+ "better",
29
+ "better-auth",
30
+ "auth",
31
+ "authentication",
32
+ "adapter"
33
+ ],
34
+ "author": "daveycodez",
35
+ "license": "MIT",
36
+ "devDependencies": {
37
+ "@better-auth/core": "^1.3.34",
38
+ "@instantdb/admin": "^0.22.51",
39
+ "@instantdb/core": "^0.22.51",
40
+ "@instantdb/react": "^0.22.51",
41
+ "@types/node": "^24.10.1",
42
+ "@types/react": "^19.2.5",
43
+ "@types/react-dom": "^19.2.3",
44
+ "better-auth": "^1.3.34",
45
+ "react": "^19.2.0",
46
+ "react-dom": "^19.2.0",
47
+ "typescript": "^5.9.3"
48
+ },
49
+ "peerDependencies": {
50
+ "@instantdb/admin": ">=0.22.0",
51
+ "better-auth": ">=1.3.0"
52
+ },
53
+ "peerDependenciesOptional": {
54
+ "@instantdb/react": ">=0.22.0",
55
+ "react": ">=18.0.0",
56
+ "react-dom": ">=18.0.0"
57
+ },
58
+ "peerDependenciesMeta": {
59
+ "@instantdb/react": {
60
+ "optional": true
61
+ },
62
+ "react": {
63
+ "optional": true
64
+ },
65
+ "react-dom": {
66
+ "optional": true
67
+ }
68
+ },
69
+ "packageManager": "pnpm@10.22.0"
70
+ }
@@ -0,0 +1,232 @@
1
+ import type { BetterAuthDBSchema, DBFieldAttribute } from "@better-auth/core/db"
2
+
3
+ import { fieldNameToLabel } from "../lib/utils"
4
+
5
+ /**
6
+ * Converts a Better Auth field type to InstantDB field type
7
+ */
8
+ function convertFieldType(
9
+ field: DBFieldAttribute,
10
+ modelName: string,
11
+ fieldKey: string
12
+ ) {
13
+ const { type, required, unique, sortable } = field
14
+
15
+ // Handle type as string or array
16
+ const typeStr = Array.isArray(type) ? type[0] : type
17
+
18
+ let fieldType: string
19
+
20
+ switch (typeStr) {
21
+ case "string":
22
+ fieldType = "i.string()"
23
+ break
24
+ case "boolean":
25
+ fieldType = "i.boolean()"
26
+ break
27
+ case "date":
28
+ fieldType = "i.date()"
29
+ break
30
+ case "number":
31
+ fieldType = "i.number()"
32
+ break
33
+ case "json":
34
+ fieldType = "i.json()"
35
+ break
36
+ case "number[]":
37
+ fieldType = "i.json()"
38
+ break
39
+ case "string[]":
40
+ fieldType = "i.json()"
41
+ break
42
+ default:
43
+ fieldType = "i.string()" // Default to string for unknown types
44
+ }
45
+
46
+ // Apply modifiers
47
+ if (unique) {
48
+ fieldType += ".unique()"
49
+ }
50
+
51
+ // Only make optional if required is explicitly false
52
+ // If required is true, never make it optional (even if defaultValue exists)
53
+ if (required === false) {
54
+ fieldType += ".optional()"
55
+ }
56
+
57
+ // Add indexed if sortable
58
+ if (sortable) {
59
+ fieldType += ".indexed()"
60
+ }
61
+
62
+ // For user model, ensure all fields except email end with optional()
63
+ if (
64
+ modelName === "user" &&
65
+ fieldKey !== "email" &&
66
+ !fieldType.endsWith(".optional()")
67
+ ) {
68
+ fieldType += ".optional()"
69
+ }
70
+
71
+ return fieldType
72
+ }
73
+
74
+ /**
75
+ * Gets the InstantDB entity name for a given model name
76
+ */
77
+ function getEntityName(
78
+ modelName: string,
79
+ tableKey: string,
80
+ usePlural: boolean
81
+ ): string {
82
+ if (modelName === "user") {
83
+ return "$users"
84
+ }
85
+ return usePlural ? `${tableKey}s` : tableKey
86
+ }
87
+
88
+ /**
89
+ * Converts a table/model name to camelCase for link names
90
+ */
91
+ function toCamelCase(str: string): string {
92
+ return str.charAt(0).toLowerCase() + str.slice(1)
93
+ }
94
+
95
+ /**
96
+ * Creates InstantDB links from Better Auth schema references
97
+ */
98
+ export function createLinks(
99
+ tables: BetterAuthDBSchema,
100
+ usePlural: boolean
101
+ ): Record<string, any> {
102
+ const links: Record<string, any> = {}
103
+ const entityNameMap: Record<string, string> = {}
104
+
105
+ // First pass: build entity name mapping
106
+ for (const [key, table] of Object.entries(tables)) {
107
+ const { modelName } = table
108
+ entityNameMap[modelName] = getEntityName(modelName, key, usePlural)
109
+ }
110
+
111
+ // Second pass: find all references and create links
112
+ for (const [key, table] of Object.entries(tables)) {
113
+ const { modelName, fields } = table
114
+ const sourceEntityName = getEntityName(modelName, key, usePlural)
115
+
116
+ for (const [fieldKey, field] of Object.entries(fields)) {
117
+ const { references } = field
118
+
119
+ if (references) {
120
+ const { model: targetModel, onDelete } = references
121
+ const targetEntityName = entityNameMap[targetModel]
122
+
123
+ if (!targetEntityName) {
124
+ console.warn(
125
+ `Warning: Could not find entity name for model "${targetModel}" referenced by ${modelName}.${fieldKey}`
126
+ )
127
+ continue
128
+ }
129
+
130
+ // Generate forward label from field name, using target model if field doesn't end with "id"
131
+ const forwardLabel = fieldNameToLabel(fieldKey, targetModel)
132
+
133
+ // Generate link name: {sourceTable}{forwardLabel}
134
+ // e.g., "invitations" + "Inviter" -> "invitationsInviter"
135
+ const sourceTableName = sourceEntityName.replace("$", "")
136
+ const forwardLabelCapitalized =
137
+ forwardLabel.charAt(0).toUpperCase() +
138
+ toCamelCase(forwardLabel.slice(1))
139
+ const linkName = `${sourceTableName}${forwardLabelCapitalized}`
140
+
141
+ // Generate reverse label (use source entity name without $ prefix)
142
+ const reverseLabel = sourceEntityName.replace("$", "")
143
+
144
+ // Create link definition
145
+ links[linkName] = {
146
+ forward: {
147
+ on: sourceEntityName,
148
+ has: "one",
149
+ label: forwardLabel,
150
+ onDelete: onDelete || "cascade"
151
+ },
152
+ reverse: {
153
+ on: targetEntityName,
154
+ has: "many",
155
+ label: reverseLabel
156
+ }
157
+ }
158
+ }
159
+ }
160
+ }
161
+
162
+ return links
163
+ }
164
+
165
+ /**
166
+ * Creates an InstantDB schema file from Better Auth schema format
167
+ */
168
+ export function createSchema(
169
+ tables: BetterAuthDBSchema,
170
+ usePlural: boolean
171
+ ): string {
172
+ const entities: Record<string, string> = {}
173
+
174
+ for (const [key, table] of Object.entries(tables)) {
175
+ const { modelName, fields } = table
176
+
177
+ // For other tables, use the key as entity name
178
+ const entityFields: string[] = []
179
+
180
+ for (const [fieldKey, field] of Object.entries(fields)) {
181
+ const fieldType = convertFieldType(field, modelName, fieldKey)
182
+ entityFields.push(`${fieldKey}: ${fieldType}`)
183
+ }
184
+
185
+ // Pluralize table name if usePlural is true
186
+ const namespace =
187
+ modelName === "user" ? "$users" : usePlural ? `${key}s` : key
188
+ entities[namespace] =
189
+ `i.entity({\n ${entityFields.join(",\n ")}\n })`
190
+ }
191
+
192
+ // Generate links from references
193
+ const links = createLinks(tables, usePlural)
194
+
195
+ // Generate the schema file content
196
+ const entitiesString = Object.entries(entities)
197
+ .map(([name, definition]) => ` ${name}: ${definition}`)
198
+ .join(",\n")
199
+
200
+ // Format links as string
201
+ const linksString = Object.entries(links)
202
+ .map(([linkName, linkDef]) => {
203
+ const forward = linkDef.forward
204
+ const reverse = linkDef.reverse
205
+ return ` ${linkName}: {
206
+ forward: {
207
+ on: "${forward.on}",
208
+ has: "${forward.has}",
209
+ label: "${forward.label}"${forward.onDelete ? `,\n onDelete: "${forward.onDelete}"` : ""}
210
+ },
211
+ reverse: {
212
+ on: "${reverse.on}",
213
+ has: "${reverse.has}",
214
+ label: "${reverse.label}"
215
+ }
216
+ }`
217
+ })
218
+ .join(",\n")
219
+
220
+ const linksSection = linksString ? `,\n links: {\n${linksString}\n }` : ""
221
+
222
+ return `// Docs: https://www.instantdb.com/docs/modeling-data
223
+
224
+ import { i } from "@instantdb/react"
225
+
226
+ export const authSchema = i.schema({
227
+ entities: {
228
+ ${entitiesString}
229
+ }${linksSection}
230
+ })
231
+ `
232
+ }