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,25 @@
1
+ import type { DBAdapterSchemaCreation } from "better-auth/adapters";
2
+ type BetterAuthField = {
3
+ type: string | string[];
4
+ required?: boolean;
5
+ unique?: boolean;
6
+ defaultValue?: unknown;
7
+ input?: boolean;
8
+ sortable?: boolean;
9
+ fieldName?: string;
10
+ references?: {
11
+ model: string;
12
+ field: string;
13
+ onDelete?: string;
14
+ };
15
+ };
16
+ type BetterAuthTable = {
17
+ modelName: string;
18
+ fields: Record<string, BetterAuthField>;
19
+ order?: number;
20
+ };
21
+ /**
22
+ * Creates an InstantDB schema file from Better Auth schema format
23
+ */
24
+ export declare function createSchema(file: string, tables: Record<string, BetterAuthTable>, usePlural: boolean): DBAdapterSchemaCreation;
25
+ export {};
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Converts a Better Auth field type to InstantDB field type
3
+ */
4
+ function convertFieldType(field) {
5
+ const { type, required, unique, sortable } = field;
6
+ // Handle type as string or array
7
+ const typeStr = Array.isArray(type) ? type[0] : type;
8
+ let fieldType = "";
9
+ switch (typeStr) {
10
+ case "string":
11
+ fieldType = "i.string()";
12
+ break;
13
+ case "boolean":
14
+ fieldType = "i.boolean()";
15
+ break;
16
+ case "date":
17
+ fieldType = "i.date()";
18
+ break;
19
+ case "number":
20
+ fieldType = "i.number()";
21
+ break;
22
+ default:
23
+ fieldType = "i.string()"; // Default to string for unknown types
24
+ }
25
+ // Apply modifiers
26
+ if (unique) {
27
+ fieldType += ".unique()";
28
+ }
29
+ // Only make optional if required is explicitly false
30
+ // If required is true, never make it optional (even if defaultValue exists)
31
+ if (required === false) {
32
+ fieldType += ".optional()";
33
+ }
34
+ // Add indexed if sortable
35
+ if (sortable) {
36
+ fieldType += ".indexed()";
37
+ }
38
+ return fieldType;
39
+ }
40
+ /**
41
+ * Converts Better Auth schema format to InstantDB schema format
42
+ */
43
+ function convertToInstantDBSchema(tables, usePlural) {
44
+ const entities = {};
45
+ for (const [key, table] of Object.entries(tables)) {
46
+ const { modelName, fields } = table;
47
+ // Special handling for user table
48
+ if (modelName === "user") {
49
+ const userFields = [];
50
+ const processedFields = new Set();
51
+ // Always add email, imageURL, and type at the top (exact format required)
52
+ userFields.push("email: i.string().unique().indexed().optional()");
53
+ processedFields.add("email");
54
+ userFields.push("imageURL: i.string().optional()");
55
+ processedFields.add("imageURL"); // Skip imageURL if it exists in fields
56
+ userFields.push("type: i.string().optional()");
57
+ processedFields.add("type"); // Skip type if it exists in fields
58
+ // Add all other fields from the schema (including image as-is, don't transform it)
59
+ // All Better Auth fields must be optional on $users
60
+ for (const [fieldKey, field] of Object.entries(fields)) {
61
+ // Skip fields that are always included at the top: email, imageURL, type
62
+ if (processedFields.has(fieldKey)) {
63
+ continue;
64
+ }
65
+ // Add field as-is but force it to be optional
66
+ const fieldType = convertFieldType(field);
67
+ // Ensure it ends with .optional() - remove existing .optional() if present and add it
68
+ const optionalFieldType = fieldType.endsWith(".optional()")
69
+ ? fieldType
70
+ : `${fieldType}.optional()`;
71
+ userFields.push(`${fieldKey}: ${optionalFieldType}`);
72
+ }
73
+ entities.$users = `i.entity({\n ${userFields.join(",\n ")}\n })`;
74
+ }
75
+ else {
76
+ // For other tables, use the key as entity name
77
+ const entityFields = [];
78
+ for (const [fieldKey, field] of Object.entries(fields)) {
79
+ const fieldType = convertFieldType(field);
80
+ entityFields.push(`${fieldKey}: ${fieldType}`);
81
+ }
82
+ // Pluralize table name if usePlural is true
83
+ const entityName = usePlural ? `${key}s` : key;
84
+ entities[entityName] =
85
+ `i.entity({\n ${entityFields.join(",\n ")}\n })`;
86
+ }
87
+ }
88
+ // Generate the schema file content
89
+ const entitiesString = Object.entries(entities)
90
+ .map(([name, definition]) => ` ${name}: ${definition}`)
91
+ .join(",\n");
92
+ return `// Docs: https://www.instantdb.com/docs/modeling-data
93
+
94
+ import { i } from "@instantdb/react"
95
+
96
+ export const authSchema = i.schema({
97
+ entities: {
98
+ ${entitiesString}
99
+ }
100
+ })
101
+ `;
102
+ }
103
+ /**
104
+ * Creates an InstantDB schema file from Better Auth schema format
105
+ */
106
+ export function createSchema(file, tables, usePlural) {
107
+ const schemaContent = convertToInstantDBSchema(tables, usePlural);
108
+ // const filePath = resolve(process.cwd(), file)
109
+ // writeFileSync(file, schemaContent, "utf-8")
110
+ //console.log(`Schema file created at: ${filePath}`)
111
+ return {
112
+ code: schemaContent,
113
+ path: file
114
+ };
115
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-schema.js","sourceRoot":"","sources":["../src/create-schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import * as better_auth_adapters from 'better-auth/adapters';
2
+ import { DBAdapterDebugLogOption } from 'better-auth/adapters';
3
+ import { InstantAdminDatabase } from '@instantdb/admin';
4
+
5
+ interface InstantAdapterConfig {
6
+ /**
7
+ * Helps you debug issues with the adapter.
8
+ */
9
+ debugLogs?: DBAdapterDebugLogOption;
10
+ /**
11
+ * If the table names in the schema are plural.
12
+ */
13
+ usePlural?: boolean;
14
+ db: InstantAdminDatabase<any>;
15
+ }
16
+ declare const instantAdapter: (config: InstantAdapterConfig) => better_auth_adapters.AdapterFactory;
17
+
18
+ export { instantAdapter };
@@ -0,0 +1,2 @@
1
+ export * from "./adapter/instant-adapter";
2
+ export * from "./shared/instant-auth";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./adapter/instant-adapter";
2
+ export * from "./shared/instant-auth";
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAiC"}
package/dist/index.mjs ADDED
@@ -0,0 +1,160 @@
1
+ import { createAdapterFactory } from 'better-auth/adapters';
2
+
3
+ // src/instant-adapter.ts
4
+
5
+ // src/create-schema.ts
6
+ function convertFieldType(field) {
7
+ const { type, required, unique, defaultValue, sortable } = field;
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
+ default:
24
+ fieldType = "i.string()";
25
+ }
26
+ if (unique) {
27
+ fieldType += ".unique()";
28
+ }
29
+ if (required === false) {
30
+ fieldType += ".optional()";
31
+ }
32
+ if (sortable) {
33
+ fieldType += ".indexed()";
34
+ }
35
+ return fieldType;
36
+ }
37
+ function convertToInstantDBSchema(tables, usePlural) {
38
+ const entities = {};
39
+ for (const [key, table] of Object.entries(tables)) {
40
+ const { modelName, fields } = table;
41
+ if (modelName === "user") {
42
+ const userFields = [];
43
+ const processedFields = /* @__PURE__ */ new Set();
44
+ userFields.push("email: i.string().unique().indexed()");
45
+ processedFields.add("email");
46
+ userFields.push("imageURL: i.string().optional()");
47
+ processedFields.add("imageURL");
48
+ userFields.push("type: i.string().optional()");
49
+ processedFields.add("type");
50
+ for (const [fieldKey, field] of Object.entries(fields)) {
51
+ if (processedFields.has(fieldKey)) {
52
+ continue;
53
+ }
54
+ const fieldType = convertFieldType(field);
55
+ const optionalFieldType = fieldType.endsWith(".optional()") ? fieldType : `${fieldType}.optional()`;
56
+ userFields.push(`${fieldKey}: ${optionalFieldType}`);
57
+ }
58
+ entities["$users"] = `i.entity({
59
+ ${userFields.join(",\n ")}
60
+ })`;
61
+ } else {
62
+ const entityFields = [];
63
+ for (const [fieldKey, field] of Object.entries(fields)) {
64
+ const fieldType = convertFieldType(field);
65
+ entityFields.push(`${fieldKey}: ${fieldType}`);
66
+ }
67
+ const entityName = usePlural ? `${key}s` : key;
68
+ entities[entityName] = `i.entity({
69
+ ${entityFields.join(",\n ")}
70
+ })`;
71
+ }
72
+ }
73
+ const entitiesString = Object.entries(entities).map(([name, definition]) => ` ${name}: ${definition}`).join(",\n");
74
+ return `// Docs: https://www.instantdb.com/docs/modeling-data
75
+
76
+ import { i } from "@instantdb/react"
77
+
78
+ export const authSchema = i.schema({
79
+ entities: {
80
+ ${entitiesString}
81
+ }
82
+ })
83
+ `;
84
+ }
85
+ function createSchema(file, tables, usePlural = false) {
86
+ const schemaContent = convertToInstantDBSchema(tables, usePlural);
87
+ return {
88
+ code: schemaContent,
89
+ path: file
90
+ };
91
+ }
92
+
93
+ // src/instant-adapter.ts
94
+ var instantAdapter = (config) => {
95
+ var _a, _b;
96
+ const usePlural = (_a = config.usePlural) != null ? _a : false;
97
+ return createAdapterFactory({
98
+ config: {
99
+ adapterId: "instantdb-adapter",
100
+ // A unique identifier for the adapter.
101
+ adapterName: "InstantDB Adapter",
102
+ // The name of the adapter.
103
+ usePlural,
104
+ // Whether the table names in the schema are plural.
105
+ debugLogs: (_b = config.debugLogs) != null ? _b : false,
106
+ // Whether to enable debug logs.
107
+ supportsJSON: true,
108
+ // Whether the database supports JSON. (Default: false)
109
+ supportsDates: true,
110
+ // Whether the database supports dates. (Default: true)
111
+ supportsBooleans: true,
112
+ // Whether the database supports booleans. (Default: true)
113
+ supportsNumericIds: false
114
+ // Whether the database supports auto-incrementing numeric IDs. (Default: true)
115
+ },
116
+ adapter: ({
117
+ options,
118
+ schema,
119
+ debugLog,
120
+ getDefaultModelName,
121
+ getDefaultFieldName
122
+ }) => {
123
+ return {
124
+ create: async ({ data, model, select }) => {
125
+ if (getDefaultModelName(model) === "user") {
126
+ console.log("create user", data);
127
+ }
128
+ throw new Error("Not implemented");
129
+ },
130
+ update: async ({ update, model, where }) => {
131
+ return null;
132
+ },
133
+ updateMany: async ({ update, model, where }) => {
134
+ return 0;
135
+ },
136
+ delete: async ({ model, where }) => {
137
+ return;
138
+ },
139
+ deleteMany: async ({ model, where }) => {
140
+ return 0;
141
+ },
142
+ findOne: async ({ model, where, select }) => {
143
+ return null;
144
+ },
145
+ findMany: async ({ model, where, limit, sortBy, offset }) => {
146
+ return [];
147
+ },
148
+ count: async ({ model, where }) => {
149
+ return 0;
150
+ },
151
+ createSchema: async ({ file, tables }) => {
152
+ file != null ? file : file = "./auth.schema.ts";
153
+ return createSchema(file, tables, usePlural);
154
+ }
155
+ };
156
+ }
157
+ });
158
+ };
159
+
160
+ export { instantAdapter };
@@ -0,0 +1,26 @@
1
+ /** biome-ignore-all lint/suspicious/noExplicitAny: any thing goes */
2
+ import { type InstantAdminDatabase } from "@instantdb/admin";
3
+ import { type DBAdapterDebugLogOption, type Where } from "better-auth/adapters";
4
+ /**
5
+ * The InstantDB adapter config options.
6
+ */
7
+ interface InstantAdapterConfig {
8
+ /**
9
+ * The InstantDB admin database instance.
10
+ */
11
+ db: InstantAdminDatabase<any, any>;
12
+ /**
13
+ * If the table names in the schema are plural.
14
+ */
15
+ usePlural?: boolean;
16
+ /**
17
+ * Helps you debug issues with the adapter.
18
+ */
19
+ debugLogs?: DBAdapterDebugLogOption;
20
+ }
21
+ /**
22
+ * The InstantDB adapter.
23
+ */
24
+ export declare const instantAdapter: ({ db, usePlural, debugLogs }: InstantAdapterConfig) => import("better-auth/adapters").AdapterFactory;
25
+ export declare function parseWhere(where?: Where[]): Record<string, unknown>;
26
+ export {};
@@ -0,0 +1,214 @@
1
+ /** biome-ignore-all lint/suspicious/noExplicitAny: any thing goes */
2
+ import { id } from "@instantdb/admin";
3
+ import { createAdapterFactory } from "better-auth/adapters";
4
+ import { createSchema } from "./create-schema";
5
+ import { prettyPrint } from "./utils";
6
+ /**
7
+ * The InstantDB adapter.
8
+ */
9
+ export const instantAdapter = ({ db, usePlural = true, debugLogs = false }) => {
10
+ return createAdapterFactory({
11
+ config: {
12
+ customIdGenerator: id,
13
+ adapterId: "instantdb-adapter", // A unique identifier for the adapter.
14
+ adapterName: "InstantDB Adapter", // The name of the adapter.
15
+ usePlural, // Whether the table names in the schema are plural.
16
+ debugLogs, // Whether to enable debug logs.
17
+ supportsJSON: true, // Whether the database supports JSON. (Default: false)
18
+ supportsDates: false, // Whether the database supports dates. (Default: true)
19
+ supportsBooleans: true, // Whether the database supports booleans. (Default: true)
20
+ supportsNumericIds: false // Whether the database supports auto-incrementing numeric IDs. (Default: true)
21
+ },
22
+ adapter: ({ debugLog, getDefaultModelName, getFieldName }) => {
23
+ return {
24
+ create: async ({ data, model }) => {
25
+ // Create the InstantDB token and override session.token
26
+ if (getDefaultModelName(model) === "session") {
27
+ // Get the $users entity for this session's userId with the user link
28
+ const result = await db.query({
29
+ $users: { $: { where: { id: data.userId } } }
30
+ });
31
+ const $users = result.$users;
32
+ if (!$users.length) {
33
+ throw new Error(`$users entity not found: ${data.userId}`);
34
+ }
35
+ const $user = $users[0];
36
+ // Create the InstantDB token and override session.token
37
+ debugLog("Create Token", $user.email);
38
+ const token = await db.auth.createToken($user.email);
39
+ const tokenField = getFieldName({ model, field: "token" });
40
+ // @ts-expect-error
41
+ data[tokenField] = token;
42
+ }
43
+ if (getDefaultModelName(model) === "user") {
44
+ model = "$users";
45
+ }
46
+ debugLog("Create", model, prettyPrint(data));
47
+ await db.transact([db.tx[model][data.id].create(data)]);
48
+ return data;
49
+ },
50
+ update: async ({ update, model, where }) => {
51
+ if (getDefaultModelName(model) === "user") {
52
+ model = "$users";
53
+ }
54
+ const query = { [model]: { $: { where: parseWhere(where) } } };
55
+ debugLog("Query", prettyPrint(query));
56
+ const result = await db.query(query);
57
+ debugLog("Result", prettyPrint(result));
58
+ const entities = result[model];
59
+ if (!entities.length)
60
+ return null;
61
+ debugLog("Update:", entities.map((entity) => entity.id), prettyPrint(update));
62
+ const transactions = entities.map((entity) => db.tx[model][entity.id].update(update));
63
+ await db.transact(transactions);
64
+ return { ...entities[0], ...update };
65
+ },
66
+ updateMany: async ({ update, model, where }) => {
67
+ if (getDefaultModelName(model) === "user") {
68
+ model = "$users";
69
+ }
70
+ const query = { [model]: { $: { where: parseWhere(where) } } };
71
+ debugLog("Query", prettyPrint(query));
72
+ const result = await db.query(query);
73
+ debugLog("Result", prettyPrint(result));
74
+ const entities = result[model];
75
+ debugLog("Update:", entities.map((entity) => entity.id), prettyPrint(update));
76
+ const transactions = entities.map((entity) => db.tx[model][entity.id].update(update));
77
+ await db.transact(transactions);
78
+ return entities.length;
79
+ },
80
+ delete: async ({ model, where }) => {
81
+ if (getDefaultModelName(model) === "user") {
82
+ model = "$users";
83
+ }
84
+ const query = { [model]: { $: { where: parseWhere(where) } } };
85
+ debugLog("Query", prettyPrint(query));
86
+ const result = await db.query(query);
87
+ debugLog("Result", prettyPrint(result));
88
+ const entities = result[model];
89
+ const transactions = entities.map((entity) => db.tx[model][entity.id].delete());
90
+ await db.transact(transactions);
91
+ if (getDefaultModelName(model) === "session") {
92
+ Promise.all(entities.map(async (entity) => {
93
+ try {
94
+ const tokenField = getFieldName({ model, field: "token" });
95
+ await db.auth.signOut({
96
+ refresh_token: entity[tokenField]
97
+ });
98
+ }
99
+ catch (_a) { }
100
+ }));
101
+ }
102
+ },
103
+ deleteMany: async ({ model, where }) => {
104
+ if (getDefaultModelName(model) === "user") {
105
+ model = "$users";
106
+ }
107
+ const query = { [model]: { $: { where: parseWhere(where) } } };
108
+ debugLog("Query", prettyPrint(query));
109
+ const result = await db.query(query);
110
+ debugLog("Result", prettyPrint(result));
111
+ const entities = result[model];
112
+ const transactions = entities.map((entity) => db.tx[model][entity.id].delete());
113
+ await db.transact(transactions);
114
+ if (getDefaultModelName(model) === "session") {
115
+ Promise.all(entities.map(async (entity) => {
116
+ try {
117
+ const tokenField = getFieldName({ model, field: "token" });
118
+ await db.auth.signOut({
119
+ refresh_token: entity[tokenField]
120
+ });
121
+ }
122
+ catch (_a) { }
123
+ }));
124
+ }
125
+ return entities.length;
126
+ },
127
+ findOne: async ({ model, where }) => {
128
+ if (getDefaultModelName(model) === "user") {
129
+ model = "$users";
130
+ }
131
+ const query = { [model]: { $: { where: parseWhere(where) } } };
132
+ debugLog("Query", prettyPrint(query));
133
+ const result = await db.query(query);
134
+ debugLog("Result", prettyPrint(result));
135
+ const entities = result[model];
136
+ if (entities.length)
137
+ return entities[0];
138
+ return null;
139
+ },
140
+ findMany: async ({ model, where, limit, sortBy, offset }) => {
141
+ if (getDefaultModelName(model) === "user") {
142
+ model = "$users";
143
+ }
144
+ let order;
145
+ if (sortBy) {
146
+ order = {
147
+ [sortBy.field]: sortBy.direction
148
+ };
149
+ }
150
+ const query = {
151
+ [model]: { $: { where: parseWhere(where), limit, offset, order } }
152
+ };
153
+ debugLog("Query", prettyPrint(query));
154
+ const result = await db.query(query);
155
+ debugLog("Result", prettyPrint(result));
156
+ const entities = result[model];
157
+ return entities;
158
+ },
159
+ count: async ({ model, where }) => {
160
+ if (getDefaultModelName(model) === "user") {
161
+ model = "$users";
162
+ }
163
+ const query = { [model]: { $: { where: parseWhere(where) } } };
164
+ debugLog("Query", prettyPrint(query));
165
+ const result = await db.query(query);
166
+ debugLog("Result", prettyPrint(result));
167
+ const entities = result[model];
168
+ return entities.length;
169
+ },
170
+ createSchema: async ({ file, tables }) => {
171
+ return createSchema(file !== null && file !== void 0 ? file : "./auth.schema.ts", tables, usePlural);
172
+ }
173
+ };
174
+ }
175
+ });
176
+ };
177
+ export function parseWhere(where) {
178
+ const whereQuery = {};
179
+ where === null || where === void 0 ? void 0 : where.forEach((item) => {
180
+ switch (item.operator) {
181
+ case "eq":
182
+ whereQuery[item.field] = item.value;
183
+ break;
184
+ case "in":
185
+ whereQuery[item.field] = { $in: item.value };
186
+ break;
187
+ case "contains":
188
+ whereQuery[item.field] = { $like: `%${item.value}%` };
189
+ break;
190
+ case "starts_with":
191
+ whereQuery[item.field] = { $like: `${item.value}%` };
192
+ break;
193
+ case "ends_with":
194
+ whereQuery[item.field] = { $like: `%${item.value}` };
195
+ break;
196
+ case "ne":
197
+ whereQuery[item.field] = { $not: item.value };
198
+ break;
199
+ case "gt":
200
+ whereQuery[item.field] = { $gt: item.value };
201
+ break;
202
+ case "gte":
203
+ whereQuery[item.field] = { $gte: item.value };
204
+ break;
205
+ case "lt":
206
+ whereQuery[item.field] = { $lt: item.value };
207
+ break;
208
+ case "lte":
209
+ whereQuery[item.field] = { $lte: item.value };
210
+ break;
211
+ }
212
+ });
213
+ return whereQuery;
214
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instant-adapter.js","sourceRoot":"","sources":["../src/instant-adapter.ts"],"names":[],"mappings":";;;AACA,mDAG6B;AAC7B,mDAA8C;AAgBvC,MAAM,cAAc,GAAG,CAAC,MAA4B,EAAE,EAAE;;IAC7D,OAAA,IAAA,+BAAoB,EAAC;QACnB,MAAM,EAAE;YACN,SAAS,EAAE,mBAAmB,EAAE,uCAAuC;YACvE,WAAW,EAAE,mBAAmB,EAAE,2BAA2B;YAC7D,SAAS,EAAE,MAAA,MAAM,CAAC,SAAS,mCAAI,KAAK,EAAE,oDAAoD;YAC1F,SAAS,EAAE,MAAA,MAAM,CAAC,SAAS,mCAAI,KAAK,EAAE,gCAAgC;YACtE,YAAY,EAAE,IAAI,EAAE,uDAAuD;YAC3E,aAAa,EAAE,IAAI,EAAE,uDAAuD;YAC5E,gBAAgB,EAAE,IAAI,EAAE,0DAA0D;YAClF,kBAAkB,EAAE,KAAK,CAAC,+EAA+E;SAC1G;QACD,OAAO,EAAE,CAAC,EACR,OAAO,EACP,MAAM,EACN,QAAQ,EACR,mBAAmB,EACnB,mBAAmB,EACpB,EAAE,EAAE;YACH,OAAO;gBACL,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE;oBACxC,IAAI,mBAAmB,CAAC,KAAK,CAAC,KAAK,MAAM,EAAE,CAAC;wBAC1C,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,CAAA;oBAClC,CAAC;oBAED,6CAA6C;oBAC7C,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAA;gBACpC,CAAC;gBACD,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;oBACzC,OAAO,IAAI,CAAA;gBACb,CAAC;gBACD,UAAU,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;oBAC7C,OAAO,CAAC,CAAA;gBACV,CAAC;gBACD,MAAM,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;oBACjC,OAAM;gBACR,CAAC;gBACD,UAAU,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;oBACrC,OAAO,CAAC,CAAA;gBACV,CAAC;gBACD,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE;oBAC1C,OAAO,IAAI,CAAA;gBACb,CAAC;gBACD,QAAQ,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE;oBAC1D,OAAO,EAAE,CAAA;gBACX,CAAC;gBACD,KAAK,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;oBAChC,OAAO,CAAC,CAAA;gBACV,CAAC;gBACD,YAAY,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE;oBACvC,IAAI,aAAJ,IAAI,cAAJ,IAAI,IAAJ,IAAI,GAAK,kBAAkB,EAAA;oBAC3B,OAAO,IAAA,4BAAY,EAAC,IAAI,EAAE,MAAM,CAAC,CAAA;gBACnC,CAAC;aACF,CAAA;QACH,CAAC;KACF,CAAC,CAAA;CAAA,CAAA;AAvDS,QAAA,cAAc,kBAuDvB"}
@@ -0,0 +1,3 @@
1
+ import type { EntitiesDef, InstantReactWebDatabase, InstantSchemaDef, LinksDef, RoomsDef } from "@instantdb/react";
2
+ import type { Session } from "better-auth";
3
+ export declare function instantAuth<TSchema extends InstantSchemaDef<EntitiesDef, LinksDef<EntitiesDef>, RoomsDef>>(db: InstantReactWebDatabase<TSchema>, 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 (user && !session) {
7
+ db.auth.signOut();
8
+ }
9
+ }
@@ -0,0 +1,3 @@
1
+ import type { EntitiesDef, InstantReactWebDatabase, InstantSchemaDef, LinksDef, RoomsDef } from "@instantdb/react";
2
+ import type { Session } from "better-auth";
3
+ export declare function instantAuth<TSchema extends InstantSchemaDef<EntitiesDef, LinksDef<EntitiesDef>, RoomsDef>>(db: InstantReactWebDatabase<TSchema>, 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 (user && !session) {
7
+ db.auth.signOut();
8
+ }
9
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Pretty an object.
3
+ * @param object - The object to pretty.
4
+ * @returns The pretty object.
5
+ */
6
+ export declare function prettyObject(object: unknown): string;
7
+ /**
8
+ * Converts a field name to a relationship label
9
+ * e.g., "userId" -> "user", "organizationId" -> "organization"
10
+ * If field doesn't end with "id", uses the target model name
11
+ */
12
+ export declare function fieldNameToLabel(fieldName: string, targetModel: string): string;