@rolder/kit-better-auth 0.1.0-alpha.2 → 0.1.0-alpha.3

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 (45) hide show
  1. package/dist/client/index.d.ts +2 -1
  2. package/dist/client/index.js +2 -0
  3. package/dist/client/tanstack/index.js +1 -0
  4. package/dist/client/tanstack/useSessionUser.js +8 -0
  5. package/dist/client/types.d.ts +1 -0
  6. package/dist/client/types.js +0 -0
  7. package/dist/server/defaultAuthServerConfig.js +36 -0
  8. package/dist/server/env.js +8 -0
  9. package/dist/server/index.d.ts +0 -4
  10. package/dist/server/index.js +6 -0
  11. package/dist/server/surrealDbAdapter/adapter.js +64 -0
  12. package/dist/server/surrealDbAdapter/db/getDB.js +25 -0
  13. package/dist/server/surrealDbAdapter/db/getDBInstance.js +39 -0
  14. package/dist/server/surrealDbAdapter/db/index.js +1 -0
  15. package/dist/server/surrealDbAdapter/db/schema.js +101 -0
  16. package/dist/server/surrealDbAdapter/index.js +1 -0
  17. package/dist/server/surrealDbAdapter/methods/count.js +17 -0
  18. package/dist/server/surrealDbAdapter/methods/create.js +21 -0
  19. package/dist/server/surrealDbAdapter/methods/delete.js +18 -0
  20. package/dist/server/surrealDbAdapter/methods/deleteMany.js +19 -0
  21. package/dist/server/surrealDbAdapter/methods/findMany.js +51 -0
  22. package/dist/server/surrealDbAdapter/methods/findOne.js +42 -0
  23. package/dist/server/surrealDbAdapter/methods/index.js +8 -0
  24. package/dist/server/surrealDbAdapter/methods/types.js +0 -0
  25. package/dist/server/surrealDbAdapter/methods/update.js +25 -0
  26. package/dist/server/surrealDbAdapter/methods/updateMany.js +21 -0
  27. package/dist/server/surrealDbAdapter/types.js +0 -0
  28. package/dist/server/surrealDbAdapter/utils/applyJoinMappings.js +31 -0
  29. package/dist/server/surrealDbAdapter/utils/buildExpression.js +58 -0
  30. package/dist/server/surrealDbAdapter/utils/buildJoinPlan.js +46 -0
  31. package/dist/server/surrealDbAdapter/utils/buildSelectFields.js +5 -0
  32. package/dist/server/surrealDbAdapter/utils/index.js +6 -0
  33. package/dist/server/surrealDbAdapter/utils/normalizeOutputRecordIds.js +43 -0
  34. package/dist/server/surrealDbAdapter/utils/normalizeRecordIds.js +38 -0
  35. package/dist/server/surrealDbAdapter/utils/normalizeWhereValue.js +33 -0
  36. package/dist/server/surrealDbAdapter/utils/types.js +0 -0
  37. package/dist/server/tanstack/getSessionTokenTanstack.js +15 -0
  38. package/dist/server/tanstack/getSessionUserTanstack.js +18 -0
  39. package/dist/server/tanstack/index.d.ts +1 -0
  40. package/dist/server/tanstack/index.js +4 -0
  41. package/dist/server/types.d.ts +0 -1
  42. package/dist/server/types.js +0 -0
  43. package/package.json +16 -4
  44. package/dist/index.d.ts +0 -2
  45. package/dist/index.js +0 -2
@@ -1 +1,2 @@
1
- export * from './tanstack';
1
+ export * from 'better-auth/client/plugins';
2
+ export * from 'better-auth/react';
@@ -0,0 +1,2 @@
1
+ export * from "better-auth/client/plugins";
2
+ export * from "better-auth/react";
@@ -0,0 +1 @@
1
+ export * from "./useSessionUser.js";
@@ -0,0 +1,8 @@
1
+ import { useRouteContext } from "@tanstack/react-router";
2
+ const useSessionUser = ()=>{
3
+ const { user } = useRouteContext({
4
+ from: '__root__'
5
+ });
6
+ return user;
7
+ };
8
+ export { useSessionUser };
@@ -0,0 +1 @@
1
+ export type * from 'better-auth/client/plugins';
File without changes
@@ -0,0 +1,36 @@
1
+ import { baseUrl } from "./env.js";
2
+ import { surrealDbAdapter } from "./surrealDbAdapter/index.js";
3
+ const defaultAuthServerPluginOptions = {
4
+ admin: {
5
+ bannedUserMessage: 'Доступ к приложению заблокирован. Обратитесь к администратору.'
6
+ },
7
+ surrealDBjwt: {
8
+ jwks: {
9
+ keyPairConfig: {
10
+ alg: 'EdDSA',
11
+ crv: 'Ed25519'
12
+ }
13
+ },
14
+ jwt: {
15
+ issuer: baseUrl,
16
+ audience: baseUrl,
17
+ expirationTime: '15m',
18
+ definePayload: ()=>({
19
+ ns: process.env.SURREALDB_NAMESPACE,
20
+ db: process.env.SURREALDB_DATABASE,
21
+ ac: 'better_auth_jwt'
22
+ })
23
+ }
24
+ }
25
+ };
26
+ const defaultAuthServerConfig = {
27
+ baseURL: baseUrl,
28
+ secret: process.env.BETTER_AUTH_SECRET
29
+ };
30
+ const defaultSurrealDBAdaperConfig = {
31
+ database: surrealDbAdapter(),
32
+ experimental: {
33
+ joins: true
34
+ }
35
+ };
36
+ export { defaultAuthServerConfig, defaultAuthServerPluginOptions, defaultSurrealDBAdaperConfig };
@@ -0,0 +1,8 @@
1
+ const baseUrl = {
2
+ MODE: "production",
3
+ DEV: false,
4
+ PROD: true,
5
+ BASE_URL: "/",
6
+ ASSET_PREFIX: "auto"
7
+ }.VITE_BASE_URL || 'http://localhost:3000';
8
+ export { baseUrl };
@@ -1,9 +1,5 @@
1
- export * from 'better-auth/client/plugins';
2
1
  export { betterAuth as betterAuthServer } from 'better-auth/minimal';
3
2
  export * from 'better-auth/plugins';
4
3
  export * from 'better-auth/plugins/admin/access';
5
- export * from 'better-auth/react';
6
- export { tanstackStartCookies } from 'better-auth/tanstack-start';
7
4
  export * from './defaultAuthServerConfig';
8
- export * from './tanstack';
9
5
  export * from './types';
@@ -0,0 +1,6 @@
1
+ import { betterAuth } from "better-auth/minimal";
2
+ export * from "better-auth/plugins";
3
+ export * from "better-auth/plugins/admin/access";
4
+ export * from "./defaultAuthServerConfig.js";
5
+ export * from "./types.js";
6
+ export { betterAuth as betterAuthServer };
@@ -0,0 +1,64 @@
1
+ import { createAdapterFactory } from "better-auth/adapters";
2
+ import { count, create, deleteFn, deleteMany, findMany, findOne, update, updateMany } from "./methods/index.js";
3
+ const surrealDbAdapter = ({ surrealDbProps, debugLogs } = {})=>{
4
+ const adapterCreator = createAdapterFactory({
5
+ config: {
6
+ adapterId: 'surrealdb',
7
+ adapterName: 'SurrealDB Adapter',
8
+ usePlural: false,
9
+ supportsJSON: true,
10
+ supportsArrays: true,
11
+ supportsDates: true,
12
+ supportsBooleans: true,
13
+ supportsUUIDs: true,
14
+ debugLogs: debugLogs ?? false
15
+ },
16
+ adapter: (a)=>({
17
+ create: (p)=>create({
18
+ ...p,
19
+ ...a,
20
+ surrealDbProps
21
+ }),
22
+ update: (p)=>update({
23
+ ...p,
24
+ ...a,
25
+ surrealDbProps
26
+ }),
27
+ updateMany: (p)=>updateMany({
28
+ ...p,
29
+ ...a,
30
+ surrealDbProps
31
+ }),
32
+ findOne: (p)=>findOne({
33
+ ...p,
34
+ ...a,
35
+ surrealDbProps
36
+ }),
37
+ findMany: (p)=>findMany({
38
+ ...p,
39
+ ...a,
40
+ surrealDbProps
41
+ }),
42
+ delete: (p)=>deleteFn({
43
+ ...p,
44
+ ...a,
45
+ surrealDbProps
46
+ }),
47
+ deleteMany: (p)=>deleteMany({
48
+ ...p,
49
+ ...a,
50
+ surrealDbProps
51
+ }),
52
+ count: (p)=>count({
53
+ ...p,
54
+ ...a,
55
+ surrealDbProps
56
+ })
57
+ })
58
+ });
59
+ return (options)=>{
60
+ const safeOptions = options ?? {};
61
+ return adapterCreator(safeOptions);
62
+ };
63
+ };
64
+ export { surrealDbAdapter };
@@ -0,0 +1,25 @@
1
+ import { baseUrl } from "../../env.js";
2
+ import { getDBInstance } from "./getDBInstance.js";
3
+ import { schema } from "./schema.js";
4
+ let schemaApplied = false;
5
+ let schemaApplying = false;
6
+ const ensureAuthSchema = async (props = {})=>{
7
+ if (schemaApplied) return;
8
+ if (!schemaApplying) try {
9
+ schemaApplying = true;
10
+ const db = await getDBInstance(props);
11
+ const betterAuthJwksUrl = `${baseUrl}/api/auth/jwks`;
12
+ await db.query(schema, {
13
+ BETTER_AUTH_JWKS_URL: betterAuthJwksUrl
14
+ });
15
+ schemaApplied = true;
16
+ } finally{
17
+ schemaApplying = false;
18
+ }
19
+ };
20
+ const getDb = async (props = {})=>{
21
+ const db = await getDBInstance(props);
22
+ await ensureAuthSchema(props);
23
+ return db;
24
+ };
25
+ export { getDb };
@@ -0,0 +1,39 @@
1
+ import { Surreal, applyDiagnostics, createRemoteEngines } from "surrealdb";
2
+ let db = null;
3
+ const getDBInstance = async (params = {})=>{
4
+ if (db?.isConnected) return db;
5
+ const instance = new Surreal(params.debug ? {
6
+ engines: applyDiagnostics(createRemoteEngines(), (event)=>{
7
+ console.log('Surreal debug:', event);
8
+ })
9
+ } : void 0);
10
+ try {
11
+ const url = params.url || process.env.BETTER_AUTH_SURREALDB_URL;
12
+ if (!url) throw new Error('Missing required BETTER_AUTH_SURREALDB_URL');
13
+ const namespace = params.namespace || process.env.BETTER_AUTH_SURREALDB_NAMESPACE;
14
+ if (!namespace) throw new Error('Missing required BETTER_AUTH_SURREALDB_NAMESPACE');
15
+ const database = params.database || process.env.BETTER_AUTH_SURREALDB_DATABASE;
16
+ if (!database) throw new Error('Missing required BETTER_AUTH_SURREALDB_DATABASE');
17
+ const username = params.username || process.env.BETTER_AUTH_SURREALDB_USERNAME;
18
+ if (!username) throw new Error('Missing required BETTER_AUTH_SURREALDB_USERNAME');
19
+ const password = params.password || process.env.BETTER_AUTH_SURREALDB_PASSWORD;
20
+ if (!password) throw new Error('Missing required BETTER_AUTH_SURREALDB_PASSWORD');
21
+ await instance.connect(url, {
22
+ authentication: {
23
+ username,
24
+ password
25
+ }
26
+ });
27
+ await instance.use({
28
+ namespace,
29
+ database
30
+ });
31
+ db = instance;
32
+ return instance;
33
+ } catch (error) {
34
+ console.error('Better Auth failed to connect to SurrealDB:', error);
35
+ db = null;
36
+ throw error;
37
+ }
38
+ };
39
+ export { getDBInstance };
@@ -0,0 +1 @@
1
+ export * from "./getDB.js";
@@ -0,0 +1,101 @@
1
+ const schema = `
2
+ // Better Auth JWT Access
3
+ DEFINE ACCESS IF NOT EXISTS better_auth_jwt ON DATABASE TYPE RECORD
4
+ WITH JWT URL $BETTER_AUTH_JWKS_URL
5
+ AUTHENTICATE {
6
+ IF $token.sub {
7
+ RETURN SELECT * FROM ONLY <RECORD>("user:"+ $token.sub);
8
+ } ELSE {
9
+ THROW "There is no user id at $token.sub"
10
+ }
11
+ }
12
+ ;
13
+
14
+ // User
15
+ DEFINE TABLE IF NOT EXISTS user TYPE NORMAL SCHEMAFULL PERMISSIONS FULL;
16
+ DEFINE FIELD IF NOT EXISTS name ON user TYPE string;
17
+ DEFINE FIELD IF NOT EXISTS email ON user TYPE option<string> ASSERT $value = NONE OR string::is_email($value);
18
+ DEFINE FIELD IF NOT EXISTS email_address ON user TYPE option<string> ASSERT $value = NONE OR string::is_email($value);
19
+ DEFINE INDEX IF NOT EXISTS userUserUnique ON user COLUMNS email UNIQUE;
20
+ DEFINE FIELD IF NOT EXISTS emailVerified ON user TYPE bool;
21
+ DEFINE FIELD IF NOT EXISTS image ON user TYPE option<string>;
22
+ DEFINE FIELD IF NOT EXISTS createdAt ON user TYPE option<datetime> DEFAULT time::now();
23
+ DEFINE FIELD IF NOT EXISTS updatedAt ON user TYPE option<datetime> DEFAULT time::now();
24
+ -- Admin plugin
25
+ DEFINE FIELD IF NOT EXISTS role ON user TYPE option<string>;
26
+ DEFINE FIELD IF NOT EXISTS banned ON user TYPE option<bool>;
27
+ DEFINE FIELD IF NOT EXISTS banReason ON user TYPE option<string> | NULL;
28
+ DEFINE FIELD IF NOT EXISTS banExpires ON user TYPE option<datetime> | NULL;
29
+ -- Username plugin
30
+ DEFINE FIELD IF NOT EXISTS username ON user TYPE option<string>;
31
+ DEFINE FIELD IF NOT EXISTS displayUsername ON user TYPE option<string>;
32
+ -- Custom
33
+ DEFINE FIELD IF NOT EXISTS accounts ON user COMPUTED <~account;
34
+ DEFINE FIELD IF NOT EXISTS sessions ON user COMPUTED <~session;
35
+
36
+ -- // Session
37
+ DEFINE TABLE IF NOT EXISTS session TYPE NORMAL SCHEMAFULL PERMISSIONS FULL;
38
+ DEFINE FIELD IF NOT EXISTS expiresAt ON session TYPE datetime;
39
+ DEFINE FIELD IF NOT EXISTS token ON session TYPE string;
40
+ DEFINE INDEX IF NOT EXISTS sessionSessionUnique ON session COLUMNS token UNIQUE;
41
+ DEFINE FIELD IF NOT EXISTS createdAt ON session TYPE option<datetime> DEFAULT time::now();
42
+ DEFINE FIELD IF NOT EXISTS updatedAt ON session TYPE option<datetime> DEFAULT time::now();
43
+ DEFINE FIELD IF NOT EXISTS ipAddress ON session TYPE option<string>;
44
+ DEFINE FIELD IF NOT EXISTS userAgent ON session TYPE option<string>;
45
+ DEFINE FIELD IF NOT EXISTS userId ON session TYPE record<user> REFERENCE ON DELETE IGNORE;
46
+ -- Admin plugin
47
+ DEFINE FIELD IF NOT EXISTS impersonatedBy ON user TYPE option<record<user>>;
48
+
49
+ -- // Account
50
+ DEFINE TABLE IF NOT EXISTS account TYPE NORMAL SCHEMAFULL PERMISSIONS FULL;
51
+ DEFINE FIELD IF NOT EXISTS accountId ON account TYPE string;
52
+ DEFINE FIELD IF NOT EXISTS providerId ON account TYPE string;
53
+ DEFINE FIELD IF NOT EXISTS accessToken ON account TYPE option<string>;
54
+ DEFINE FIELD IF NOT EXISTS refreshToken ON account TYPE option<string>;
55
+ DEFINE FIELD IF NOT EXISTS idToken ON account TYPE option<string>;
56
+ DEFINE FIELD IF NOT EXISTS accessTokenExpiresAt ON account TYPE option<datetime>;
57
+ DEFINE FIELD IF NOT EXISTS refreshTokenExpiresAt ON account TYPE option<datetime>;
58
+ DEFINE FIELD IF NOT EXISTS scope ON account TYPE option<string>;
59
+ DEFINE FIELD IF NOT EXISTS password ON account TYPE option<string>;
60
+ DEFINE FIELD IF NOT EXISTS createdAt ON account TYPE option<datetime> DEFAULT time::now();
61
+ DEFINE FIELD IF NOT EXISTS updatedAt ON account TYPE option<datetime> DEFAULT time::now();
62
+ -- Custom
63
+ DEFINE FIELD IF NOT EXISTS userId ON account TYPE record<user> REFERENCE ON DELETE IGNORE;
64
+
65
+ // Verification
66
+ DEFINE TABLE IF NOT EXISTS verification TYPE NORMAL SCHEMAFULL PERMISSIONS FULL;
67
+ DEFINE FIELD IF NOT EXISTS identifier ON verification TYPE string;
68
+ DEFINE FIELD IF NOT EXISTS value ON verification TYPE string;
69
+ DEFINE FIELD IF NOT EXISTS expiresAt ON verification TYPE datetime;
70
+ DEFINE FIELD IF NOT EXISTS createdAt ON verification TYPE option<datetime> DEFAULT time::now();
71
+ DEFINE FIELD IF NOT EXISTS updatedAt ON verification TYPE option<datetime> DEFAULT time::now();
72
+
73
+ // JWKS
74
+ DEFINE TABLE IF NOT EXISTS jwks TYPE NORMAL SCHEMAFULL PERMISSIONS FULL;
75
+ DEFINE FIELD IF NOT EXISTS publicKey ON jwks TYPE string;
76
+ DEFINE FIELD IF NOT EXISTS privateKey ON jwks TYPE string;
77
+ DEFINE FIELD IF NOT EXISTS createdAt ON jwks TYPE option<datetime> DEFAULT time::now();
78
+ DEFINE FIELD IF NOT EXISTS updatedAt ON jwks TYPE option<datetime> DEFAULT time::now();
79
+
80
+ // Root auth user
81
+ IF !user:root.* {
82
+ CREATE user:root CONTENT {
83
+ name: 'Системная учетная запись',
84
+ role: 'admin',
85
+ username: 'root',
86
+ displayUsername: 'Root',
87
+ email: 'mail@rolder.dev',
88
+ emailVerified: true,
89
+ banned: false,
90
+ };
91
+ };
92
+
93
+ IF !account:root.* {
94
+ CREATE account:root CONTENT {
95
+ accountId: 'root',
96
+ userId: user:root,
97
+ password: '52c2da6bc1a525ee0f6a298e228a1314:c7ba714b9467d4582c5848947c2536340ff9f85ff6ca6c9ee022f68cd5eb60e227ba0cbadd3d46e9316c4562cef9d7551c957192809bf2a4b27f2a2b1365a08e',
98
+ providerId: 'credential',
99
+ };
100
+ };`;
101
+ export { schema };
@@ -0,0 +1 @@
1
+ export * from "./adapter.js";
@@ -0,0 +1,17 @@
1
+ import { Table, surql } from "surrealdb";
2
+ import { getDb } from "../db/index.js";
3
+ import { buildExpression } from "../utils/index.js";
4
+ const count = async ({ model, where, surrealDbProps, ...args })=>{
5
+ const db = await getDb(surrealDbProps);
6
+ const table = new Table(model);
7
+ const expression = buildExpression({
8
+ model,
9
+ where,
10
+ ...args
11
+ });
12
+ const query = surql`SELECT count() AS count FROM ${table}`;
13
+ if (expression) query.append(surql` WHERE ${expression}`);
14
+ const [rows] = await db.query(query).json().collect();
15
+ return rows?.[0]?.count ?? 0;
16
+ };
17
+ export { count };
@@ -0,0 +1,21 @@
1
+ import { RecordId, Table, surql } from "surrealdb";
2
+ import { getDb } from "../db/index.js";
3
+ import { normalizeOutputRecordIds, normalizeRecordIds } from "../utils/index.js";
4
+ const create = async ({ model, data, surrealDbProps, ...args })=>{
5
+ const db = await getDb(surrealDbProps);
6
+ const { id, ...content } = data;
7
+ const normalized = normalizeRecordIds({
8
+ model,
9
+ data: content,
10
+ ...args
11
+ });
12
+ const hasId = null != id;
13
+ const target = hasId ? new RecordId(model, String(id)) : new Table(model);
14
+ const [rows] = await db.query(surql`CREATE ${target} CONTENT ${normalized} RETURN AFTER`).json().collect();
15
+ return normalizeOutputRecordIds({
16
+ model,
17
+ data: rows?.[0] ?? null,
18
+ ...args
19
+ });
20
+ };
21
+ export { create };
@@ -0,0 +1,18 @@
1
+ import { Table, surql } from "surrealdb";
2
+ import { getDb } from "../db/index.js";
3
+ import { buildExpression } from "../utils/index.js";
4
+ const deleteFn = async ({ model, where, surrealDbProps, ...args })=>{
5
+ if (!where || 0 === where.length) throw new Error('Delete requires a where clause');
6
+ const db = await getDb(surrealDbProps);
7
+ const table = new Table(model);
8
+ const expression = buildExpression({
9
+ model,
10
+ where,
11
+ ...args
12
+ });
13
+ const query = surql`DELETE FROM ${table}`;
14
+ if (expression) query.append(surql` WHERE ${expression}`);
15
+ query.append(surql` RETURN BEFORE`);
16
+ await db.query(query);
17
+ };
18
+ export { deleteFn };
@@ -0,0 +1,19 @@
1
+ import { Table, surql } from "surrealdb";
2
+ import { getDb } from "../db/index.js";
3
+ import { buildExpression } from "../utils/index.js";
4
+ const deleteMany = async ({ model, where, surrealDbProps, ...args })=>{
5
+ if (!where || 0 === where.length) throw new Error('DeleteMany requires a where clause');
6
+ const db = await getDb(surrealDbProps);
7
+ const table = new Table(model);
8
+ const expression = buildExpression({
9
+ model,
10
+ where,
11
+ ...args
12
+ });
13
+ const query = surql`DELETE FROM ${table}`;
14
+ if (expression) query.append(surql` WHERE ${expression}`);
15
+ query.append(surql` RETURN BEFORE`);
16
+ const [rows] = await db.query(query).json().collect();
17
+ return rows?.length ?? 0;
18
+ };
19
+ export { deleteMany };
@@ -0,0 +1,51 @@
1
+ import { Table, raw, surql } from "surrealdb";
2
+ import { getDb } from "../db/index.js";
3
+ import { applyJoinMappings, buildExpression, buildJoinPlan, buildSelectFields, normalizeOutputRecordIds } from "../utils/index.js";
4
+ const findMany = async ({ model, where, limit, offset, sortBy, join, surrealDbProps, ...args })=>{
5
+ const db = await getDb(surrealDbProps);
6
+ const table = new Table(model);
7
+ const expression = buildExpression({
8
+ model,
9
+ where,
10
+ ...args
11
+ });
12
+ const joinPlan = buildJoinPlan({
13
+ model,
14
+ join,
15
+ ...args
16
+ });
17
+ const fields = buildSelectFields(void 0, (field)=>args.getFieldName({
18
+ model,
19
+ field
20
+ }));
21
+ const baseFields = fields.split(', ');
22
+ const joinFields = joinPlan.selectFields.filter((field)=>!baseFields.includes(field));
23
+ const finalFields = [
24
+ ...baseFields,
25
+ ...joinFields
26
+ ].join(', ');
27
+ const query = surql`SELECT ${raw(finalFields)} FROM ${table}`;
28
+ if (expression) query.append(surql` WHERE ${expression}`);
29
+ if (sortBy) {
30
+ const orderField = args.getFieldName({
31
+ model,
32
+ field: sortBy.field
33
+ });
34
+ const direction = sortBy.direction.toUpperCase();
35
+ query.append(surql` ORDER BY ${raw(orderField)} ${raw(direction)}`);
36
+ }
37
+ if ('number' == typeof offset) query.append(surql` START ${offset}`);
38
+ query.append(surql` LIMIT ${limit}`);
39
+ if (joinPlan.fetchFields.size > 0) query.append(surql` FETCH ${raw(Array.from(joinPlan.fetchFields).join(', '))}`);
40
+ const [rows] = await db.query(query).json().collect();
41
+ return (rows ?? []).map((row)=>normalizeOutputRecordIds({
42
+ model,
43
+ data: applyJoinMappings({
44
+ row,
45
+ mappings: joinPlan.mappings,
46
+ ...args
47
+ }),
48
+ ...args
49
+ }));
50
+ };
51
+ export { findMany };
@@ -0,0 +1,42 @@
1
+ import { Table, raw, surql } from "surrealdb";
2
+ import { getDb } from "../db/index.js";
3
+ import { applyJoinMappings, buildExpression, buildJoinPlan, buildSelectFields, normalizeOutputRecordIds } from "../utils/index.js";
4
+ const findOne = async ({ model, where, select, join, surrealDbProps, ...args })=>{
5
+ const db = await getDb(surrealDbProps);
6
+ const table = new Table(model);
7
+ const expression = buildExpression({
8
+ model,
9
+ where,
10
+ ...args
11
+ });
12
+ const joinPlan = buildJoinPlan({
13
+ model,
14
+ join,
15
+ ...args
16
+ });
17
+ const fields = buildSelectFields(select, (field)=>args.getFieldName({
18
+ model,
19
+ field
20
+ }));
21
+ const baseFields = fields.split(', ');
22
+ const joinFields = joinPlan.selectFields.filter((field)=>!baseFields.includes(field));
23
+ const finalFields = [
24
+ ...baseFields,
25
+ ...joinFields
26
+ ].join(', ');
27
+ const query = surql`SELECT ${raw(finalFields)} FROM ${table}`;
28
+ if (expression) query.append(surql` WHERE ${expression}`);
29
+ query.append(surql` LIMIT ${1}`);
30
+ if (joinPlan.fetchFields.size > 0) query.append(surql` FETCH ${raw(Array.from(joinPlan.fetchFields).join(', '))}`);
31
+ const [rows] = await db.query(query).json().collect();
32
+ return normalizeOutputRecordIds({
33
+ model,
34
+ data: applyJoinMappings({
35
+ row: rows?.[0] ?? null,
36
+ mappings: joinPlan.mappings,
37
+ ...args
38
+ }),
39
+ ...args
40
+ });
41
+ };
42
+ export { findOne };
@@ -0,0 +1,8 @@
1
+ export * from "./count.js";
2
+ export * from "./create.js";
3
+ export * from "./delete.js";
4
+ export * from "./deleteMany.js";
5
+ export * from "./findMany.js";
6
+ export * from "./findOne.js";
7
+ export * from "./update.js";
8
+ export * from "./updateMany.js";
File without changes
@@ -0,0 +1,25 @@
1
+ import { Table } from "surrealdb";
2
+ import { getDb } from "../db/index.js";
3
+ import { buildExpression, normalizeOutputRecordIds, normalizeRecordIds } from "../utils/index.js";
4
+ const update_update = async ({ model, where, update, surrealDbProps, ...args })=>{
5
+ if (!where || 0 === where.length) throw new Error('Update requires a where clause');
6
+ const db = await getDb(surrealDbProps);
7
+ const normalized = normalizeRecordIds({
8
+ model,
9
+ data: update,
10
+ ...args
11
+ });
12
+ const table = new Table(model);
13
+ const expression = buildExpression({
14
+ model,
15
+ where,
16
+ ...args
17
+ });
18
+ const [updated] = await db.update(table).merge(normalized).where(expression).json();
19
+ return normalizeOutputRecordIds({
20
+ model,
21
+ data: updated ?? null,
22
+ ...args
23
+ });
24
+ };
25
+ export { update_update as update };
@@ -0,0 +1,21 @@
1
+ import { Table } from "surrealdb";
2
+ import { getDb } from "../db/index.js";
3
+ import { buildExpression, normalizeRecordIds } from "../utils/index.js";
4
+ const updateMany = async ({ model, where, update, surrealDbProps, ...args })=>{
5
+ if (!where || 0 === where.length) throw new Error('UpdateMany requires a where clause');
6
+ const db = await getDb(surrealDbProps);
7
+ const normalized = normalizeRecordIds({
8
+ model,
9
+ data: update,
10
+ ...args
11
+ });
12
+ const table = new Table(model);
13
+ const expression = buildExpression({
14
+ model,
15
+ where,
16
+ ...args
17
+ });
18
+ const updated = await db.update(table).merge(normalized).where(expression).json();
19
+ return updated?.length ?? 0;
20
+ };
21
+ export { updateMany };
File without changes
@@ -0,0 +1,31 @@
1
+ import { normalizeOutputRecordIds } from "./normalizeOutputRecordIds.js";
2
+ const applyJoinMappings = ({ row, mappings, ...args })=>{
3
+ if (!row || 0 === mappings.length) return row;
4
+ const normalized = {
5
+ ...row
6
+ };
7
+ for (const mapping of mappings){
8
+ if (void 0 !== normalized[mapping.modelName]) continue;
9
+ if (void 0 === normalized[mapping.sourceField]) continue;
10
+ const sourceValue = normalized[mapping.sourceField];
11
+ if (Array.isArray(sourceValue)) {
12
+ normalized[mapping.modelName] = sourceValue.map((item)=>normalizeOutputRecordIds({
13
+ model: mapping.modelName,
14
+ data: item,
15
+ ...args
16
+ }));
17
+ continue;
18
+ }
19
+ if (sourceValue && 'object' == typeof sourceValue) {
20
+ normalized[mapping.modelName] = normalizeOutputRecordIds({
21
+ model: mapping.modelName,
22
+ data: sourceValue,
23
+ ...args
24
+ });
25
+ continue;
26
+ }
27
+ normalized[mapping.modelName] = sourceValue;
28
+ }
29
+ return normalized;
30
+ };
31
+ export { applyJoinMappings };
@@ -0,0 +1,58 @@
1
+ import { and, contains, eq, gt, gte, inside, lt, lte, or } from "surrealdb";
2
+ import { normalizeWhereValue } from "./normalizeWhereValue.js";
3
+ const buildExpression = ({ model, where, ...args })=>{
4
+ if (!where || 0 === where.length) return;
5
+ const opExpression = (clause)=>{
6
+ const field = clause.field;
7
+ const value = normalizeWhereValue({
8
+ model,
9
+ field,
10
+ value: clause.value,
11
+ ...args
12
+ });
13
+ const operator = clause.operator ?? 'eq';
14
+ switch(operator){
15
+ case 'ne':
16
+ return {
17
+ toSQL: (ctx)=>`${field} != ${ctx.def(value)}`
18
+ };
19
+ case 'gt':
20
+ return gt(field, value);
21
+ case 'gte':
22
+ return gte(field, value);
23
+ case 'lt':
24
+ return lt(field, value);
25
+ case 'lte':
26
+ return lte(field, value);
27
+ case 'contains':
28
+ return contains(field, value);
29
+ case 'starts_with':
30
+ return {
31
+ toSQL: (ctx)=>`string::starts_with(${field}, ${ctx.def(value)})`
32
+ };
33
+ case 'ends_with':
34
+ return {
35
+ toSQL: (ctx)=>`string::ends_with(${field}, ${ctx.def(value)})`
36
+ };
37
+ case 'in':
38
+ return inside(field, value);
39
+ case 'not_in':
40
+ return {
41
+ toSQL: (ctx)=>`${field} NOT IN ${ctx.def(value)}`
42
+ };
43
+ default:
44
+ return eq(field, value);
45
+ }
46
+ };
47
+ let expression;
48
+ for (const clause of where){
49
+ const next = opExpression(clause);
50
+ if (!expression) {
51
+ expression = next;
52
+ continue;
53
+ }
54
+ expression = 'OR' === clause.connector ? or(expression, next) : and(expression, next);
55
+ }
56
+ return expression;
57
+ };
58
+ export { buildExpression };
@@ -0,0 +1,46 @@
1
+ const buildJoinPlan = ({ model, join, getDefaultModelName })=>{
2
+ const joinFieldsByModel = {
3
+ user: {
4
+ account: 'accounts',
5
+ session: 'sessions'
6
+ }
7
+ };
8
+ const selectFields = [];
9
+ const fetchFields = new Set();
10
+ const mappings = [];
11
+ if (!join || 0 === Object.keys(join).length) return {
12
+ selectFields,
13
+ fetchFields,
14
+ mappings
15
+ };
16
+ for (const [joinModel, joinConfig] of Object.entries(join)){
17
+ const baseModelName = getDefaultModelName(model);
18
+ const joinModelName = getDefaultModelName(joinModel);
19
+ const relation = joinConfig.relation ?? 'one-to-many';
20
+ if ('one-to-one' === relation) {
21
+ const fromField = joinConfig.on.from;
22
+ selectFields.push(fromField);
23
+ fetchFields.add(fromField);
24
+ mappings.push({
25
+ modelName: joinModel,
26
+ sourceField: fromField
27
+ });
28
+ continue;
29
+ }
30
+ const computedField = joinFieldsByModel[baseModelName]?.[joinModelName];
31
+ if (computedField) {
32
+ selectFields.push(computedField);
33
+ fetchFields.add(computedField);
34
+ mappings.push({
35
+ modelName: joinModel,
36
+ sourceField: computedField
37
+ });
38
+ }
39
+ }
40
+ return {
41
+ selectFields,
42
+ fetchFields,
43
+ mappings
44
+ };
45
+ };
46
+ export { buildJoinPlan };
@@ -0,0 +1,5 @@
1
+ const buildSelectFields = (select, mapField)=>{
2
+ if (!select || 0 === select.length) return '*';
3
+ return select.map(mapField).join(', ');
4
+ };
5
+ export { buildSelectFields };
@@ -0,0 +1,6 @@
1
+ export * from "./applyJoinMappings.js";
2
+ export * from "./buildExpression.js";
3
+ export * from "./buildJoinPlan.js";
4
+ export * from "./buildSelectFields.js";
5
+ export * from "./normalizeOutputRecordIds.js";
6
+ export * from "./normalizeRecordIds.js";
@@ -0,0 +1,43 @@
1
+ import { RecordId } from "surrealdb";
2
+ const normalizeOutputRecordIds = ({ model, data, getDefaultModelName, getDefaultFieldName, getFieldAttributes })=>{
3
+ if (!data) return data;
4
+ const normalized = {
5
+ ...data
6
+ };
7
+ const defaultModelName = getDefaultModelName(model);
8
+ const stripRecordId = (raw)=>{
9
+ if (raw instanceof RecordId) return stripRecordId(String(raw));
10
+ if ('string' != typeof raw) return raw;
11
+ const separatorIndex = raw.indexOf(':');
12
+ if (-1 === separatorIndex) return raw;
13
+ let id = raw.slice(separatorIndex + 1);
14
+ if (id.startsWith('⟨') && id.endsWith('⟩')) id = id.slice(1, -1);
15
+ return id;
16
+ };
17
+ for (const [field, value] of Object.entries(normalized)){
18
+ let defaultField;
19
+ try {
20
+ defaultField = getDefaultFieldName({
21
+ model: defaultModelName,
22
+ field
23
+ });
24
+ } catch {
25
+ continue;
26
+ }
27
+ const applyStrip = (targetValue)=>{
28
+ if (Array.isArray(targetValue)) return targetValue.map((entry)=>stripRecordId(entry));
29
+ return stripRecordId(targetValue);
30
+ };
31
+ if ('id' === defaultField) {
32
+ normalized[field] = applyStrip(value);
33
+ continue;
34
+ }
35
+ const fieldAttr = getFieldAttributes({
36
+ model: defaultModelName,
37
+ field: defaultField
38
+ });
39
+ if (fieldAttr?.references?.field === 'id') normalized[field] = applyStrip(value);
40
+ }
41
+ return normalized;
42
+ };
43
+ export { normalizeOutputRecordIds };
@@ -0,0 +1,38 @@
1
+ import { deserialize } from "@rolder/kit-surreal-db";
2
+ import { RecordId } from "surrealdb";
3
+ const normalizeRecordIds = ({ model, data, getDefaultModelName, getDefaultFieldName, getFieldAttributes, getModelName })=>{
4
+ const normalized = {
5
+ ...data
6
+ };
7
+ const defaultModelName = getDefaultModelName(model);
8
+ const toRecordId = (target, raw)=>{
9
+ if (raw instanceof RecordId) return raw;
10
+ if ('string' == typeof raw) return raw.includes(':') ? deserialize(raw) : new RecordId(target, raw);
11
+ return raw;
12
+ };
13
+ for (const [field, value] of Object.entries(normalized)){
14
+ let defaultField;
15
+ try {
16
+ defaultField = getDefaultFieldName({
17
+ model: defaultModelName,
18
+ field
19
+ });
20
+ } catch {
21
+ continue;
22
+ }
23
+ if ('id' === defaultField) continue;
24
+ const fieldAttr = getFieldAttributes({
25
+ model: defaultModelName,
26
+ field: defaultField
27
+ });
28
+ if (fieldAttr?.references?.field !== 'id') continue;
29
+ const targetModel = getModelName(fieldAttr.references.model);
30
+ if (Array.isArray(value)) {
31
+ normalized[field] = value.map((entry)=>toRecordId(targetModel, entry));
32
+ continue;
33
+ }
34
+ normalized[field] = toRecordId(targetModel, value);
35
+ }
36
+ return normalized;
37
+ };
38
+ export { normalizeRecordIds };
@@ -0,0 +1,33 @@
1
+ import { deserialize } from "@rolder/kit-surreal-db";
2
+ import { RecordId } from "surrealdb";
3
+ const normalizeWhereValue = ({ model, field, value, getDefaultModelName, getDefaultFieldName, getModelName, getFieldAttributes })=>{
4
+ const defaultModelName = getDefaultModelName(model);
5
+ const toRecordId = (target, raw)=>{
6
+ if (raw instanceof RecordId) return raw;
7
+ if ('string' == typeof raw) return raw.includes(':') ? deserialize(raw) : new RecordId(target, raw);
8
+ return raw;
9
+ };
10
+ let defaultField;
11
+ try {
12
+ defaultField = getDefaultFieldName({
13
+ model: defaultModelName,
14
+ field
15
+ });
16
+ } catch {
17
+ return value;
18
+ }
19
+ if ('id' === defaultField) {
20
+ const targetModel = getModelName(defaultModelName);
21
+ if (Array.isArray(value)) return value.map((entry)=>toRecordId(targetModel, entry));
22
+ return toRecordId(targetModel, value);
23
+ }
24
+ const fieldAttr = getFieldAttributes({
25
+ model: defaultModelName,
26
+ field: defaultField
27
+ });
28
+ if (fieldAttr?.references?.field !== 'id') return value;
29
+ const targetModel = getModelName(fieldAttr.references.model);
30
+ if (Array.isArray(value)) return value.map((entry)=>toRecordId(targetModel, entry));
31
+ return toRecordId(targetModel, value);
32
+ };
33
+ export { normalizeWhereValue };
File without changes
@@ -0,0 +1,15 @@
1
+ import { createServerFn } from "@tanstack/react-start";
2
+ import { getRequestHeaders } from "@tanstack/react-start/server";
3
+ import { baseUrl } from "../env.js";
4
+ const getSessionTokenTanstack = createServerFn().handler(async ()=>{
5
+ const headers = getRequestHeaders();
6
+ const tokenResponse = await fetch(`${baseUrl}/api/auth/token`, {
7
+ headers: {
8
+ cookie: headers.get('cookie') || ''
9
+ }
10
+ });
11
+ if (!tokenResponse.ok) return;
12
+ const { token } = await tokenResponse.json();
13
+ return token;
14
+ });
15
+ export { getSessionTokenTanstack };
@@ -0,0 +1,18 @@
1
+ import { createServerFn } from "@tanstack/react-start";
2
+ import { getRequestHeaders } from "@tanstack/react-start/server";
3
+ import { baseUrl } from "../env.js";
4
+ const getSessionUserFn = createServerFn().handler(async ()=>{
5
+ const headers = getRequestHeaders();
6
+ const sessionResponse = await fetch(`${baseUrl}/api/auth/get-session`, {
7
+ headers: {
8
+ cookie: headers.get('cookie') || ''
9
+ }
10
+ });
11
+ if (!sessionResponse.ok) return;
12
+ const session = await sessionResponse.json();
13
+ return session?.user;
14
+ });
15
+ async function getSessionUserTanstack() {
16
+ return await getSessionUserFn();
17
+ }
18
+ export { getSessionUserTanstack };
@@ -1,2 +1,3 @@
1
+ export { tanstackStartCookies } from 'better-auth/tanstack-start';
1
2
  export * from './getSessionTokenTanstack';
2
3
  export * from './getSessionUserTanstack';
@@ -0,0 +1,4 @@
1
+ import { tanstackStartCookies } from "better-auth/tanstack-start";
2
+ export * from "./getSessionTokenTanstack.js";
3
+ export * from "./getSessionUserTanstack.js";
4
+ export { tanstackStartCookies };
@@ -1,4 +1,3 @@
1
- export type * from 'better-auth/client/plugins';
2
1
  export type * from 'better-auth/minimal';
3
2
  export type * from 'better-auth/plugins';
4
3
  export type InferUserWithRoles<TInferredUser extends {
File without changes
package/package.json CHANGED
@@ -1,11 +1,23 @@
1
1
  {
2
2
  "name": "@rolder/kit-better-auth",
3
- "version": "0.1.0-alpha.2",
3
+ "version": "0.1.0-alpha.3",
4
4
  "type": "module",
5
5
  "exports": {
6
- ".": {
7
- "types": "./dist/index.d.ts",
8
- "import": "./dist/index.js"
6
+ "./server": {
7
+ "types": "./dist/server/index.d.ts",
8
+ "import": "./dist/server/index.js"
9
+ },
10
+ "./server/tanstack": {
11
+ "types": "./dist/server/tanstack/index.d.ts",
12
+ "import": "./dist/server/tanstack/index.js"
13
+ },
14
+ "./client": {
15
+ "types": "./dist/client/index.d.ts",
16
+ "import": "./dist/client/index.js"
17
+ },
18
+ "./client/tanstack": {
19
+ "types": "./dist/client/tanstack/index.d.ts",
20
+ "import": "./dist/client/tanstack/index.js"
9
21
  }
10
22
  },
11
23
  "sideEffects": false,
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export * from './client';
2
- export * from './server';
package/dist/index.js DELETED
@@ -1,2 +0,0 @@
1
- export * from "./client/index.js";
2
- export * from "./server/index.js";