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,273 @@
1
+ import { id } from "@instantdb/admin";
2
+ import { createAdapterFactory } from "better-auth/adapters";
3
+ import { fieldNameToLabel, prettyObject } from "../lib/utils";
4
+ import { createSchema } from "./create-schema";
5
+ /**
6
+ * Gets the InstantDB entity name for a given model name
7
+ */
8
+ function getEntityName(modelName, tableKey, usePlural) {
9
+ if (modelName === "user") {
10
+ return "$users";
11
+ }
12
+ return usePlural ? `${tableKey}s` : tableKey;
13
+ }
14
+ /**
15
+ * Builds entity name mapping from schema
16
+ */
17
+ function buildEntityNameMap(schema, usePlural) {
18
+ const entityNameMap = {};
19
+ for (const [key, table] of Object.entries(schema)) {
20
+ const { modelName } = table;
21
+ entityNameMap[modelName] = getEntityName(modelName, key, usePlural);
22
+ }
23
+ return entityNameMap;
24
+ }
25
+ /**
26
+ * Creates link transactions for fields with references
27
+ */
28
+ function createLinkTransactions({ db, model, modelSchema, data, entityNameMap }) {
29
+ const linkTransactions = [];
30
+ const { fields, modelName } = modelSchema;
31
+ for (const [fieldKey, field] of Object.entries(fields)) {
32
+ const { references } = field;
33
+ if (references) {
34
+ const { model: targetModel } = references;
35
+ const targetEntityName = entityNameMap[targetModel];
36
+ if (!targetEntityName) {
37
+ console.warn(`Warning: Could not find entity name for model "${targetModel}" referenced by ${modelName}.${fieldKey}`);
38
+ continue;
39
+ }
40
+ // Check if data has a value for this reference field
41
+ const fieldValue = data[fieldKey];
42
+ if (fieldValue != null) {
43
+ // Generate forward label from field name, using target model if field doesn't end with "id"
44
+ const forwardLabel = fieldNameToLabel(fieldKey, targetModel);
45
+ // Create link transaction
46
+ const linkParams = {
47
+ [forwardLabel]: fieldValue
48
+ };
49
+ const linkTransaction = db.tx[model][data.id].link(linkParams);
50
+ linkTransactions.push(linkTransaction);
51
+ }
52
+ }
53
+ }
54
+ return linkTransactions;
55
+ }
56
+ /**
57
+ * The InstantDB adapter.
58
+ */
59
+ export const instantAdapter = ({ db, usePlural = true, debugLogs = false }) => {
60
+ return createAdapterFactory({
61
+ config: {
62
+ customIdGenerator: id,
63
+ adapterId: "instantdb-adapter", // A unique identifier for the adapter.
64
+ adapterName: "InstantDB Adapter", // The name of the adapter.
65
+ usePlural, // Whether the table names in the schema are plural.
66
+ debugLogs, // Whether to enable debug logs.
67
+ supportsJSON: true, // Whether the database supports JSON. (Default: false)
68
+ supportsDates: false, // Whether the database supports dates. (Default: true)
69
+ supportsBooleans: true, // Whether the database supports booleans. (Default: true)
70
+ supportsNumericIds: false // Whether the database supports auto-incrementing numeric IDs. (Default: true)
71
+ },
72
+ adapter: ({ debugLog, getDefaultModelName, getFieldName, schema }) => {
73
+ return {
74
+ create: async ({ data, model }) => {
75
+ const defaultModelName = getDefaultModelName(model);
76
+ const modelSchema = schema[defaultModelName];
77
+ // Create the InstantDB token and override session.token
78
+ if (defaultModelName === "session") {
79
+ // Get the $users entity for this session's userId
80
+ const result = await db.query({
81
+ $users: { $: { where: { id: data.userId } } }
82
+ });
83
+ const $users = result.$users;
84
+ if (!$users.length) {
85
+ throw new Error(`$users entity not found: ${data.userId}`);
86
+ }
87
+ const $user = $users[0];
88
+ // Create the InstantDB token and override session.token
89
+ debugLog("Create Token", $user.email);
90
+ const token = await db.auth.createToken($user.email);
91
+ const tokenField = getFieldName({ model, field: "token" });
92
+ Object.assign(data, { [tokenField]: token });
93
+ }
94
+ if (defaultModelName === "user") {
95
+ model = "$users";
96
+ }
97
+ debugLog("Create", model, prettyObject(data));
98
+ // Build entity name map for link resolution
99
+ const entityNameMap = buildEntityNameMap(schema, usePlural);
100
+ // Create the main entity transaction
101
+ const createTransaction = db.tx[model][data.id].create(data);
102
+ // Create link transactions for fields with references
103
+ const linkTransactions = createLinkTransactions({
104
+ db,
105
+ model,
106
+ modelSchema,
107
+ data,
108
+ entityNameMap
109
+ });
110
+ // Combine all transactions and execute in a single transaction
111
+ const allTransactions = [createTransaction, ...linkTransactions];
112
+ await db.transact(allTransactions);
113
+ return data;
114
+ },
115
+ update: async ({ update, model, where }) => {
116
+ if (getDefaultModelName(model) === "user") {
117
+ model = "$users";
118
+ }
119
+ const entities = await fetchEntities({ db, model, where, debugLog });
120
+ if (!entities.length)
121
+ return null;
122
+ debugLog("Update:", entities.map((entity) => entity.id), prettyObject(update));
123
+ const transactions = entities.map((entity) => db.tx[model][entity.id].update(update));
124
+ await db.transact(transactions);
125
+ return { ...entities[0], ...update };
126
+ },
127
+ updateMany: async ({ update, model, where }) => {
128
+ if (getDefaultModelName(model) === "user") {
129
+ model = "$users";
130
+ }
131
+ const entities = await fetchEntities({ db, model, where, debugLog });
132
+ if (!entities.length)
133
+ return 0;
134
+ debugLog("Update:", entities.map((entity) => entity.id), prettyObject(update));
135
+ const transactions = entities.map((entity) => db.tx[model][entity.id].update(update));
136
+ await db.transact(transactions);
137
+ return entities.length;
138
+ },
139
+ delete: async ({ model, where }) => {
140
+ if (getDefaultModelName(model) === "user") {
141
+ model = "$users";
142
+ }
143
+ const entities = await fetchEntities({ db, model, where, debugLog });
144
+ if (!entities.length)
145
+ return;
146
+ const transactions = entities.map((entity) => db.tx[model][entity.id].delete());
147
+ await db.transact(transactions);
148
+ if (getDefaultModelName(model) === "session") {
149
+ Promise.all(entities.map(async (entity) => {
150
+ try {
151
+ const tokenField = getFieldName({ model, field: "token" });
152
+ await db.auth.signOut({
153
+ refresh_token: entity[tokenField]
154
+ });
155
+ }
156
+ catch (_a) { }
157
+ }));
158
+ }
159
+ },
160
+ deleteMany: async ({ model, where }) => {
161
+ if (getDefaultModelName(model) === "user") {
162
+ model = "$users";
163
+ }
164
+ const entities = await fetchEntities({ db, model, where, debugLog });
165
+ if (!entities.length)
166
+ return 0;
167
+ const transactions = entities.map((entity) => db.tx[model][entity.id].delete());
168
+ await db.transact(transactions);
169
+ if (getDefaultModelName(model) === "session") {
170
+ Promise.all(entities.map(async (entity) => {
171
+ try {
172
+ const tokenField = getFieldName({ model, field: "token" });
173
+ await db.auth.signOut({
174
+ refresh_token: entity[tokenField]
175
+ });
176
+ }
177
+ catch (_a) { }
178
+ }));
179
+ }
180
+ return entities.length;
181
+ },
182
+ findOne: async ({ model, where }) => {
183
+ if (getDefaultModelName(model) === "user") {
184
+ model = "$users";
185
+ }
186
+ const entities = await fetchEntities({ db, model, where, debugLog });
187
+ if (entities.length)
188
+ return entities[0];
189
+ return null;
190
+ },
191
+ findMany: async ({ model, where, limit, sortBy, offset }) => {
192
+ if (getDefaultModelName(model) === "user") {
193
+ model = "$users";
194
+ }
195
+ const entities = await fetchEntities({
196
+ db,
197
+ model,
198
+ where,
199
+ limit,
200
+ sortBy,
201
+ offset,
202
+ debugLog
203
+ });
204
+ return entities;
205
+ },
206
+ count: async ({ model, where }) => {
207
+ if (getDefaultModelName(model) === "user") {
208
+ model = "$users";
209
+ }
210
+ const entities = await fetchEntities({ db, model, where, debugLog });
211
+ return entities.length;
212
+ },
213
+ createSchema: async ({ file = "./auth.schema.ts", tables }) => {
214
+ const code = createSchema(tables, usePlural);
215
+ return { code, path: file };
216
+ }
217
+ };
218
+ }
219
+ });
220
+ };
221
+ async function fetchEntities({ db, debugLog, model, where, limit, offset, sortBy }) {
222
+ let order;
223
+ if (sortBy) {
224
+ order = {
225
+ [sortBy.field]: sortBy.direction
226
+ };
227
+ }
228
+ const query = {
229
+ [model]: { $: { where: parseWhere(where), limit, offset, order } }
230
+ };
231
+ debugLog("Query", prettyObject(query));
232
+ const result = await db.query(query);
233
+ debugLog("Result", prettyObject(result));
234
+ return result[model];
235
+ }
236
+ export function parseWhere(where) {
237
+ const whereQuery = {};
238
+ where === null || where === void 0 ? void 0 : where.forEach((item) => {
239
+ switch (item.operator) {
240
+ case "eq":
241
+ whereQuery[item.field] = item.value;
242
+ break;
243
+ case "in":
244
+ whereQuery[item.field] = { $in: item.value };
245
+ break;
246
+ case "contains":
247
+ whereQuery[item.field] = { $like: `%${item.value}%` };
248
+ break;
249
+ case "starts_with":
250
+ whereQuery[item.field] = { $like: `${item.value}%` };
251
+ break;
252
+ case "ends_with":
253
+ whereQuery[item.field] = { $like: `%${item.value}` };
254
+ break;
255
+ case "ne":
256
+ whereQuery[item.field] = { $not: item.value };
257
+ break;
258
+ case "gt":
259
+ whereQuery[item.field] = { $gt: item.value };
260
+ break;
261
+ case "gte":
262
+ whereQuery[item.field] = { $gte: item.value };
263
+ break;
264
+ case "lt":
265
+ whereQuery[item.field] = { $lt: item.value };
266
+ break;
267
+ case "lte":
268
+ whereQuery[item.field] = { $lte: item.value };
269
+ break;
270
+ }
271
+ });
272
+ return whereQuery;
273
+ }