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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 daveyplate
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,216 @@
1
+ # Better Auth InstantDB Adapter
2
+
3
+ A seamless integration between [Better Auth](https://better-auth.com) and [InstantDB](https://www.instantdb.com) that allows you to use InstantDB as your authentication database.
4
+
5
+ [Better Auth UI Integration](https://better-auth-ui.com/data/instantdb)
6
+
7
+ - *Own Your Auth*
8
+
9
+ 𝕏 [@daveycodez](https://x.com/daveycodez)
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ bun add better-auth-instantdb@latest
15
+ ```
16
+
17
+ ## Features
18
+
19
+ - 🔐 **Complete Authentication**: Leverage Better Auth's authentication features with InstantDB as your database
20
+ - 🔄 **Session Sync**: Automatically synchronize auth sessions between Better Auth and InstantDB
21
+ - 🛠️ **Customizable**: Configure the adapter to match your specific needs
22
+ - 🧩 **Type-Safe**: Fully typed with TypeScript for improved developer experience
23
+
24
+ ## Usage
25
+
26
+ ### Basic Setup
27
+
28
+ First you need to add the InstantDB Adapter to your Better Auth config.
29
+
30
+ #### auth.ts
31
+ ```typescript
32
+ import { betterAuth } from "better-auth"
33
+ import { instantDBAdapter } from "better-auth-instantdb"
34
+ import { init } from "@instantdb/admin"
35
+ import schema from "@/instant.schema"
36
+
37
+ // Create InstantDB admin client
38
+ export const adminDb = init({
39
+ schema,
40
+ appId: process.env.VITE_INSTANT_APP_ID as string,
41
+ adminToken: process.env.INSTANT_ADMIN_TOKEN,
42
+ useDateObjects: true
43
+ })
44
+
45
+ // Create Better Auth instance with InstantDB adapter
46
+ export const auth = betterAuth({
47
+ database: instantDBAdapter({
48
+ db: adminDb,
49
+ usePlural: true, // Optional: set to true if your schema uses plural table names
50
+ debugLogs: false // Optional: set to true to see detailed logs
51
+ }),
52
+ // Other Better Auth configuration options
53
+ emailAndPassword: {
54
+ enabled: true
55
+ },
56
+ })
57
+ ```
58
+
59
+ ### Client-Side Usage
60
+
61
+ Synchronize authentication state between Better Auth and InstantDB:
62
+
63
+ #### providers.tsx
64
+ ```typescript
65
+ import authClient from "@/lib/auth-client"
66
+ import { init } from "@instantdb/react"
67
+ import { useInstantAuth } from "better-auth-instantdb"
68
+
69
+ // Initialize InstantDB client
70
+ const db = init({
71
+ appId: process.env.NEXT_PUBLIC_INSTANT_APP_ID
72
+ })
73
+
74
+ export function Providers({ children }) {
75
+ return (
76
+ <>
77
+ <InstantAuth db={db} authClient={authClient} persistent />
78
+
79
+ {children}
80
+ </>
81
+ )
82
+ }
83
+ ```
84
+
85
+ ## InstantDB Schema and Permissions Setup
86
+
87
+ ⚠️ **Important**: You can now use Better Auth cli to generate InstantDB schema, but you must manually configure permissions, for now.
88
+
89
+ ### 1. Create Schema File
90
+
91
+ Run the following command in terminal:
92
+
93
+ ```bash
94
+ npx @better-auth/cli generate
95
+ ```
96
+
97
+ ### 2. Create Permissions File
98
+
99
+ Create an `instant.perms.ts` file to secure your schema:
100
+
101
+ ```typescript
102
+ // instant.perms.ts
103
+ import type { InstantRules } from "@instantdb/react";
104
+
105
+ const rules = {
106
+ // Prevent creation of new attributes without explicit schema changes
107
+ attrs: {
108
+ allow: {
109
+ $default: "false",
110
+ },
111
+ },
112
+ // Auth entities permissions
113
+ users: {
114
+ bind: ["isOwner", "auth.id != null && auth.id == data.id"],
115
+ allow: {
116
+ view: "isOwner",
117
+ create: "false",
118
+ delete: "false",
119
+ update: "isOwner && (newData.email == data.email) && (newData.emailVerified == data.emailVerified) && (newData.createdAt == data.createdAt)",
120
+ },
121
+ },
122
+ accounts: {
123
+ bind: ["isOwner", "auth.id != null && auth.id == data.userId"],
124
+ allow: {
125
+ view: "isOwner",
126
+ create: "false",
127
+ delete: "false",
128
+ update: "false",
129
+ },
130
+ },
131
+ sessions: {
132
+ bind: ["isOwner", "auth.id != null && auth.id == data.userId"],
133
+ allow: {
134
+ view: "isOwner",
135
+ create: "false",
136
+ delete: "false",
137
+ update: "false",
138
+ },
139
+ },
140
+ verifications: {
141
+ allow: {
142
+ $default: "false"
143
+ }
144
+ },
145
+ // Optional permissions (public profile example)
146
+ profiles: {
147
+ bind: ["isOwner", "auth.id != null && auth.id == data.id"],
148
+ allow: {
149
+ view: "true",
150
+ create: "false",
151
+ delete: "false",
152
+ update: "isOwner",
153
+ },
154
+ },
155
+ // Add your custom entity permissions here
156
+ } satisfies InstantRules;
157
+
158
+ export default rules;
159
+ ```
160
+
161
+ ### 3. Push Schema and Permissions to InstantDB
162
+
163
+ After creating these files, use the InstantDB CLI to push them to your app:
164
+
165
+ ```bash
166
+ # Push schema
167
+ npx instant-cli@latest push schema
168
+
169
+ # Push permissions
170
+ npx instant-cli@latest push perms
171
+ ```
172
+
173
+ ### 4. Initialize InstantDB with Your Schema
174
+
175
+ Update your client-side InstantDB initialization to use your schema:
176
+
177
+ #### /database/instant.ts
178
+ ```typescript
179
+ import { init } from "@instantdb/react"
180
+ import schema from "../../instant.schema"
181
+
182
+ export const db = init({
183
+ appId: process.env.NEXT_PUBLIC_INSTANT_APP_ID,
184
+ schema
185
+ })
186
+ ```
187
+
188
+ ## API Reference
189
+
190
+ ### `instantDBAdapter(options)`
191
+
192
+ Creates an adapter that allows Better Auth to use InstantDB as its database.
193
+
194
+ #### Options
195
+
196
+ | Option | Type | Default | Description |
197
+ |--------|------|---------|-------------|
198
+ | `db` | `InstantAdminDatabase` | (required) | An InstantDB admin client instance |
199
+ | `usePlural` | `boolean` | `true` | Set to `false` if your schema uses singular table names |
200
+ | `debugLogs` | `boolean` | `false` | Set to `true` to enable detailed logging |
201
+
202
+ ### `useInstantAuth({ db, useSession })`
203
+
204
+ A React hook that synchronizes authentication state between Better Auth and InstantDB.
205
+
206
+ #### Parameters
207
+
208
+ | Parameter | Type | Description |
209
+ |-----------|------|-------------|
210
+ | `db` | `InstantReactWebDatabase` | An InstantDB client instance |
211
+ | `authClient` | `AuthClient` | The `authClient` from Better Auth |
212
+ | `persistent` | `boolean?` | Whether to enable offline persistence for session |
213
+
214
+ ## License
215
+
216
+ MIT
@@ -0,0 +1,9 @@
1
+ import type { BetterAuthDBSchema } from "@better-auth/core/db";
2
+ /**
3
+ * Creates InstantDB links from Better Auth schema references
4
+ */
5
+ export declare function createLinks(tables: BetterAuthDBSchema, usePlural: boolean): Record<string, any>;
6
+ /**
7
+ * Creates an InstantDB schema file from Better Auth schema format
8
+ */
9
+ export declare function createSchema(tables: BetterAuthDBSchema, usePlural: boolean): string;
@@ -0,0 +1,178 @@
1
+ import { fieldNameToLabel } from "../lib/utils";
2
+ /**
3
+ * Converts a Better Auth field type to InstantDB field type
4
+ */
5
+ function convertFieldType(field, modelName, fieldKey) {
6
+ const { type, required, unique, sortable } = field;
7
+ // Handle type as string or array
8
+ const typeStr = Array.isArray(type) ? type[0] : type;
9
+ let fieldType;
10
+ switch (typeStr) {
11
+ case "string":
12
+ fieldType = "i.string()";
13
+ break;
14
+ case "boolean":
15
+ fieldType = "i.boolean()";
16
+ break;
17
+ case "date":
18
+ fieldType = "i.date()";
19
+ break;
20
+ case "number":
21
+ fieldType = "i.number()";
22
+ break;
23
+ case "json":
24
+ fieldType = "i.json()";
25
+ break;
26
+ case "number[]":
27
+ fieldType = "i.json()";
28
+ break;
29
+ case "string[]":
30
+ fieldType = "i.json()";
31
+ break;
32
+ default:
33
+ fieldType = "i.string()"; // Default to string for unknown types
34
+ }
35
+ // Apply modifiers
36
+ if (unique) {
37
+ fieldType += ".unique()";
38
+ }
39
+ // Only make optional if required is explicitly false
40
+ // If required is true, never make it optional (even if defaultValue exists)
41
+ if (required === false) {
42
+ fieldType += ".optional()";
43
+ }
44
+ // Add indexed if sortable
45
+ if (sortable) {
46
+ fieldType += ".indexed()";
47
+ }
48
+ // For user model, ensure all fields except email end with optional()
49
+ if (modelName === "user" &&
50
+ fieldKey !== "email" &&
51
+ !fieldType.endsWith(".optional()")) {
52
+ fieldType += ".optional()";
53
+ }
54
+ return fieldType;
55
+ }
56
+ /**
57
+ * Gets the InstantDB entity name for a given model name
58
+ */
59
+ function getEntityName(modelName, tableKey, usePlural) {
60
+ if (modelName === "user") {
61
+ return "$users";
62
+ }
63
+ return usePlural ? `${tableKey}s` : tableKey;
64
+ }
65
+ /**
66
+ * Converts a table/model name to camelCase for link names
67
+ */
68
+ function toCamelCase(str) {
69
+ return str.charAt(0).toLowerCase() + str.slice(1);
70
+ }
71
+ /**
72
+ * Creates InstantDB links from Better Auth schema references
73
+ */
74
+ export function createLinks(tables, usePlural) {
75
+ const links = {};
76
+ const entityNameMap = {};
77
+ // First pass: build entity name mapping
78
+ for (const [key, table] of Object.entries(tables)) {
79
+ const { modelName } = table;
80
+ entityNameMap[modelName] = getEntityName(modelName, key, usePlural);
81
+ }
82
+ // Second pass: find all references and create links
83
+ for (const [key, table] of Object.entries(tables)) {
84
+ const { modelName, fields } = table;
85
+ const sourceEntityName = getEntityName(modelName, key, usePlural);
86
+ for (const [fieldKey, field] of Object.entries(fields)) {
87
+ const { references } = field;
88
+ if (references) {
89
+ const { model: targetModel, onDelete } = references;
90
+ const targetEntityName = entityNameMap[targetModel];
91
+ if (!targetEntityName) {
92
+ console.warn(`Warning: Could not find entity name for model "${targetModel}" referenced by ${modelName}.${fieldKey}`);
93
+ continue;
94
+ }
95
+ // Generate forward label from field name, using target model if field doesn't end with "id"
96
+ const forwardLabel = fieldNameToLabel(fieldKey, targetModel);
97
+ // Generate link name: {sourceTable}{forwardLabel}
98
+ // e.g., "invitations" + "Inviter" -> "invitationsInviter"
99
+ const sourceTableName = sourceEntityName.replace("$", "");
100
+ const forwardLabelCapitalized = forwardLabel.charAt(0).toUpperCase() +
101
+ toCamelCase(forwardLabel.slice(1));
102
+ const linkName = `${sourceTableName}${forwardLabelCapitalized}`;
103
+ // Generate reverse label (use source entity name without $ prefix)
104
+ const reverseLabel = sourceEntityName.replace("$", "");
105
+ // Create link definition
106
+ links[linkName] = {
107
+ forward: {
108
+ on: sourceEntityName,
109
+ has: "one",
110
+ label: forwardLabel,
111
+ onDelete: onDelete || "cascade"
112
+ },
113
+ reverse: {
114
+ on: targetEntityName,
115
+ has: "many",
116
+ label: reverseLabel
117
+ }
118
+ };
119
+ }
120
+ }
121
+ }
122
+ return links;
123
+ }
124
+ /**
125
+ * Creates an InstantDB schema file from Better Auth schema format
126
+ */
127
+ export function createSchema(tables, usePlural) {
128
+ const entities = {};
129
+ for (const [key, table] of Object.entries(tables)) {
130
+ const { modelName, fields } = table;
131
+ // For other tables, use the key as entity name
132
+ const entityFields = [];
133
+ for (const [fieldKey, field] of Object.entries(fields)) {
134
+ const fieldType = convertFieldType(field, modelName, fieldKey);
135
+ entityFields.push(`${fieldKey}: ${fieldType}`);
136
+ }
137
+ // Pluralize table name if usePlural is true
138
+ const namespace = modelName === "user" ? "$users" : usePlural ? `${key}s` : key;
139
+ entities[namespace] =
140
+ `i.entity({\n ${entityFields.join(",\n ")}\n })`;
141
+ }
142
+ // Generate links from references
143
+ const links = createLinks(tables, usePlural);
144
+ // Generate the schema file content
145
+ const entitiesString = Object.entries(entities)
146
+ .map(([name, definition]) => ` ${name}: ${definition}`)
147
+ .join(",\n");
148
+ // Format links as string
149
+ const linksString = Object.entries(links)
150
+ .map(([linkName, linkDef]) => {
151
+ const forward = linkDef.forward;
152
+ const reverse = linkDef.reverse;
153
+ return ` ${linkName}: {
154
+ forward: {
155
+ on: "${forward.on}",
156
+ has: "${forward.has}",
157
+ label: "${forward.label}"${forward.onDelete ? `,\n onDelete: "${forward.onDelete}"` : ""}
158
+ },
159
+ reverse: {
160
+ on: "${reverse.on}",
161
+ has: "${reverse.has}",
162
+ label: "${reverse.label}"
163
+ }
164
+ }`;
165
+ })
166
+ .join(",\n");
167
+ const linksSection = linksString ? `,\n links: {\n${linksString}\n }` : "";
168
+ return `// Docs: https://www.instantdb.com/docs/modeling-data
169
+
170
+ import { i } from "@instantdb/react"
171
+
172
+ export const authSchema = i.schema({
173
+ entities: {
174
+ ${entitiesString}
175
+ }${linksSection}
176
+ })
177
+ `;
178
+ }
@@ -0,0 +1,25 @@
1
+ import { type InstantAdminDatabase } from "@instantdb/admin";
2
+ import { type DBAdapterDebugLogOption, type Where } from "better-auth/adapters";
3
+ /**
4
+ * The InstantDB adapter config options.
5
+ */
6
+ interface InstantAdapterConfig {
7
+ /**
8
+ * The InstantDB admin database instance.
9
+ */
10
+ db: InstantAdminDatabase<any, any>;
11
+ /**
12
+ * If the table names in the schema are plural.
13
+ */
14
+ usePlural?: boolean;
15
+ /**
16
+ * Helps you debug issues with the adapter.
17
+ */
18
+ debugLogs?: DBAdapterDebugLogOption;
19
+ }
20
+ /**
21
+ * The InstantDB adapter.
22
+ */
23
+ export declare const instantAdapter: ({ db, usePlural, debugLogs }: InstantAdapterConfig) => import("better-auth/adapters").AdapterFactory;
24
+ export declare function parseWhere(where?: Where[]): Record<string, unknown>;
25
+ export {};