@hexis-ai/engram-server 0.11.3 → 0.12.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.
@@ -0,0 +1,37 @@
1
+ import type { Pool } from "pg";
2
+ import type { OrgMembershipRow, OrgRow, OrgStore } from "../org-store";
3
+ export declare class PostgresOrgStore implements OrgStore {
4
+ private readonly pool;
5
+ constructor(pool: Pool);
6
+ createOrg(input: {
7
+ id?: string;
8
+ name?: string;
9
+ metadata?: Record<string, unknown>;
10
+ }): Promise<OrgRow>;
11
+ getOrg(id: string): Promise<OrgRow | null>;
12
+ listOrgs(): Promise<OrgRow[]>;
13
+ deleteOrg(id: string): Promise<void>;
14
+ listMembers(orgId: string): Promise<OrgMembershipRow[]>;
15
+ upsertMember(input: {
16
+ orgId: string;
17
+ userId: string;
18
+ role?: string;
19
+ }): Promise<OrgMembershipRow>;
20
+ removeMember(orgId: string, userId: string): Promise<void>;
21
+ findUserByEmail(email: string): Promise<{
22
+ id: string;
23
+ email: string;
24
+ } | null>;
25
+ listOrgsForUser(userId: string): Promise<OrgMembershipRow[]>;
26
+ setWorkspaceOrg(workspaceId: string, orgId: string): Promise<void>;
27
+ listWorkspacesForOrg(orgId: string): Promise<{
28
+ id: string;
29
+ name: string | null;
30
+ }[]>;
31
+ listWorkspacesForUser(userId: string): Promise<{
32
+ id: string;
33
+ name: string | null;
34
+ orgId: string;
35
+ }[]>;
36
+ userCanAccessWorkspace(userId: string, workspaceId: string): Promise<boolean>;
37
+ }
@@ -0,0 +1,102 @@
1
+ import { randomUUID } from "node:crypto";
2
+ function iso(v) {
3
+ return typeof v === "string" ? v : v.toISOString();
4
+ }
5
+ function toOrg(r) {
6
+ return {
7
+ id: r.id,
8
+ name: r.name,
9
+ metadata: r.metadata ?? {},
10
+ createdAt: iso(r.created_at),
11
+ };
12
+ }
13
+ function toMember(r) {
14
+ return {
15
+ orgId: r.org_id,
16
+ userId: r.user_id,
17
+ role: r.role,
18
+ joinedAt: iso(r.joined_at),
19
+ };
20
+ }
21
+ export class PostgresOrgStore {
22
+ pool;
23
+ constructor(pool) {
24
+ this.pool = pool;
25
+ }
26
+ // ---------- orgs ---------------------------------------------
27
+ async createOrg(input) {
28
+ const id = input.id ?? `org_${randomUUID().replace(/-/g, "").slice(0, 16)}`;
29
+ const { rows } = await this.pool.query(`INSERT INTO engram_orgs (id, name, metadata)
30
+ VALUES ($1, $2, $3::jsonb)
31
+ ON CONFLICT (id) DO NOTHING
32
+ RETURNING id, name, metadata, created_at`, [id, input.name ?? null, JSON.stringify(input.metadata ?? {})]);
33
+ if (rows[0])
34
+ return toOrg(rows[0]);
35
+ const existing = await this.getOrg(id);
36
+ if (!existing)
37
+ throw new Error("org_create_failed");
38
+ return existing;
39
+ }
40
+ async getOrg(id) {
41
+ const { rows } = await this.pool.query(`SELECT id, name, metadata, created_at FROM engram_orgs WHERE id = $1`, [id]);
42
+ return rows[0] ? toOrg(rows[0]) : null;
43
+ }
44
+ async listOrgs() {
45
+ const { rows } = await this.pool.query(`SELECT id, name, metadata, created_at FROM engram_orgs ORDER BY created_at`);
46
+ return rows.map(toOrg);
47
+ }
48
+ async deleteOrg(id) {
49
+ // CASCADE drops members + workspaces under this org.
50
+ await this.pool.query(`DELETE FROM engram_orgs WHERE id = $1`, [id]);
51
+ }
52
+ // ---------- members ------------------------------------------
53
+ async listMembers(orgId) {
54
+ const { rows } = await this.pool.query(`SELECT org_id, user_id, role, joined_at
55
+ FROM engram_org_members WHERE org_id = $1 ORDER BY joined_at`, [orgId]);
56
+ return rows.map(toMember);
57
+ }
58
+ async upsertMember(input) {
59
+ const role = input.role ?? "member";
60
+ const { rows } = await this.pool.query(`INSERT INTO engram_org_members (org_id, user_id, role)
61
+ VALUES ($1, $2, $3)
62
+ ON CONFLICT (org_id, user_id) DO UPDATE SET role = EXCLUDED.role
63
+ RETURNING org_id, user_id, role, joined_at`, [input.orgId, input.userId, role]);
64
+ return toMember(rows[0]);
65
+ }
66
+ async removeMember(orgId, userId) {
67
+ await this.pool.query(`DELETE FROM engram_org_members WHERE org_id = $1 AND user_id = $2`, [orgId, userId]);
68
+ }
69
+ async findUserByEmail(email) {
70
+ const { rows } = await this.pool.query(`SELECT id, email FROM engram_auth_users WHERE LOWER(email) = LOWER($1) LIMIT 1`, [email]);
71
+ return rows[0] ?? null;
72
+ }
73
+ async listOrgsForUser(userId) {
74
+ const { rows } = await this.pool.query(`SELECT org_id, user_id, role, joined_at
75
+ FROM engram_org_members WHERE user_id = $1 ORDER BY joined_at`, [userId]);
76
+ return rows.map(toMember);
77
+ }
78
+ // ---------- workspace ↔ org link -----------------------------
79
+ async setWorkspaceOrg(workspaceId, orgId) {
80
+ await this.pool.query(`UPDATE engram_workspaces SET org_id = $1 WHERE id = $2`, [orgId, workspaceId]);
81
+ }
82
+ async listWorkspacesForOrg(orgId) {
83
+ const { rows } = await this.pool.query(`SELECT id, name FROM engram_workspaces WHERE org_id = $1 ORDER BY created_at`, [orgId]);
84
+ return rows;
85
+ }
86
+ async listWorkspacesForUser(userId) {
87
+ const { rows } = await this.pool.query(`SELECT w.id, w.name, w.org_id
88
+ FROM engram_workspaces w
89
+ JOIN engram_org_members m ON m.org_id = w.org_id
90
+ WHERE m.user_id = $1
91
+ ORDER BY w.created_at`, [userId]);
92
+ return rows.map((r) => ({ id: r.id, name: r.name, orgId: r.org_id }));
93
+ }
94
+ async userCanAccessWorkspace(userId, workspaceId) {
95
+ const { rows } = await this.pool.query(`SELECT 1 AS ok
96
+ FROM engram_workspaces w
97
+ JOIN engram_org_members m ON m.org_id = w.org_id
98
+ WHERE m.user_id = $1 AND w.id = $2
99
+ LIMIT 1`, [userId, workspaceId]);
100
+ return rows.length > 0;
101
+ }
102
+ }
package/dist/admin.d.ts CHANGED
@@ -1,9 +1,17 @@
1
1
  import { Hono } from "hono";
2
2
  import { type KeyStore } from "./key-store";
3
+ import type { OrgStore } from "./org-store";
3
4
  export interface AdminOptions {
4
5
  /** Bearer token required for every /admin/v1 request. */
5
6
  token: string;
6
7
  keyStore: KeyStore;
8
+ /**
9
+ * Optional org store. When provided, mounts the orgs surface
10
+ * (\`/admin/v1/orgs/*\` plus \`/admin/v1/orgs/:id/workspaces\` for
11
+ * org-scoped workspace creation). Without it only the legacy
12
+ * \`/admin/v1/workspaces\` endpoints are exposed.
13
+ */
14
+ orgStore?: OrgStore;
7
15
  }
8
16
  interface Env {
9
17
  Variables: {
@@ -11,11 +19,25 @@ interface Env {
11
19
  };
12
20
  }
13
21
  /**
14
- * Build the admin sub-router. Mount under `/admin/v1`.
22
+ * Build the admin sub-router. Mount under \`/admin/v1\`.
15
23
  *
16
- * Auth model: a single platform-level bearer token (`ENGRAM_ADMIN_TOKEN`).
17
- * The admin token is checked here only — it never reaches the workspace
18
- * KeyStore, so an admin token cannot accidentally double as a workspace key.
24
+ * Auth model: a single platform-level bearer token
25
+ * (\`ENGRAM_ADMIN_TOKEN\`). Never crosses with workspace api-keys.
26
+ *
27
+ * Surface, when orgStore is wired:
28
+ *
29
+ * POST /orgs create org
30
+ * GET /orgs list orgs
31
+ * GET /orgs/:id get org
32
+ * DELETE /orgs/:id delete org (CASCADE)
33
+ * POST /orgs/:id/members add member (email|userId)
34
+ * GET /orgs/:id/members
35
+ * DELETE /orgs/:id/members/:userId
36
+ * POST /orgs/:id/workspaces create + key (under org)
37
+ * GET /orgs/:id/workspaces
38
+ *
39
+ * Plus the lower-level workspace + key endpoints from before
40
+ * (\`/workspaces\`, \`/workspaces/:id/keys\`, ...).
19
41
  */
20
42
  export declare function createAdminRouter(opts: AdminOptions): Hono<Env>;
21
43
  export {};
package/dist/admin.js CHANGED
@@ -2,11 +2,25 @@ import { Hono } from "hono";
2
2
  import { isValidWorkspaceId } from "./key-store";
3
3
  import { createWorkspaceSchema, issueKeySchema, parseJsonBody } from "./schemas";
4
4
  /**
5
- * Build the admin sub-router. Mount under `/admin/v1`.
5
+ * Build the admin sub-router. Mount under \`/admin/v1\`.
6
6
  *
7
- * Auth model: a single platform-level bearer token (`ENGRAM_ADMIN_TOKEN`).
8
- * The admin token is checked here only — it never reaches the workspace
9
- * KeyStore, so an admin token cannot accidentally double as a workspace key.
7
+ * Auth model: a single platform-level bearer token
8
+ * (\`ENGRAM_ADMIN_TOKEN\`). Never crosses with workspace api-keys.
9
+ *
10
+ * Surface, when orgStore is wired:
11
+ *
12
+ * POST /orgs create org
13
+ * GET /orgs list orgs
14
+ * GET /orgs/:id get org
15
+ * DELETE /orgs/:id delete org (CASCADE)
16
+ * POST /orgs/:id/members add member (email|userId)
17
+ * GET /orgs/:id/members
18
+ * DELETE /orgs/:id/members/:userId
19
+ * POST /orgs/:id/workspaces create + key (under org)
20
+ * GET /orgs/:id/workspaces
21
+ *
22
+ * Plus the lower-level workspace + key endpoints from before
23
+ * (\`/workspaces\`, \`/workspaces/:id/keys\`, ...).
10
24
  */
11
25
  export function createAdminRouter(opts) {
12
26
  const app = new Hono();
@@ -18,6 +32,9 @@ export function createAdminRouter(opts) {
18
32
  }
19
33
  await next();
20
34
  });
35
+ // -------------------- workspaces (legacy / api-key-only) ----
36
+ // Kept for backwards compatibility; new callers should create
37
+ // workspaces under an org so they're reachable from the web UI.
21
38
  app.post("/workspaces", async (c) => {
22
39
  const body = await parseJsonBody(c, createWorkspaceSchema);
23
40
  if (body instanceof Response)
@@ -31,8 +48,6 @@ export function createAdminRouter(opts) {
31
48
  ...(body.name !== undefined ? { name: body.name } : {}),
32
49
  ...(body.metadata !== undefined ? { metadata: body.metadata } : {}),
33
50
  });
34
- // Default: issue an initial key so the caller can start using the
35
- // workspace in one round trip. Opt out with `issueKey: false`.
36
51
  if (body.issueKey === false) {
37
52
  return c.json({ workspace: ws });
38
53
  }
@@ -68,7 +83,6 @@ export function createAdminRouter(opts) {
68
83
  const ws = await opts.keyStore.getWorkspace(workspaceId);
69
84
  if (!ws)
70
85
  return c.json({ error: "workspace_not_found" }, 404);
71
- // The body is optional here — an empty POST issues a key with no name.
72
86
  const raw = await c.req.json().catch(() => ({}));
73
87
  const parsed = issueKeySchema.safeParse(raw);
74
88
  if (!parsed.success) {
@@ -101,5 +115,121 @@ export function createAdminRouter(opts) {
101
115
  }
102
116
  return c.body(null, 204);
103
117
  });
118
+ // -------------------- orgs (org-scoped admin surface) -------
119
+ const orgStore = opts.orgStore;
120
+ if (!orgStore)
121
+ return app;
122
+ app.post("/orgs", async (c) => {
123
+ const body = (await c.req.json().catch(() => ({})));
124
+ try {
125
+ const org = await orgStore.createOrg({
126
+ ...(body.id !== undefined ? { id: body.id } : {}),
127
+ ...(body.name !== undefined ? { name: body.name } : {}),
128
+ ...(body.metadata !== undefined ? { metadata: body.metadata } : {}),
129
+ });
130
+ return c.json({ org });
131
+ }
132
+ catch (e) {
133
+ return c.json({ error: e.message }, 400);
134
+ }
135
+ });
136
+ app.get("/orgs", async (c) => {
137
+ const orgs = await orgStore.listOrgs();
138
+ return c.json({ orgs });
139
+ });
140
+ app.get("/orgs/:id", async (c) => {
141
+ const org = await orgStore.getOrg(c.req.param("id"));
142
+ if (!org)
143
+ return c.json({ error: "org_not_found" }, 404);
144
+ return c.json({ org });
145
+ });
146
+ app.delete("/orgs/:id", async (c) => {
147
+ const id = c.req.param("id");
148
+ const org = await orgStore.getOrg(id);
149
+ if (!org)
150
+ return c.json({ error: "org_not_found" }, 404);
151
+ await orgStore.deleteOrg(id);
152
+ return c.body(null, 204);
153
+ });
154
+ // ----- org members ------------------------------------------
155
+ app.get("/orgs/:id/members", async (c) => {
156
+ const id = c.req.param("id");
157
+ const org = await orgStore.getOrg(id);
158
+ if (!org)
159
+ return c.json({ error: "org_not_found" }, 404);
160
+ return c.json({ members: await orgStore.listMembers(id) });
161
+ });
162
+ app.post("/orgs/:id/members", async (c) => {
163
+ const orgId = c.req.param("id");
164
+ const org = await orgStore.getOrg(orgId);
165
+ if (!org)
166
+ return c.json({ error: "org_not_found" }, 404);
167
+ const body = (await c.req.json().catch(() => ({})));
168
+ let userId = body.userId;
169
+ if (!userId && body.email) {
170
+ const u = await orgStore.findUserByEmail(body.email);
171
+ if (!u)
172
+ return c.json({ error: "user_not_found" }, 404);
173
+ userId = u.id;
174
+ }
175
+ if (!userId)
176
+ return c.json({ error: "userId_or_email_required" }, 400);
177
+ const member = await orgStore.upsertMember({
178
+ orgId,
179
+ userId,
180
+ ...(body.role !== undefined ? { role: body.role } : {}),
181
+ });
182
+ return c.json({ member });
183
+ });
184
+ app.delete("/orgs/:id/members/:userId", async (c) => {
185
+ const orgId = c.req.param("id");
186
+ const org = await orgStore.getOrg(orgId);
187
+ if (!org)
188
+ return c.json({ error: "org_not_found" }, 404);
189
+ await orgStore.removeMember(orgId, c.req.param("userId"));
190
+ return c.body(null, 204);
191
+ });
192
+ // ----- org workspaces ---------------------------------------
193
+ // Combines createWorkspace + setWorkspaceOrg + issueKey so a
194
+ // tenant can be stood up in one round-trip. Mirrors the legacy
195
+ // /workspaces POST shape but adds org scoping.
196
+ app.post("/orgs/:id/workspaces", async (c) => {
197
+ const orgId = c.req.param("id");
198
+ const org = await orgStore.getOrg(orgId);
199
+ if (!org)
200
+ return c.json({ error: "org_not_found" }, 404);
201
+ const body = await parseJsonBody(c, createWorkspaceSchema);
202
+ if (body instanceof Response)
203
+ return body;
204
+ if (body.id !== undefined && !isValidWorkspaceId(body.id)) {
205
+ return c.json({ error: "invalid_workspace_id" }, 400);
206
+ }
207
+ try {
208
+ const ws = await opts.keyStore.createWorkspace({
209
+ ...(body.id !== undefined ? { id: body.id } : {}),
210
+ ...(body.name !== undefined ? { name: body.name } : {}),
211
+ ...(body.metadata !== undefined ? { metadata: body.metadata } : {}),
212
+ });
213
+ await orgStore.setWorkspaceOrg(ws.id, orgId);
214
+ if (body.issueKey === false) {
215
+ return c.json({ workspace: { ...ws, orgId } });
216
+ }
217
+ const key = await opts.keyStore.issueKey(ws.id, {
218
+ ...(body.keyName !== undefined ? { name: body.keyName } : {}),
219
+ });
220
+ return c.json({ workspace: { ...ws, orgId }, key });
221
+ }
222
+ catch (e) {
223
+ return c.json({ error: e.message }, 400);
224
+ }
225
+ });
226
+ app.get("/orgs/:id/workspaces", async (c) => {
227
+ const orgId = c.req.param("id");
228
+ const org = await orgStore.getOrg(orgId);
229
+ if (!org)
230
+ return c.json({ error: "org_not_found" }, 404);
231
+ const workspaces = await orgStore.listWorkspacesForOrg(orgId);
232
+ return c.json({ workspaces });
233
+ });
104
234
  return app;
105
235
  }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Cookie-session → WorkspaceContext bridge (org-based).
3
+ *
4
+ * Each user joins an org once; the org has many workspaces. To
5
+ * resolve a request:
6
+ *
7
+ * 1) Validate the cookie via better-auth.
8
+ * 2) Look up every workspace visible to the user via
9
+ * \`engram_org_members\` JOIN \`engram_workspaces\`.
10
+ * 3) Pick one by, in order:
11
+ * a) x-workspace-id header (verified against the visible list)
12
+ * b) session.currentWorkspaceId (sticky from last UI choice)
13
+ * c) the user's only visible workspace, if exactly one exists
14
+ *
15
+ * Membership at the workspace level is intentionally not modeled —
16
+ * any org member sees every workspace in that org. Add finer-grained
17
+ * scoping at the app level if needed later.
18
+ */
19
+ import type { Pool } from "pg";
20
+ import type { EngramAuth } from "./auth";
21
+ import type { WorkspaceContext } from "./context";
22
+ import type { OrgStore } from "./org-store";
23
+ import type { StorageAdapter } from "./storage";
24
+ export interface CookieResolverOptions {
25
+ auth: EngramAuth;
26
+ pool: Pool;
27
+ orgStore: OrgStore;
28
+ /** Workspace-scoped storage factory; same one wired into api-key auth. */
29
+ getStorage: (workspaceId: string) => StorageAdapter;
30
+ }
31
+ export type CookieAuthResolver = (req: Request) => Promise<WorkspaceContext | null>;
32
+ export declare function makeCookieAuthResolver(opts: CookieResolverOptions): CookieAuthResolver;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Cookie-session → WorkspaceContext bridge (org-based).
3
+ *
4
+ * Each user joins an org once; the org has many workspaces. To
5
+ * resolve a request:
6
+ *
7
+ * 1) Validate the cookie via better-auth.
8
+ * 2) Look up every workspace visible to the user via
9
+ * \`engram_org_members\` JOIN \`engram_workspaces\`.
10
+ * 3) Pick one by, in order:
11
+ * a) x-workspace-id header (verified against the visible list)
12
+ * b) session.currentWorkspaceId (sticky from last UI choice)
13
+ * c) the user's only visible workspace, if exactly one exists
14
+ *
15
+ * Membership at the workspace level is intentionally not modeled —
16
+ * any org member sees every workspace in that org. Add finer-grained
17
+ * scoping at the app level if needed later.
18
+ */
19
+ async function sessionWorkspace(pool, sessionId) {
20
+ const { rows } = await pool.query(`SELECT current_workspace_id FROM engram_auth_sessions WHERE id = $1 LIMIT 1`, [sessionId]);
21
+ return rows[0]?.current_workspace_id ?? null;
22
+ }
23
+ export function makeCookieAuthResolver(opts) {
24
+ return async (req) => {
25
+ const session = await opts.auth.api
26
+ .getSession({ headers: req.headers })
27
+ .catch(() => null);
28
+ if (!session?.user)
29
+ return null;
30
+ const visible = await opts.orgStore.listWorkspacesForUser(session.user.id);
31
+ if (visible.length === 0)
32
+ return null;
33
+ const allowed = new Set(visible.map((w) => w.id));
34
+ const headerWs = req.headers.get("x-workspace-id");
35
+ let pick = null;
36
+ if (headerWs) {
37
+ if (allowed.has(headerWs))
38
+ pick = headerWs;
39
+ else
40
+ return null; // explicit header that isn't allowed → 401
41
+ }
42
+ else {
43
+ const stickied = await sessionWorkspace(opts.pool, session.session.id);
44
+ if (stickied && allowed.has(stickied))
45
+ pick = stickied;
46
+ else if (visible.length === 1)
47
+ pick = visible[0].id;
48
+ }
49
+ if (!pick)
50
+ return null;
51
+ return { workspaceId: pick, storage: opts.getStorage(pick) };
52
+ };
53
+ }
package/dist/auth.d.ts ADDED
@@ -0,0 +1,196 @@
1
+ /**
2
+ * better-auth instance for engram-server.
3
+ *
4
+ * Two auth modes coexist on /v1/*:
5
+ * - x-api-key / Bearer → machine callers (monet telemetry / MCP),
6
+ * resolved through KeyStore → workspace.
7
+ * - cookie session → human users from engram-web,
8
+ * resolved through this module → user →
9
+ * membership check → workspace.
10
+ *
11
+ * Tables live under \`engram_auth_*\` (see migration 0006-auth) so the
12
+ * shorter \`engram_session\` namespace remains agent-trajectory only.
13
+ *
14
+ * Required env (production):
15
+ * ENGRAM_AUTH_SECRET cookie/JWT signing secret (>= 32 chars)
16
+ * ENGRAM_AUTH_URL full base URL of this server
17
+ * (e.g. https://api.engram.hexis.ltd)
18
+ * ENGRAM_AUTH_GOOGLE_ID Google OAuth client id
19
+ * ENGRAM_AUTH_GOOGLE_SECRET Google OAuth client secret
20
+ *
21
+ * Optional:
22
+ * ENGRAM_AUTH_COOKIE_DOMAIN set to .hexis.ltd to share cookies
23
+ * between engram.hexis.ltd (web) and
24
+ * api.engram.hexis.ltd (this server)
25
+ * ENGRAM_AUTH_TRUSTED_ORIGINS comma-separated extra origins, e.g.
26
+ * engram-web's URL in dev
27
+ * ENGRAM_AUTH_EMAIL_DOMAIN restrict sign-in to one email domain
28
+ * (default: hexis.ltd)
29
+ */
30
+ import type { Pool } from "pg";
31
+ export interface BuildAuthOptions {
32
+ /** node-postgres Pool for better-auth's Kysely adapter. */
33
+ pool: Pool;
34
+ /** Cookie/JWT signing secret. Required, >= 32 chars in prod. */
35
+ secret: string;
36
+ /** Full base URL of this server (e.g. https://api.engram.hexis.ltd). */
37
+ baseURL: string;
38
+ google: {
39
+ clientId: string;
40
+ clientSecret: string;
41
+ };
42
+ /** Cookie scope when web + api are on sibling subdomains. */
43
+ cookieDomain?: string;
44
+ /** Additional trustedOrigins for CSRF / redirect allowlist. */
45
+ trustedOrigins?: string[];
46
+ /** Restrict sign-in to a single email domain. Default: hexis.ltd. */
47
+ emailDomain?: string;
48
+ /** Use Secure cookies (HTTPS only). Defaults to NODE_ENV=production. */
49
+ secureCookies?: boolean;
50
+ }
51
+ export type EngramAuth = ReturnType<typeof buildAuth>;
52
+ /**
53
+ * Construct the better-auth instance. The returned value exposes
54
+ * - \`handler(req: Request): Response\` for Hono \`.all("/auth/*", ...)\`
55
+ * - \`api.getSession({ headers })\` for cookie → session lookup
56
+ */
57
+ export declare function buildAuth(opts: BuildAuthOptions): import("better-auth").Auth<{
58
+ database: Pool;
59
+ secret: string;
60
+ baseURL: string;
61
+ basePath: string;
62
+ trustedOrigins: string[];
63
+ user: {
64
+ modelName: "engram_auth_users";
65
+ fields: {
66
+ emailVerified: string;
67
+ createdAt: string;
68
+ updatedAt: string;
69
+ };
70
+ };
71
+ session: {
72
+ modelName: "engram_auth_sessions";
73
+ fields: {
74
+ userId: string;
75
+ expiresAt: string;
76
+ ipAddress: string;
77
+ userAgent: string;
78
+ createdAt: string;
79
+ updatedAt: string;
80
+ token: string;
81
+ };
82
+ additionalFields: {
83
+ currentWorkspaceId: {
84
+ type: "string";
85
+ required: false;
86
+ input: false;
87
+ fieldName: string;
88
+ };
89
+ };
90
+ cookieCache: {
91
+ enabled: true;
92
+ maxAge: number;
93
+ };
94
+ };
95
+ account: {
96
+ modelName: "engram_auth_accounts";
97
+ fields: {
98
+ userId: string;
99
+ accountId: string;
100
+ providerId: string;
101
+ accessToken: string;
102
+ refreshToken: string;
103
+ idToken: string;
104
+ accessTokenExpiresAt: string;
105
+ refreshTokenExpiresAt: string;
106
+ scope: string;
107
+ password: string;
108
+ createdAt: string;
109
+ updatedAt: string;
110
+ };
111
+ accountLinking: {
112
+ enabled: true;
113
+ trustedProviders: "google"[];
114
+ };
115
+ };
116
+ verification: {
117
+ modelName: "engram_auth_verifications";
118
+ fields: {
119
+ expiresAt: string;
120
+ createdAt: string;
121
+ updatedAt: string;
122
+ };
123
+ };
124
+ advanced: {
125
+ database: {
126
+ generateId: () => string;
127
+ };
128
+ cookiePrefix: string;
129
+ cookies: {
130
+ sessionToken: {
131
+ attributes: {
132
+ domain: string;
133
+ sameSite: "lax";
134
+ secure: boolean;
135
+ httpOnly: true;
136
+ path: string;
137
+ };
138
+ };
139
+ } | undefined;
140
+ useSecureCookies: boolean;
141
+ crossSubDomainCookies: {
142
+ enabled: true;
143
+ domain: string;
144
+ } | undefined;
145
+ };
146
+ socialProviders: {
147
+ google: {
148
+ clientId: string;
149
+ clientSecret: string;
150
+ };
151
+ };
152
+ databaseHooks: {
153
+ user: {
154
+ create: {
155
+ before: (user: {
156
+ id: string;
157
+ createdAt: Date;
158
+ updatedAt: Date;
159
+ email: string;
160
+ emailVerified: boolean;
161
+ name: string;
162
+ image?: string | null | undefined;
163
+ } & Record<string, unknown>) => Promise<{
164
+ data: {
165
+ id: string;
166
+ createdAt: Date;
167
+ updatedAt: Date;
168
+ email: string;
169
+ emailVerified: boolean;
170
+ name: string;
171
+ image?: string | null | undefined;
172
+ } & Record<string, unknown>;
173
+ }>;
174
+ };
175
+ };
176
+ };
177
+ plugins: [{
178
+ id: "bearer";
179
+ version: string;
180
+ hooks: {
181
+ before: {
182
+ matcher(context: import("better-auth").HookEndpointContext): boolean;
183
+ handler: (inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<{
184
+ context: {
185
+ headers: Headers;
186
+ };
187
+ } | undefined>;
188
+ }[];
189
+ after: {
190
+ matcher(context: import("better-auth").HookEndpointContext): true;
191
+ handler: (inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<void>;
192
+ }[];
193
+ };
194
+ options: import("better-auth/plugins").BearerOptions | undefined;
195
+ }];
196
+ }>;