@opensaas/stack-auth 0.20.1 → 0.22.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 (50) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +122 -0
  3. package/CLAUDE.md +115 -17
  4. package/INTEGRATION_SUMMARY.md +21 -20
  5. package/README.md +82 -48
  6. package/dist/config/adopt-better-auth-tables.d.ts +107 -0
  7. package/dist/config/adopt-better-auth-tables.d.ts.map +1 -0
  8. package/dist/config/adopt-better-auth-tables.js +70 -0
  9. package/dist/config/adopt-better-auth-tables.js.map +1 -0
  10. package/dist/config/derive-auth-lists.d.ts +50 -0
  11. package/dist/config/derive-auth-lists.d.ts.map +1 -0
  12. package/dist/config/derive-auth-lists.js +274 -0
  13. package/dist/config/derive-auth-lists.js.map +1 -0
  14. package/dist/config/index.d.ts.map +1 -1
  15. package/dist/config/index.js +43 -0
  16. package/dist/config/index.js.map +1 -1
  17. package/dist/config/plugin.d.ts +1 -1
  18. package/dist/config/plugin.d.ts.map +1 -1
  19. package/dist/config/plugin.js +52 -9
  20. package/dist/config/plugin.js.map +1 -1
  21. package/dist/config/types.d.ts +130 -3
  22. package/dist/config/types.d.ts.map +1 -1
  23. package/dist/index.d.ts +4 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +6 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/lists/index.d.ts +17 -11
  28. package/dist/lists/index.d.ts.map +1 -1
  29. package/dist/lists/index.js +34 -208
  30. package/dist/lists/index.js.map +1 -1
  31. package/dist/server/index.d.ts.map +1 -1
  32. package/dist/server/index.js +28 -7
  33. package/dist/server/index.js.map +1 -1
  34. package/dist/server/schema-converter.d.ts +1 -1
  35. package/dist/server/schema-converter.js +1 -1
  36. package/package.json +3 -3
  37. package/src/config/adopt-better-auth-tables.ts +146 -0
  38. package/src/config/derive-auth-lists.ts +323 -0
  39. package/src/config/index.ts +58 -0
  40. package/src/config/plugin.ts +67 -10
  41. package/src/config/types.ts +146 -3
  42. package/src/index.ts +13 -0
  43. package/src/lists/index.ts +42 -202
  44. package/src/server/index.ts +33 -10
  45. package/src/server/schema-converter.ts +1 -1
  46. package/tests/adopt-better-auth-tables.test.ts +183 -0
  47. package/tests/derive-auth-lists.test.ts +232 -0
  48. package/tests/plugin-derived-keys.test.ts +138 -0
  49. package/tests/plugin-schema-placement.test.ts +121 -0
  50. package/tsconfig.tsbuildinfo +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lists/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lists/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAwBhE;;;;;GAKG;AACH,MAAM,cAAc,GAAyB;IAC3C,IAAI,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE;IACvC,OAAO,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE;IAC7C,OAAO,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE;IAC7C,YAAY,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE;CACxD,CAAA;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC5B,MAA6B;IAG7B,OAAO,eAAe,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,CAAA;AAC3D,CAAC;AAED;;GAEG;AACH,qGAAqG;AACrG,MAAM,UAAU,iBAAiB;IAC/B,OAAO,eAAe,CAAC,cAAc,CAAC,CAAC,KAAK,CAAC,OAAO,CAAA;AACtD,CAAC;AAED;;GAEG;AACH,qGAAqG;AACrG,MAAM,UAAU,iBAAiB;IAC/B,OAAO,eAAe,CAAC,cAAc,CAAC,CAAC,KAAK,CAAC,OAAO,CAAA;AACtD,CAAC;AAED;;GAEG;AACH,qGAAqG;AACrG,MAAM,UAAU,sBAAsB;IACpC,OAAO,eAAe,CAAC,cAAc,CAAC,CAAC,KAAK,CAAC,YAAY,CAAA;AAC3D,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAC1B,UAAiC,EACjC,SAA+B,cAAc;IAG7C,OAAO,eAAe,CAAC,MAAM,EAAE,UAAU,IAAI,EAAE,CAAC,CAAC,KAAK,CAAA;AACxD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACpD,OAAO,KAAK,EAAE,cAAc,EAAkB,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAezF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CACxB,cAAc,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,EACxD,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,iDAmIhD;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,EACnC,aAAa,EAAE,MAAM,EAAE,2CA0BxB;AAED,YAAY,EAAE,iBAAiB,EAAE,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAgCzE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CACxB,cAAc,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,EACxD,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,iDAyIhD;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,EACnC,aAAa,EAAE,MAAM,EAAE,2CA0BxB;AAED,YAAY,EAAE,iBAAiB,EAAE,CAAA"}
@@ -8,6 +8,21 @@ function getDatabaseConfig(dbConfig, context) {
8
8
  provider: dbConfig.provider,
9
9
  });
10
10
  }
11
+ /**
12
+ * Translate a normalized OpenSaaS auth model config into the better-auth
13
+ * per-model options (`modelName` + `fields` column map). Returns `undefined`
14
+ * when there is nothing to override so the running auth instance keeps
15
+ * better-auth's own defaults untouched.
16
+ */
17
+ function toBetterAuthModelOptions(model) {
18
+ const hasFields = Object.keys(model.fields).length > 0;
19
+ const options = {};
20
+ if (model.modelName)
21
+ options.modelName = model.modelName;
22
+ if (hasFields)
23
+ options.fields = model.fields;
24
+ return Object.keys(options).length > 0 ? options : undefined;
25
+ }
11
26
  /**
12
27
  * Create a better-auth instance from OpenSaas config
13
28
  * This should be called once at app startup
@@ -44,6 +59,19 @@ export function createAuth(opensaasConfig, context) {
44
59
  // Build better-auth configuration
45
60
  const betterAuthConfig = {
46
61
  database: getDatabaseConfig(resolvedConfig.db, resolvedContext),
62
+ // Mirror the per-model config (modelName + field column maps) back to
63
+ // better-auth so the running auth instance reads/writes the same
64
+ // tables/columns the OpenSaaS Auth lists were derived from.
65
+ user: toBetterAuthModelOptions(authConfig.models.user),
66
+ session: {
67
+ ...toBetterAuthModelOptions(authConfig.models.session),
68
+ expiresIn: authConfig.session.expiresIn || 604800,
69
+ updateAge: authConfig.session.updateAge
70
+ ? (authConfig.session.expiresIn || 604800) / 10
71
+ : 0,
72
+ },
73
+ account: toBetterAuthModelOptions(authConfig.models.account),
74
+ verification: toBetterAuthModelOptions(authConfig.models.verification),
47
75
  // Enable email and password if configured
48
76
  emailAndPassword: authConfig.emailAndPassword.enabled
49
77
  ? {
@@ -51,13 +79,6 @@ export function createAuth(opensaasConfig, context) {
51
79
  requireEmailVerification: authConfig.emailVerification.enabled,
52
80
  }
53
81
  : undefined,
54
- // Configure session
55
- session: {
56
- expiresIn: authConfig.session.expiresIn || 604800,
57
- updateAge: authConfig.session.updateAge
58
- ? (authConfig.session.expiresIn || 604800) / 10
59
- : 0,
60
- },
61
82
  // Trust host (required for production)
62
83
  trustedOrigins: process.env.BETTER_AUTH_TRUSTED_ORIGINS?.split(',') || [],
63
84
  // Social providers
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
@@ -33,7 +33,7 @@ type BetterAuthTableSchema = {
33
33
  export declare function convertTableToList(tableName: string, tableSchema: BetterAuthTableSchema): ListConfig<any>;
34
34
  /**
35
35
  * Convert all Better Auth tables to OpenSaaS list configs
36
- * This is called by withAuth() to generate lists from Better Auth + plugins
36
+ * This is called by authPlugin() to generate lists from Better Auth + plugins
37
37
  */
38
38
  export declare function convertBetterAuthSchema(tables: Record<string, BetterAuthTableSchema>): Record<string, ListConfig<any>>;
39
39
  export {};
@@ -237,7 +237,7 @@ export function convertTableToList(tableName, tableSchema) {
237
237
  }
238
238
  /**
239
239
  * Convert all Better Auth tables to OpenSaaS list configs
240
- * This is called by withAuth() to generate lists from Better Auth + plugins
240
+ * This is called by authPlugin() to generate lists from Better Auth + plugins
241
241
  */
242
242
  export function convertBetterAuthSchema(tables) {
243
243
  const lists = {}; // eslint-disable-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opensaas/stack-auth",
3
- "version": "0.20.1",
3
+ "version": "0.22.0",
4
4
  "description": "Better-auth integration for OpenSaas Stack",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -55,7 +55,7 @@
55
55
  "react": "^18.0.0 || ^19.0.0"
56
56
  },
57
57
  "devDependencies": {
58
- "@types/node": "^24.12.0",
58
+ "@types/node": "^25.9.1",
59
59
  "@types/react": "^19.2.14",
60
60
  "@vitest/coverage-v8": "^4.0.18",
61
61
  "@vitest/ui": "^4.0.18",
@@ -64,7 +64,7 @@
64
64
  "react": "^19.2.4",
65
65
  "typescript": "^5.9.3",
66
66
  "vitest": "^4.1.0",
67
- "@opensaas/stack-core": "0.20.1"
67
+ "@opensaas/stack-core": "0.22.0"
68
68
  },
69
69
  "scripts": {
70
70
  "build": "tsc",
@@ -0,0 +1,146 @@
1
+ /**
2
+ * "Adopt existing better-auth tables" recipe.
3
+ *
4
+ * A migrating project usually already has a working, hand-wired better-auth
5
+ * installation: its tables are `AuthUser`/`AuthSession`/`AuthAccount`/
6
+ * `AuthVerification`, mapped into a separate `auth` Postgres schema, and its
7
+ * application `User` (`public.User`) is a *different* model. Reconstructing the
8
+ * matching {@link AuthConfig} by hand — four `modelName`s plus a `schema` on each
9
+ * model — is repetitive and easy to get wrong.
10
+ *
11
+ * {@link adoptBetterAuthTables} produces that {@link AuthConfig} fragment from a
12
+ * couple of options so the migrator doesn't rebuild it from scratch. It only
13
+ * sets the *adoption* knobs (per-model `modelName` + the plugin-level `schema`);
14
+ * the developer composes it with the rest of their auth config (providers,
15
+ * session fields, `extendUserList`, etc.):
16
+ *
17
+ * ```typescript
18
+ * authPlugin({
19
+ * ...adoptBetterAuthTables(),
20
+ * emailAndPassword: { enabled: true },
21
+ * sessionFields: ['userId', 'email', 'name'],
22
+ * })
23
+ * ```
24
+ *
25
+ * Combined with the keys/field derivation (`deriveAuthLists`) and schema
26
+ * placement, the generated Auth lists reach **Schema parity** with the live
27
+ * tables — they are modelled for runtime/types without producing a destructive
28
+ * auth migration. The recipe never touches the application's own domain `User`:
29
+ * its model names are `Auth`-prefixed by default and the plugin only ever
30
+ * adds/extends its *derived* keys.
31
+ */
32
+
33
+ import type { AuthConfig, AuthModelConfig } from './types.js'
34
+
35
+ /**
36
+ * Options for {@link adoptBetterAuthTables}.
37
+ *
38
+ * All options are optional and default to the conventions of a standard
39
+ * separate-schema better-auth install (an `auth` Postgres schema with
40
+ * `Auth`-prefixed model names).
41
+ */
42
+ export type AdoptBetterAuthTablesOptions = {
43
+ /**
44
+ * The Postgres schema the live better-auth tables live in.
45
+ *
46
+ * Applied as the plugin-level {@link AuthConfig.schema}, placing every Auth
47
+ * list in this schema via `@@schema(...)`. Pass `'public'` (or any single
48
+ * schema) for an install that is not on a separate schema; pass an explicit
49
+ * value to match your layout.
50
+ *
51
+ * @default 'auth'
52
+ */
53
+ schema?: string
54
+
55
+ /**
56
+ * Prefix applied to each better-auth model name to derive the list key /
57
+ * table name (e.g. prefix `'Auth'` → `AuthUser`/`AuthSession`/...).
58
+ *
59
+ * A live better-auth install with `modelName: 'AuthUser'` etc. is the common
60
+ * case; override this if your tables use a different prefix. Set to `''` to
61
+ * keep the default better-auth names (`User`/`Session`/...) — but note that an
62
+ * unprefixed `User` will share the app's `User` key, so prefer a prefix when
63
+ * your domain `User` is a separate model (see {@link AuthConfig.user}).
64
+ *
65
+ * @default 'Auth'
66
+ */
67
+ modelNamePrefix?: string
68
+
69
+ /**
70
+ * Per-model better-auth field → column maps, keyed by model.
71
+ *
72
+ * Only needed when your live tables renamed columns away from the better-auth
73
+ * defaults (e.g. `name → full_name`). Merged into the matching model config so
74
+ * the derived field-level `@map`s match your live columns.
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * adoptBetterAuthTables({
79
+ * fields: {
80
+ * user: { name: 'full_name' },
81
+ * session: { userId: 'user_id' },
82
+ * },
83
+ * })
84
+ * ```
85
+ */
86
+ fields?: {
87
+ user?: Record<string, string>
88
+ session?: Record<string, string>
89
+ account?: Record<string, string>
90
+ verification?: Record<string, string>
91
+ }
92
+ }
93
+
94
+ /** The four better-auth models and their default (unprefixed) model names. */
95
+ const MODEL_DEFAULT_NAMES = {
96
+ user: 'User',
97
+ session: 'Session',
98
+ account: 'Account',
99
+ verification: 'Verification',
100
+ } as const
101
+
102
+ /**
103
+ * The adoption-relevant slice of {@link AuthConfig}: the plugin-level `schema`
104
+ * and the per-model `modelName`/`fields`. Returned (not the full `AuthConfig`)
105
+ * so it spreads cleanly into the developer's own `authPlugin` config.
106
+ */
107
+ export type AdoptBetterAuthTablesConfig = Pick<
108
+ AuthConfig,
109
+ 'schema' | 'user' | 'session' | 'account' | 'verification'
110
+ >
111
+
112
+ /**
113
+ * Build the adoption {@link AuthConfig} fragment for a pre-existing better-auth
114
+ * installation.
115
+ *
116
+ * Returns only the model/schema knobs needed to adopt the live tables; spread it
117
+ * into {@link authPlugin} alongside your own auth config.
118
+ *
119
+ * @param options - Adoption conventions (schema, model-name prefix, column maps)
120
+ * @returns An {@link AuthConfig} fragment with `schema` + per-model `modelName`
121
+ * (and any field column maps) set to match the live tables
122
+ */
123
+ export function adoptBetterAuthTables(
124
+ options: AdoptBetterAuthTablesOptions = {},
125
+ ): AdoptBetterAuthTablesConfig {
126
+ const { schema = 'auth', modelNamePrefix = 'Auth', fields = {} } = options
127
+
128
+ const buildModel = (model: keyof typeof MODEL_DEFAULT_NAMES): AuthModelConfig => {
129
+ const config: AuthModelConfig = {
130
+ modelName: `${modelNamePrefix}${MODEL_DEFAULT_NAMES[model]}`,
131
+ }
132
+ const fieldMap = fields[model]
133
+ if (fieldMap && Object.keys(fieldMap).length > 0) {
134
+ config.fields = fieldMap
135
+ }
136
+ return config
137
+ }
138
+
139
+ return {
140
+ schema,
141
+ user: buildModel('user'),
142
+ session: buildModel('session'),
143
+ account: buildModel('account'),
144
+ verification: buildModel('verification'),
145
+ }
146
+ }
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Pure `better-auth config → Auth lists` derivation.
3
+ *
4
+ * This module is intentionally free of side effects and plugin/runtime
5
+ * concerns: given the resolved better-auth model config (per-model `modelName`
6
+ * and `fields` column maps) plus any custom User fields, it produces the four
7
+ * OpenSaaS Auth lists (user/session/account/verification) with:
8
+ *
9
+ * - list keys taken from each model's `modelName`
10
+ * - a table `@@map` (list-level `db.map`) when the key differs from the
11
+ * default better-auth model name
12
+ * - field-level `@map` (`db.map`) for any better-auth field → column override
13
+ * - relationship refs between the auth lists wired to the *derived* keys
14
+ * (e.g. `Session.user → AuthUser.sessions`)
15
+ *
16
+ * When the developer supplies no `modelName`/`fields` overrides, the output is
17
+ * byte-for-byte the historical default set keyed `User`/`Session`/`Account`/
18
+ * `Verification` with the original field shapes — see the unit tests.
19
+ *
20
+ * `getAuthLists`/`convertBetterAuthSchema` (and the runtime user-key
21
+ * resolution) consume this module so derivation lives in exactly one place.
22
+ */
23
+
24
+ import { list } from '@opensaas/stack-core'
25
+ import { text, timestamp, checkbox, relationship } from '@opensaas/stack-core/fields'
26
+ import type { ListConfig } from '@opensaas/stack-core'
27
+ import type { ExtendUserListConfig } from '../lists/index.js'
28
+ import type { NormalizedAuthModelConfig, NormalizedAuthModels } from './types.js'
29
+
30
+ /**
31
+ * Default better-auth model names — used to decide whether a `@@map` is needed
32
+ * (only when the configured `modelName` differs from the default).
33
+ */
34
+ const DEFAULT_MODEL_NAMES = {
35
+ user: 'User',
36
+ session: 'Session',
37
+ account: 'Account',
38
+ verification: 'Verification',
39
+ } as const
40
+
41
+ /**
42
+ * The derived Auth list set together with the keys each list was placed under.
43
+ * Keys are surfaced separately so callers (plugin add-vs-extend logic, runtime
44
+ * user-key resolution) don't have to re-derive them.
45
+ */
46
+ export type DerivedAuthLists = {
47
+ /** Derived list keys, one per better-auth model. */
48
+ keys: {
49
+ user: string
50
+ session: string
51
+ account: string
52
+ verification: string
53
+ }
54
+ /** The derived list configs, keyed by their derived list keys. */
55
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
56
+ lists: Record<string, ListConfig<any>>
57
+ }
58
+
59
+ /**
60
+ * Build the list-level `db` config (`timestamps` + `@@map` + `@@schema`) for a
61
+ * derived list.
62
+ *
63
+ * Always opts the list into auto-timestamps (`timestamps: true`). better-auth's
64
+ * adapter writes `createdAt`/`updatedAt` on every auth row and the schema
65
+ * converter returns `null` for those columns (it assumes the generator injects
66
+ * them). Now that auto-timestamps are OFF by default (ADR-0004), each derived
67
+ * Auth list must opt back in so the generated models keep those columns and
68
+ * better-auth keeps working.
69
+ *
70
+ * When the developer renames the model (e.g. `modelName: 'AuthUser'`), we also
71
+ * pin the physical table name to that model name via `@@map("AuthUser")` so the
72
+ * generated list adopts the developer's live table exactly. When a `schema` is
73
+ * configured (plugin-level or per-model), the list is placed in that Postgres
74
+ * schema via `@@schema(...)`.
75
+ *
76
+ * With no `modelName`/`schema` overrides we emit only `timestamps: true`,
77
+ * leaving the default `User`/`Session`/... table/schema output unchanged.
78
+ */
79
+ function listDb(
80
+ model: NormalizedAuthModelConfig,
81
+ defaultModelName: string,
82
+ ): { timestamps: true; map?: string; schema?: string } {
83
+ const map = model.modelName !== defaultModelName ? model.modelName : undefined
84
+ const schema = model.schema
85
+ return {
86
+ timestamps: true,
87
+ ...(map !== undefined ? { map } : {}),
88
+ ...(schema !== undefined ? { schema } : {}),
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Resolve the `db.map` (`@map` column override) for a better-auth field, or
94
+ * `undefined` when there is no override (so default output is unchanged).
95
+ *
96
+ * The field builders capture `options.db` in a closure when generating Prisma
97
+ * types, so the column map MUST be passed through the builder's `db` option
98
+ * rather than patched onto the returned field object.
99
+ */
100
+ function fieldDb(fieldName: string, fields: Record<string, string>): { map: string } | undefined {
101
+ const column = fields[fieldName]
102
+ return column ? { map: column } : undefined
103
+ }
104
+
105
+ /**
106
+ * Build the foreign-key `db` config for a `user` relationship, honouring a
107
+ * `userId` column override from the better-auth `fields` map.
108
+ */
109
+ function userForeignKeyDb(
110
+ fields: Record<string, string>,
111
+ ): { foreignKey: { map: string } } | undefined {
112
+ const column = fields.userId
113
+ return column ? { foreignKey: { map: column } } : undefined
114
+ }
115
+
116
+ /**
117
+ * Create the Auth user list, applying derived field column maps + table map and
118
+ * wiring the session/account relationships to the derived keys.
119
+ */
120
+ function createUserList(
121
+ model: NormalizedAuthModelConfig,
122
+ keys: DerivedAuthLists['keys'],
123
+ userConfig: ExtendUserListConfig,
124
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
125
+ ): ListConfig<any> {
126
+ const f = model.fields
127
+ return list({
128
+ fields: {
129
+ name: text({ validation: { isRequired: true }, db: fieldDb('name', f) }),
130
+ email: text({
131
+ validation: { isRequired: true },
132
+ isIndexed: 'unique',
133
+ db: fieldDb('email', f),
134
+ }),
135
+ emailVerified: checkbox({ defaultValue: false, db: fieldDb('emailVerified', f) }),
136
+ image: text({ db: fieldDb('image', f) }),
137
+
138
+ // Relationships to the other auth lists — refs follow the derived keys.
139
+ sessions: relationship({ ref: `${keys.session}.user`, many: true }),
140
+ accounts: relationship({ ref: `${keys.account}.user`, many: true }),
141
+
142
+ // Custom fields from user config
143
+ ...(userConfig.fields || {}),
144
+ },
145
+ db: listDb(model, DEFAULT_MODEL_NAMES.user),
146
+ access: userConfig.access || {
147
+ operation: {
148
+ query: () => true,
149
+ create: () => true,
150
+ update: ({ session, item }) => {
151
+ if (!session) return false
152
+ const userId = (session as { userId?: string }).userId
153
+ const itemId = (item as { id?: string })?.id
154
+ return userId === itemId
155
+ },
156
+ delete: ({ session, item }) => {
157
+ if (!session) return false
158
+ const userId = (session as { userId?: string }).userId
159
+ const itemId = (item as { id?: string })?.id
160
+ return userId === itemId
161
+ },
162
+ },
163
+ },
164
+ hooks: userConfig.hooks,
165
+ })
166
+ }
167
+
168
+ /**
169
+ * Create the Auth session list.
170
+ */
171
+ function createSessionList(
172
+ model: NormalizedAuthModelConfig,
173
+ keys: DerivedAuthLists['keys'],
174
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
175
+ ): ListConfig<any> {
176
+ const f = model.fields
177
+ return list({
178
+ fields: {
179
+ token: text({
180
+ validation: { isRequired: true },
181
+ isIndexed: 'unique',
182
+ db: fieldDb('token', f),
183
+ }),
184
+ expiresAt: timestamp({ db: fieldDb('expiresAt', f) }),
185
+ ipAddress: text({ db: fieldDb('ipAddress', f) }),
186
+ userAgent: text({ db: fieldDb('userAgent', f) }),
187
+ user: relationship({
188
+ ref: `${keys.user}.sessions`,
189
+ db: userForeignKeyDb(f),
190
+ }),
191
+ },
192
+ db: listDb(model, DEFAULT_MODEL_NAMES.session),
193
+ access: {
194
+ operation: {
195
+ query: ({ session }) => {
196
+ if (!session) return false
197
+ const userId = (session as { userId?: string }).userId
198
+ if (!userId) return false
199
+ return {
200
+ user: { id: { equals: userId } },
201
+ } as Record<string, unknown>
202
+ },
203
+ create: () => true,
204
+ update: () => false,
205
+ delete: ({ session, item }) => {
206
+ if (!session) return false
207
+ const userId = (session as { userId?: string }).userId
208
+ const itemUserId = (item as { user?: { id?: string } })?.user?.id
209
+ return userId === itemUserId
210
+ },
211
+ },
212
+ },
213
+ })
214
+ }
215
+
216
+ /**
217
+ * Create the Auth account list.
218
+ */
219
+ function createAccountList(
220
+ model: NormalizedAuthModelConfig,
221
+ keys: DerivedAuthLists['keys'],
222
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
223
+ ): ListConfig<any> {
224
+ const f = model.fields
225
+ return list({
226
+ fields: {
227
+ accountId: text({ validation: { isRequired: true }, db: fieldDb('accountId', f) }),
228
+ providerId: text({ validation: { isRequired: true }, db: fieldDb('providerId', f) }),
229
+ user: relationship({
230
+ ref: `${keys.user}.accounts`,
231
+ db: userForeignKeyDb(f),
232
+ }),
233
+ accessToken: text({ db: fieldDb('accessToken', f) }),
234
+ refreshToken: text({ db: fieldDb('refreshToken', f) }),
235
+ accessTokenExpiresAt: timestamp({ db: fieldDb('accessTokenExpiresAt', f) }),
236
+ refreshTokenExpiresAt: timestamp({ db: fieldDb('refreshTokenExpiresAt', f) }),
237
+ scope: text({ db: fieldDb('scope', f) }),
238
+ idToken: text({ db: fieldDb('idToken', f) }),
239
+ password: text({ db: fieldDb('password', f) }),
240
+ },
241
+ db: listDb(model, DEFAULT_MODEL_NAMES.account),
242
+ access: {
243
+ operation: {
244
+ query: ({ session }) => {
245
+ if (!session) return false
246
+ const userId = (session as { userId?: string }).userId
247
+ if (!userId) return false
248
+ return {
249
+ user: { id: { equals: userId } },
250
+ } as Record<string, unknown>
251
+ },
252
+ create: () => true,
253
+ update: ({ session, item }) => {
254
+ if (!session) return false
255
+ const userId = (session as { userId?: string }).userId
256
+ const itemUserId = (item as { user?: { id?: string } })?.user?.id
257
+ return userId === itemUserId
258
+ },
259
+ delete: ({ session, item }) => {
260
+ if (!session) return false
261
+ const userId = (session as { userId?: string }).userId
262
+ const itemUserId = (item as { user?: { id?: string } })?.user?.id
263
+ return userId === itemUserId
264
+ },
265
+ },
266
+ },
267
+ })
268
+ }
269
+
270
+ /**
271
+ * Create the Auth verification list.
272
+ */
273
+ function createVerificationList(
274
+ model: NormalizedAuthModelConfig,
275
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
276
+ ): ListConfig<any> {
277
+ const f = model.fields
278
+ return list({
279
+ fields: {
280
+ identifier: text({ validation: { isRequired: true }, db: fieldDb('identifier', f) }),
281
+ value: text({ validation: { isRequired: true }, db: fieldDb('value', f) }),
282
+ expiresAt: timestamp({ db: fieldDb('expiresAt', f) }),
283
+ },
284
+ db: listDb(model, DEFAULT_MODEL_NAMES.verification),
285
+ access: {
286
+ operation: {
287
+ query: () => false,
288
+ create: () => true,
289
+ update: () => false,
290
+ delete: () => true,
291
+ },
292
+ },
293
+ })
294
+ }
295
+
296
+ /**
297
+ * Derive the OpenSaaS Auth lists from the resolved better-auth model config.
298
+ *
299
+ * @param models - Resolved better-auth per-model config (modelName + field column maps)
300
+ * @param userConfig - Extra User-list fields/access/hooks supplied via `extendUserList`
301
+ * @returns The derived list keys and the four Auth list configs keyed by those keys
302
+ */
303
+ export function deriveAuthLists(
304
+ models: NormalizedAuthModels,
305
+ userConfig: ExtendUserListConfig = {},
306
+ ): DerivedAuthLists {
307
+ const keys = {
308
+ user: models.user.modelName,
309
+ session: models.session.modelName,
310
+ account: models.account.modelName,
311
+ verification: models.verification.modelName,
312
+ }
313
+
314
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
315
+ const lists: Record<string, ListConfig<any>> = {
316
+ [keys.user]: createUserList(models.user, keys, userConfig),
317
+ [keys.session]: createSessionList(models.session, keys),
318
+ [keys.account]: createAccountList(models.account, keys),
319
+ [keys.verification]: createVerificationList(models.verification),
320
+ }
321
+
322
+ return { keys, lists }
323
+ }