@daveyplate/better-auth-instantdb
Version:
Better Auth InstantDB Adapter
308 lines (305 loc) • 10.7 kB
JavaScript
import { id } from '@instantdb/react';
import { createAdapter } from 'better-auth/adapters';
import { useEffect } from 'react';
// src/instant-adapter.ts
function parseWhere(where) {
const whereQuery = {};
where == null ? void 0 : where.map((item) => {
switch (item.operator) {
case "eq":
whereQuery[item.field] = item.value;
break;
case "in":
whereQuery[item.field] = { $in: item.value };
break;
case "contains":
whereQuery[item.field] = { $like: `%${item.value}%` };
break;
case "starts_with":
whereQuery[item.field] = { $like: `${item.value}%` };
break;
case "ends_with":
whereQuery[item.field] = { $like: `%${item.value}` };
break;
case "ne":
whereQuery[item.field] = { $not: item.value };
break;
case "gt":
whereQuery[item.field] = { $gt: item.value };
break;
case "gte":
whereQuery[item.field] = { $gte: item.value };
break;
case "lt":
whereQuery[item.field] = { $lt: item.value };
break;
case "lte":
whereQuery[item.field] = { $lte: item.value };
break;
}
});
return whereQuery;
}
var instantDBAdapter = ({
usePlural = true,
debugLogs = false,
transactionHooks,
db
}) => createAdapter({
config: {
adapterId: "instantdb-adapter",
// A unique identifier for the adapter.
adapterName: "InstantDB Adapter",
// The name of the adapter.
usePlural,
// Whether the table names in the schema are plural.
debugLogs,
// Whether to enable debug logs.
supportsJSON: false,
// Whether the database supports JSON. (Default: true)
supportsDates: false,
// Whether the database supports dates. (Default: true)
supportsBooleans: true,
// Whether the database supports booleans. (Default: true)
disableIdGeneration: true,
// Whether to disable automatic ID generation. (Default: false)
supportsNumericIds: false
// Whether the database supports numeric IDs. (Default: true)
},
adapter: ({ options, getFieldName, getDefaultModelName }) => {
return {
async create({ data, model }) {
var _a, _b;
data.id = ((_b = (_a = options.advanced) == null ? void 0 : _a.database) == null ? void 0 : _b.generateId) ? options.advanced.database.generateId({ model }) : id();
const transactions = [];
if (getDefaultModelName(model) === "user") {
transactions.push(db.tx.$users[data.id].update({ email: data.email }));
}
if (getDefaultModelName(model) === "session") {
const queryData = await db.query({
$users: { $: { where: { id: data.userId } }, user: {} }
});
const $users = queryData.$users;
if ($users.length === 0) {
throw new Error(`$users entity not found: ${data.userId}`);
}
const $user = $users[0];
const user = $user.user;
if (!user) {
throw new Error(`user link not found: ${data.userId}`);
}
if (debugLogs) {
console.log("[InstantDB] Create token for:", $user.email);
}
const token = await db.auth.createToken($user.email);
const tokenField = getFieldName({ model, field: "token" });
data[tokenField] = token;
if (user.email !== $user.email) {
transactions.push(
db.tx.$users[data.userId].update({ email: user.email })
);
}
}
transactions.push(db.tx[model][data.id].update(data));
if (getDefaultModelName(model) === "user") {
transactions.push(db.tx[model][data.id].link({ $user: data.id }));
}
try {
const userIdField = getFieldName({ model, field: "userId" });
if (data[userIdField]) {
transactions.push(
db.tx[model][data.id].link({ user: data[userIdField] })
);
}
} catch (error) {
}
if (transactionHooks == null ? void 0 : transactionHooks.create) {
const hookTransactions = await transactionHooks.create({
data,
model
});
if (hookTransactions) transactions.push(...hookTransactions);
}
await db.transact(transactions);
return data;
},
async count({ model, where }) {
const query = { [model]: { $: { where: parseWhere(where) } } };
if (debugLogs) {
console.log("[InstantDB] Query:", JSON.stringify(query));
}
const result = await db.query(query);
if (debugLogs) {
console.log("[InstantDB] Result:", JSON.stringify(result));
}
const entities = result[model];
return entities.length;
},
async delete({ model, where }) {
const query = { [model]: { $: { where: parseWhere(where) } } };
if (debugLogs) {
console.log("[InstantDB] Query:", JSON.stringify(query));
}
const result = await db.query(query);
if (debugLogs) {
console.log("[InstantDB] Result:", JSON.stringify(result));
}
const entities = result[model];
if (getDefaultModelName(model) === "session") {
entities.map(async (entity) => {
try {
const tokenField = getFieldName({ model, field: "token" });
await db.auth.signOut({
refresh_token: entity[tokenField]
});
} catch (error) {
}
});
}
const transactions = entities.map((entity) => db.tx[model][entity.id].delete());
await db.transact(transactions);
},
async deleteMany({ model, where }) {
const query = { [model]: { $: { where: parseWhere(where) } } };
if (debugLogs) {
console.log("[InstantDB] Query:", JSON.stringify(query));
}
const result = await db.query(query);
if (debugLogs) {
console.log("[InstantDB] Result:", JSON.stringify(result));
}
const entities = result[model];
if (getDefaultModelName(model) === "session") {
entities.map(async (entity) => {
try {
const tokenField = getFieldName({ model, field: "token" });
await db.auth.signOut({
refresh_token: entity[tokenField]
});
} catch (error) {
}
});
}
const transactions = entities.map((entity) => db.tx[model][entity.id].delete());
await db.transact(transactions);
return entities.length;
},
async findMany({ model, where, limit, sortBy, offset }) {
let order;
if (sortBy) {
order = {
[sortBy.field]: sortBy.direction
};
}
const query = {
[model]: { $: { where: parseWhere(where), limit, offset, order } }
};
if (debugLogs) {
console.log("[InstantDB] Query:", JSON.stringify(query));
}
const result = await db.query(query);
if (debugLogs) {
console.log("[InstantDB] Result:", JSON.stringify(result));
}
const entities = result[model];
return entities;
},
async findOne({ model, where }) {
const query = { [model]: { $: { where: parseWhere(where) } } };
if (debugLogs) {
console.log("[InstantDB] Query:", JSON.stringify(query));
}
const result = await db.query(query);
if (debugLogs) {
console.log("[InstantDB] Result:", JSON.stringify(result));
}
const entities = result[model];
if (entities.length > 0) return entities[0];
return null;
},
async update({ model, update, where }) {
const query = { [model]: { $: { where: parseWhere(where) } } };
if (debugLogs) {
console.log("[InstantDB] Query:", JSON.stringify(query));
}
const result = await db.query(query);
if (debugLogs) {
console.log("[InstantDB] Result:", JSON.stringify(result));
}
const entities = result[model];
const transactions = entities.map(
(entity) => db.tx[model][entity.id].update(update)
);
if (getDefaultModelName(model) === "user") {
const emailField = getFieldName({ model, field: "email" });
const email = update[emailField];
if (email) {
transactions.push(
...entities.map(
(entity) => db.tx.$users[entity.id].update({ email })
)
);
}
}
if (transactionHooks == null ? void 0 : transactionHooks.update) {
const hookTransactions = await transactionHooks.update({
update,
model,
where
});
if (hookTransactions) transactions.push(...hookTransactions);
}
await db.transact(transactions);
if (entities.length > 0) {
return { ...entities[0], ...update };
}
return null;
},
async updateMany({ model, update, where }) {
const query = { [model]: { $: { where: parseWhere(where) } } };
if (debugLogs) {
console.log("[InstantDB] Query:", JSON.stringify(query));
}
const result = await db.query(query);
if (debugLogs) {
console.log("[InstantDB] Result:", JSON.stringify(result));
}
const entities = result[model];
const transactions = entities.map(
(entity) => db.tx[model][entity.id].update(update)
);
if (transactionHooks == null ? void 0 : transactionHooks.update) {
const hookTransactions = await transactionHooks.update({
update,
model,
where
});
if (hookTransactions) {
transactions.push(...hookTransactions);
}
}
await db.transact(transactions);
return entities.length;
},
options: { usePlural, debugLogs }
};
}
});
function useInstantAuth({
db,
sessionData,
isPending
}) {
const { user, isLoading } = db.useAuth();
useEffect(() => {
if (isPending || isLoading) return;
if (sessionData) {
if (!user || user.id !== sessionData.user.id) {
db.auth.signInWithToken(sessionData.session.token);
}
} else {
db.auth.signOut({ invalidateToken: false });
}
}, [db, isPending, isLoading, sessionData, user]);
}
export { instantDBAdapter, parseWhere, useInstantAuth };