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.
- package/LICENSE +21 -0
- package/README.md +216 -0
- package/dist/adapter/create-schema.d.ts +9 -0
- package/dist/adapter/create-schema.js +178 -0
- package/dist/adapter/instant-adapter.d.ts +25 -0
- package/dist/adapter/instant-adapter.js +273 -0
- package/dist/client-plugin.d.ts +2143 -0
- package/dist/client-plugin.js +21 -0
- package/dist/create-schema.d.ts +25 -0
- package/dist/create-schema.js +115 -0
- package/dist/create-schema.js.map +1 -0
- package/dist/index.d.mts +18 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +160 -0
- package/dist/instant-adapter.d.ts +26 -0
- package/dist/instant-adapter.js +214 -0
- package/dist/instant-adapter.js.map +1 -0
- package/dist/instant-auth.d.ts +3 -0
- package/dist/instant-auth.js +9 -0
- package/dist/lib/instant-auth.d.ts +3 -0
- package/dist/lib/instant-auth.js +9 -0
- package/dist/lib/utils.d.ts +12 -0
- package/dist/lib/utils.js +22 -0
- package/dist/metafile-cjs.json +1 -0
- package/dist/metafile-esm.json +1 -0
- package/dist/react/client-plugin.d.ts +2143 -0
- package/dist/react/client-plugin.js +21 -0
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.js +4 -0
- package/dist/react/instant-auth.d.ts +7 -0
- package/dist/react/instant-auth.js +5 -0
- package/dist/react/react.d.ts +2 -0
- package/dist/react/react.js +2 -0
- package/dist/react/types.d.ts +6 -0
- package/dist/react/types.js +1 -0
- package/dist/react/use-hydrated.d.ts +1 -0
- package/dist/react/use-hydrated.js +7 -0
- package/dist/react/use-instant-auth.d.ts +8 -0
- package/dist/react/use-instant-auth.js +13 -0
- package/dist/react/use-instant-session.d.ts +32 -0
- package/dist/react/use-instant-session.js +25 -0
- package/dist/react/use-persistent-session.d.ts +27 -0
- package/dist/react/use-persistent-session.js +49 -0
- package/dist/react/use-session.d.ts +0 -0
- package/dist/react/use-session.js +1 -0
- package/dist/react/with-instant.d.ts +3 -0
- package/dist/react/with-instant.js +47 -0
- package/dist/react.d.ts +2 -0
- package/dist/react.js +2 -0
- package/dist/shared/instant-auth.d.ts +4 -0
- package/dist/shared/instant-auth.js +9 -0
- package/dist/utils.d.ts +6 -0
- package/dist/utils.js +9 -0
- package/package.json +70 -0
- package/src/adapter/create-schema.ts +232 -0
- package/src/adapter/instant-adapter.ts +422 -0
- package/src/index.ts +2 -0
- package/src/lib/utils.ts +24 -0
- package/src/react/index.ts +4 -0
- package/src/react/instant-auth.tsx +17 -0
- package/src/react/types.ts +9 -0
- package/src/react/use-hydrated.ts +13 -0
- package/src/react/use-instant-auth.ts +28 -0
- package/src/react/use-instant-session.ts +46 -0
- package/src/react/use-persistent-session.ts +64 -0
- 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
|
+
}
|