UNPKG

@proofkit/better-auth

Version:

FileMaker adapter for Better Auth

288 lines (287 loc) 9.62 kB
import { logger } from "better-auth"; import { createAdapterFactory } from "better-auth/adapters"; const FIELD_SPECIAL_CHARS_REGEX = /[\s_]/; const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/; function parseWhere(where) { if (!where || where.length === 0) { return ""; } function quoteField(field, value) { if (value === null || value instanceof Date) { return field; } if (field === "id" || FIELD_SPECIAL_CHARS_REGEX.test(field)) { return `"${field}"`; } return field; } function formatValue(value) { if (value === null) { return "null"; } if (typeof value === "boolean") { return value ? "true" : "false"; } if (value instanceof Date) { return value.toISOString(); } if (Array.isArray(value)) { return `(${value.map(formatValue).join(",")})`; } if (typeof value === "string") { if (ISO_DATE_REGEX.test(value)) { return value; } return `'${value.replace(/'/g, "''")}'`; } return (value == null ? void 0 : value.toString()) ?? ""; } const opMap = { eq: "eq", ne: "ne", lt: "lt", lte: "le", gt: "gt", gte: "ge" }; const clauses = []; for (let i = 0; i < where.length; i++) { const cond = where[i]; if (!cond) { continue; } const field = quoteField(cond.field, cond.value); let clause = ""; switch (cond.operator) { case "eq": case "ne": case "lt": case "lte": case "gt": case "gte": clause = `${field} ${opMap[cond.operator]} ${formatValue(cond.value)}`; break; case "in": if (Array.isArray(cond.value)) { clause = cond.value.map((v) => `${field} eq ${formatValue(v)}`).join(" or "); clause = `(${clause})`; } break; case "contains": clause = `contains(${field}, ${formatValue(cond.value)})`; break; case "starts_with": clause = `startswith(${field}, ${formatValue(cond.value)})`; break; case "ends_with": clause = `endswith(${field}, ${formatValue(cond.value)})`; break; default: clause = `${field} eq ${formatValue(cond.value)}`; } clauses.push(clause); if (i < where.length - 1) { clauses.push((cond.connector || "and").toLowerCase()); } } return clauses.join(" "); } function buildQueryString(params) { var _a; const parts = []; if (params.top !== void 0) { parts.push(`$top=${params.top}`); } if (params.skip !== void 0) { parts.push(`$skip=${params.skip}`); } if (params.filter) { parts.push(`$filter=${encodeURIComponent(params.filter)}`); } if (params.orderBy) { parts.push(`$orderby=${encodeURIComponent(params.orderBy)}`); } if ((_a = params.select) == null ? void 0 : _a.length) { parts.push(`$select=${params.select.map(encodeURIComponent).join(",")}`); } return parts.length > 0 ? `?${parts.join("&")}` : ""; } const FileMakerAdapter = (config) => { if (!config.database || typeof config.database !== "object") { throw new Error("FileMakerAdapter requires a `database` (fmodata Database instance)."); } const db = config.database; const adapterFactory = createAdapterFactory({ config: { adapterId: "filemaker", adapterName: "FileMaker", usePlural: config.usePlural ?? false, debugLogs: config.debugLogs ?? false, supportsJSON: false, supportsDates: false, supportsBooleans: false, supportsNumericIds: false }, adapter: () => { return { create: async ({ data, model }) => { const result = await db._makeRequest(`/${model}`, { method: "POST", body: JSON.stringify(data) }); if (result.error) { throw new Error("Failed to create record"); } return result.data; }, count: async ({ model, where }) => { var _a; const filter = parseWhere(where); logger.debug("$filter", filter); const query = buildQueryString({ filter: filter.length > 0 ? filter : void 0 }); const result = await db._makeRequest(`/${model}/$count${query}`); if (result.error) { throw new Error("Failed to count records"); } return ((_a = result.data) == null ? void 0 : _a.value) ?? 0; }, findOne: async ({ model, where }) => { var _a, _b; const filter = parseWhere(where); logger.debug("$filter", filter); const query = buildQueryString({ top: 1, filter: filter.length > 0 ? filter : void 0 }); const result = await db._makeRequest(`/${model}${query}`); if (result.error) { throw new Error("Failed to find record"); } return ((_b = (_a = result.data) == null ? void 0 : _a.value) == null ? void 0 : _b[0]) ?? null; }, findMany: async ({ model, where, limit, offset, sortBy }) => { var _a; const filter = parseWhere(where); logger.debug("FIND MANY", { where, filter }); const query = buildQueryString({ top: limit, skip: offset, orderBy: sortBy ? `${sortBy.field} ${sortBy.direction ?? "asc"}` : void 0, filter: filter.length > 0 ? filter : void 0 }); logger.debug("QUERY", query); const result = await db._makeRequest(`/${model}${query}`); logger.debug("RESULT", result); if (result.error) { throw new Error("Failed to find records"); } return ((_a = result.data) == null ? void 0 : _a.value) ?? []; }, delete: async ({ model, where }) => { var _a, _b, _c; const filter = parseWhere(where); logger.debug("$filter", filter); const query = buildQueryString({ top: 1, select: [`"id"`], filter: filter.length > 0 ? filter : void 0 }); const toDelete = await db._makeRequest(`/${model}${query}`); const id = (_c = (_b = (_a = toDelete.data) == null ? void 0 : _a.value) == null ? void 0 : _b[0]) == null ? void 0 : _c.id; if (!id) { return; } const result = await db._makeRequest(`/${model}('${id}')`, { method: "DELETE" }); if (result.error) { throw new Error("Failed to delete record"); } }, deleteMany: async ({ model, where }) => { var _a, _b; const filter = parseWhere(where); const query = buildQueryString({ select: [`"id"`], filter: filter.length > 0 ? filter : void 0 }); const rows = await db._makeRequest(`/${model}${query}`); const ids = ((_b = (_a = rows.data) == null ? void 0 : _a.value) == null ? void 0 : _b.map((r) => r.id)) ?? []; let deleted = 0; for (const id of ids) { const res = await db._makeRequest(`/${model}('${id}')`, { method: "DELETE" }); if (!res.error) { deleted++; } } return deleted; }, update: async ({ model, where, update }) => { var _a, _b, _c; const filter = parseWhere(where); logger.debug("UPDATE", { model, where, update }); logger.debug("$filter", filter); const query = buildQueryString({ select: [`"id"`], filter: filter.length > 0 ? filter : void 0 }); const existing = await db._makeRequest(`/${model}${query}`); logger.debug("EXISTING", existing.data); const id = (_c = (_b = (_a = existing.data) == null ? void 0 : _a.value) == null ? void 0 : _b[0]) == null ? void 0 : _c.id; if (!id) { return null; } const patchRes = await db._makeRequest(`/${model}('${id}')`, { method: "PATCH", body: JSON.stringify(update) }); logger.debug("PATCH RES", patchRes.data); if (patchRes.error) { return null; } const readBack = await db._makeRequest(`/${model}('${id}')`); logger.debug("READ BACK", readBack.data); return readBack.data ?? null; }, updateMany: async ({ model, where, update }) => { var _a, _b; const filter = parseWhere(where); const query = buildQueryString({ select: [`"id"`], filter: filter.length > 0 ? filter : void 0 }); const rows = await db._makeRequest(`/${model}${query}`); const ids = ((_b = (_a = rows.data) == null ? void 0 : _a.value) == null ? void 0 : _b.map((r) => r.id)) ?? []; let updated = 0; for (const id of ids) { const res = await db._makeRequest(`/${model}('${id}')`, { method: "PATCH", body: JSON.stringify(update) }); if (!res.error) { updated++; } } return updated; } }; } }); const originalFactory = adapterFactory; const wrappedFactory = ((options) => { const adapter = originalFactory(options); adapter.database = db; return adapter; }); wrappedFactory.database = db; return wrappedFactory; }; export { FileMakerAdapter, parseWhere }; //# sourceMappingURL=adapter.js.map