@storyshelf/auth 0.5.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.
- package/LICENSE +21 -0
- package/README.md +43 -0
- package/dist/auth-tables-pg.d.mts +1971 -0
- package/dist/auth-tables-pg.mjs +167 -0
- package/dist/auth-tables-pg.mjs.map +1 -0
- package/dist/auth-tables.d.mts +1092 -0
- package/dist/auth-tables.mjs +122 -0
- package/dist/auth-tables.mjs.map +1 -0
- package/dist/bootstrap.d.mts +15 -0
- package/dist/bootstrap.mjs +40 -0
- package/dist/bootstrap.mjs.map +1 -0
- package/dist/config.d.mts +27 -0
- package/dist/config.mjs +204 -0
- package/dist/config.mjs.map +1 -0
- package/dist/db-bridge.d.mts +20 -0
- package/dist/db-bridge.mjs +208 -0
- package/dist/db-bridge.mjs.map +1 -0
- package/dist/db-values.mjs +41 -0
- package/dist/db-values.mjs.map +1 -0
- package/dist/db-where.mjs +85 -0
- package/dist/db-where.mjs.map +1 -0
- package/dist/engine.d.mts +208 -0
- package/dist/engine.mjs +444 -0
- package/dist/engine.mjs.map +1 -0
- package/dist/groups.d.mts +54 -0
- package/dist/groups.mjs +129 -0
- package/dist/groups.mjs.map +1 -0
- package/dist/index.d.mts +18 -0
- package/dist/index.mjs +17 -0
- package/dist/invites.d.mts +41 -0
- package/dist/invites.mjs +225 -0
- package/dist/invites.mjs.map +1 -0
- package/dist/passkeys.d.mts +21 -0
- package/dist/passkeys.mjs +27 -0
- package/dist/passkeys.mjs.map +1 -0
- package/dist/presets/enterprise.d.mts +24 -0
- package/dist/presets/enterprise.mjs +44 -0
- package/dist/presets/enterprise.mjs.map +1 -0
- package/dist/presets/index.d.mts +5 -0
- package/dist/presets/social.d.mts +34 -0
- package/dist/presets/social.mjs +60 -0
- package/dist/presets/social.mjs.map +1 -0
- package/dist/presets/sso.d.mts +72 -0
- package/dist/presets/sso.mjs +136 -0
- package/dist/presets/sso.mjs.map +1 -0
- package/dist/sessions.d.mts +23 -0
- package/dist/sessions.mjs +51 -0
- package/dist/sessions.mjs.map +1 -0
- package/package.json +82 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { applySelect, toDriverRow, toModelRow } from "./db-values.mjs";
|
|
5
|
+
import { buildCondition, buildOrderBy } from "./db-where.mjs";
|
|
6
|
+
import { ulid } from "@storyshelf/core/utils";
|
|
7
|
+
import { getTableColumns } from "drizzle-orm";
|
|
8
|
+
//#region src/db-bridge.ts
|
|
9
|
+
function tableFor(schema, model) {
|
|
10
|
+
const table = schema.tables[model];
|
|
11
|
+
if (!table) throw new Error(`Unknown auth model "${model}"`);
|
|
12
|
+
return table;
|
|
13
|
+
}
|
|
14
|
+
function datesFor(schema, model) {
|
|
15
|
+
return schema.dateFields[model] ?? [];
|
|
16
|
+
}
|
|
17
|
+
function defaultGenerateId() {
|
|
18
|
+
return ulid();
|
|
19
|
+
}
|
|
20
|
+
function whereOf(table, where) {
|
|
21
|
+
if (!where || where.length === 0) return;
|
|
22
|
+
return buildCondition(table, where);
|
|
23
|
+
}
|
|
24
|
+
async function findFirst(store, schema, model, where) {
|
|
25
|
+
const table = tableFor(schema, model);
|
|
26
|
+
const row = (await store.list(table, {
|
|
27
|
+
where: whereOf(table, where),
|
|
28
|
+
limit: 1
|
|
29
|
+
}))[0];
|
|
30
|
+
return row ? toModelRow(row, datesFor(schema, model)) : null;
|
|
31
|
+
}
|
|
32
|
+
async function updateById(store, schema, model, id, update) {
|
|
33
|
+
const table = tableFor(schema, model);
|
|
34
|
+
if (!await store.get(table, id)) return null;
|
|
35
|
+
const updated = await store.update(table, id, toDriverRow(update));
|
|
36
|
+
return toModelRow(updated, datesFor(schema, model));
|
|
37
|
+
}
|
|
38
|
+
async function opCreate(store, schema, input) {
|
|
39
|
+
const table = tableFor(schema, input.model);
|
|
40
|
+
const generate = schema.generateId ?? defaultGenerateId;
|
|
41
|
+
const data = input.data["id"] === void 0 || input.data["id"] === null ? {
|
|
42
|
+
...input.data,
|
|
43
|
+
id: generate(input.model)
|
|
44
|
+
} : input.data;
|
|
45
|
+
const inserted = await store.insert(table, toDriverRow(data));
|
|
46
|
+
return toModelRow(inserted, datesFor(schema, input.model));
|
|
47
|
+
}
|
|
48
|
+
async function opUpdate(store, schema, input) {
|
|
49
|
+
const row = await findFirst(store, schema, input.model, input.where);
|
|
50
|
+
if (!row || typeof row["id"] !== "string") return null;
|
|
51
|
+
return await updateById(store, schema, input.model, row["id"], input.update);
|
|
52
|
+
}
|
|
53
|
+
async function opUpdateMany(store, schema, input) {
|
|
54
|
+
const table = tableFor(schema, input.model);
|
|
55
|
+
const ids = (await store.list(table, { where: whereOf(table, input.where) })).map((row) => row["id"]).filter((id) => typeof id === "string");
|
|
56
|
+
await Promise.all(ids.map(async (id) => await updateById(store, schema, input.model, id, input.update)));
|
|
57
|
+
return ids.length;
|
|
58
|
+
}
|
|
59
|
+
async function opDeleteMany(store, schema, input) {
|
|
60
|
+
const table = tableFor(schema, input.model);
|
|
61
|
+
const ids = (await store.list(table, { where: whereOf(table, input.where) })).map((row) => row["id"]).filter((id) => typeof id === "string");
|
|
62
|
+
await Promise.all(ids.map(async (id) => {
|
|
63
|
+
await store.remove(table, id);
|
|
64
|
+
}));
|
|
65
|
+
return ids.length;
|
|
66
|
+
}
|
|
67
|
+
async function opConsumeOne(store, schema, input) {
|
|
68
|
+
const row = await findFirst(store, schema, input.model, input.where);
|
|
69
|
+
if (!row || typeof row["id"] !== "string") return null;
|
|
70
|
+
await store.remove(tableFor(schema, input.model), row["id"]);
|
|
71
|
+
return row;
|
|
72
|
+
}
|
|
73
|
+
async function opIncrementOne(store, schema, input) {
|
|
74
|
+
const row = await findFirst(store, schema, input.model, input.where);
|
|
75
|
+
if (!row || typeof row["id"] !== "string") return null;
|
|
76
|
+
const deltas = Object.fromEntries(Object.entries(input.increment).map(([field, delta]) => {
|
|
77
|
+
const current = row[field];
|
|
78
|
+
return [field, (typeof current === "number" ? current : 0) + delta];
|
|
79
|
+
}));
|
|
80
|
+
return await updateById(store, schema, input.model, row["id"], {
|
|
81
|
+
...deltas,
|
|
82
|
+
...input.set
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function hasColumn(table, field) {
|
|
86
|
+
return getTableColumns(table)[field] !== void 0;
|
|
87
|
+
}
|
|
88
|
+
async function joinMany(store, schema, parentModel, parentId, joinedModel, limit) {
|
|
89
|
+
const table = tableFor(schema, joinedModel);
|
|
90
|
+
const opts = { limit };
|
|
91
|
+
const where = whereOf(table, [{
|
|
92
|
+
field: `${parentModel}Id`,
|
|
93
|
+
value: parentId
|
|
94
|
+
}]);
|
|
95
|
+
if (where) opts.where = where;
|
|
96
|
+
return (await store.list(table, opts)).map((row) => toModelRow(row, datesFor(schema, joinedModel)));
|
|
97
|
+
}
|
|
98
|
+
async function joinRelation(store, schema, parentModel, parent, joinedModel, limit) {
|
|
99
|
+
if (hasColumn(tableFor(schema, joinedModel), `${parentModel}Id`)) return await joinMany(store, schema, parentModel, parent["id"], joinedModel, limit);
|
|
100
|
+
if (typeof parent[`${joinedModel}Id`] === "string") return await findFirst(store, schema, joinedModel, [{
|
|
101
|
+
field: "id",
|
|
102
|
+
value: parent[`${joinedModel}Id`]
|
|
103
|
+
}]);
|
|
104
|
+
throw new Error(`Unknown auth relation "${parentModel}.${joinedModel}"`);
|
|
105
|
+
}
|
|
106
|
+
async function attachJoins(store, schema, parentModel, parent, join) {
|
|
107
|
+
if (!join) return {};
|
|
108
|
+
const attached = {};
|
|
109
|
+
const entries = Object.entries(join);
|
|
110
|
+
await Promise.all(entries.map(async ([joinedModel, joinAttr]) => {
|
|
111
|
+
const limit = typeof joinAttr === "object" ? joinAttr.limit : void 0;
|
|
112
|
+
attached[joinedModel] = await joinRelation(store, schema, parentModel, parent, joinedModel, limit);
|
|
113
|
+
}));
|
|
114
|
+
return attached;
|
|
115
|
+
}
|
|
116
|
+
async function listRows(store, schema, input) {
|
|
117
|
+
const table = tableFor(schema, input.model);
|
|
118
|
+
const opts = {
|
|
119
|
+
limit: input.limit,
|
|
120
|
+
offset: input.offset
|
|
121
|
+
};
|
|
122
|
+
const where = whereOf(table, input.where);
|
|
123
|
+
if (where) opts.where = where;
|
|
124
|
+
const orderBy = buildOrderBy(table, input.sortBy);
|
|
125
|
+
if (orderBy) opts.orderBy = orderBy;
|
|
126
|
+
return await store.list(table, opts);
|
|
127
|
+
}
|
|
128
|
+
async function opFindMany(store, schema, input) {
|
|
129
|
+
const rows = await listRows(store, schema, input);
|
|
130
|
+
return await Promise.all(rows.map(async (row) => {
|
|
131
|
+
const withJoins = input.join ? {
|
|
132
|
+
...row,
|
|
133
|
+
...await attachJoins(store, schema, input.model, row, input.join)
|
|
134
|
+
} : row;
|
|
135
|
+
return applySelect(toModelRow(withJoins, datesFor(schema, input.model)), input.select);
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
async function opFindOne(store, schema, input) {
|
|
139
|
+
const row = await findFirst(store, schema, input.model, input.where);
|
|
140
|
+
if (!row) return null;
|
|
141
|
+
const withJoins = input.join ? {
|
|
142
|
+
...row,
|
|
143
|
+
...await attachJoins(store, schema, input.model, row, input.join)
|
|
144
|
+
} : row;
|
|
145
|
+
return applySelect(withJoins, input.select);
|
|
146
|
+
}
|
|
147
|
+
/** Read operations bound to one store. */
|
|
148
|
+
function bindQueryOps(store, schema) {
|
|
149
|
+
return {
|
|
150
|
+
findOne: async (input) => await opFindOne(store, schema, input),
|
|
151
|
+
findMany: async (input) => await opFindMany(store, schema, input),
|
|
152
|
+
count: async (input) => {
|
|
153
|
+
const table = tableFor(schema, input.model);
|
|
154
|
+
return await store.count(table, whereOf(table, input.where));
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** Write operations bound to one store (transaction-aware where marked). */
|
|
159
|
+
function bindMutationOps(store, schema, db) {
|
|
160
|
+
return {
|
|
161
|
+
create: async (input) => await opCreate(store, schema, input),
|
|
162
|
+
update: async (input) => await opUpdate(store, schema, input),
|
|
163
|
+
updateMany: async (input) => await opUpdateMany(store, schema, input),
|
|
164
|
+
delete: async (input) => {
|
|
165
|
+
const row = await findFirst(store, schema, input.model, input.where);
|
|
166
|
+
if (row && typeof row["id"] === "string") await store.remove(tableFor(schema, input.model), row["id"]);
|
|
167
|
+
},
|
|
168
|
+
deleteMany: async (input) => await opDeleteMany(store, schema, input),
|
|
169
|
+
consumeOne: async (input) => {
|
|
170
|
+
if (store === db && db.transact) return await db.transact(async (tx) => await opConsumeOne(tx, schema, input));
|
|
171
|
+
return await opConsumeOne(store, schema, input);
|
|
172
|
+
},
|
|
173
|
+
incrementOne: async (input) => {
|
|
174
|
+
if (store === db && db.transact) return await db.transact(async (tx) => await opIncrementOne(tx, schema, input));
|
|
175
|
+
return await opIncrementOne(store, schema, input);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/** Build the Better Auth database adapter over a shelf database. */
|
|
180
|
+
function createShelfDbBridge(db, schema) {
|
|
181
|
+
return (_options) => {
|
|
182
|
+
const ops = {
|
|
183
|
+
...bindQueryOps(db, schema),
|
|
184
|
+
...bindMutationOps(db, schema, db)
|
|
185
|
+
};
|
|
186
|
+
return {
|
|
187
|
+
...ops,
|
|
188
|
+
transaction: async (callback) => {
|
|
189
|
+
if (db.transact) return await db.transact(async (tx) => {
|
|
190
|
+
return await callback({
|
|
191
|
+
...bindQueryOps(tx, schema),
|
|
192
|
+
...bindMutationOps(tx, schema, db)
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
return await callback(ops);
|
|
196
|
+
},
|
|
197
|
+
options: {
|
|
198
|
+
adapterName: "storyshelf",
|
|
199
|
+
supportsDates: false,
|
|
200
|
+
supportsJSON: false
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
//#endregion
|
|
206
|
+
export { createShelfDbBridge };
|
|
207
|
+
|
|
208
|
+
//# sourceMappingURL=db-bridge.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-bridge.mjs","names":[],"sources":["../src/db-bridge.ts"],"sourcesContent":["import type { DatabaseAdapter, ListOptions, TxStore } from \"@storyshelf/core/adapter/database\";\nimport { ulid } from \"@storyshelf/core/utils\";\n/**\n * Opaque Better Auth database bridge over the shelf DatabaseAdapter.\n *\n * Better Auth talks to `DBAdapterInstance` (create/findOne/findMany/count/\n * update/updateMany/delete/deleteMany/consumeOne/incrementOne/transaction);\n * this maps each call onto {@link DatabaseAdapter} CRUD over an explicit\n * model→table registry. No dialect knowledge: sqlite, Turso, and Postgres\n * drivers all work untouched, present and future.\n */\nimport type { DBAdapterInstance, DBTransactionAdapter, Where } from \"better-auth\";\nimport type { SQL, Table } from \"drizzle-orm\";\nimport { getTableColumns } from \"drizzle-orm\";\nimport { applySelect, toDriverRow, toModelRow } from \"./db-values.ts\";\nimport { buildCondition, buildOrderBy } from \"./db-where.ts\";\n\n/** Model→table registry plus per-model date fields. */\nexport interface AuthBridgeSchema {\n /** Drizzle table handles keyed by Better Auth model name. */\n tables: Record<string, Table>;\n /** Date-valued fields per model (ISO at rest, Date in flight). */\n dateFields: Record<string, readonly string[]>;\n /** Id generator for creates missing an id (defaults to ULID). */\n generateId?: (model: string) => string;\n}\n\n/** CRUD surface the bridge is built over (adapter or transaction store). */\ntype CrudStore = Pick<TxStore, \"insert\" | \"update\" | \"get\" | \"remove\" | \"list\" | \"count\">;\n\ninterface OpInput {\n model: string;\n}\n\nfunction tableFor(schema: AuthBridgeSchema, model: string): Table {\n const table = schema.tables[model];\n if (!table) {\n throw new Error(`Unknown auth model \"${model}\"`);\n }\n return table;\n}\n\nfunction datesFor(schema: AuthBridgeSchema, model: string): readonly string[] {\n return schema.dateFields[model] ?? [];\n}\n\nfunction defaultGenerateId(): string {\n return ulid();\n}\n\nfunction whereOf(table: Table, where: readonly Where[] | undefined): SQL | undefined {\n if (!where || where.length === 0) {\n return undefined;\n }\n return buildCondition(table, where);\n}\n\nasync function findFirst(\n store: CrudStore,\n schema: AuthBridgeSchema,\n model: string,\n where: readonly Where[] | undefined,\n): Promise<Record<string, unknown> | null> {\n const table = tableFor(schema, model);\n const rows = (await store.list(table, { where: whereOf(table, where), limit: 1 })) as Record<\n string,\n unknown\n >[];\n const row = rows[0];\n return row ? toModelRow(row, datesFor(schema, model)) : null;\n}\n\nasync function updateById(\n store: CrudStore,\n schema: AuthBridgeSchema,\n model: string,\n id: string,\n update: Record<string, unknown>,\n): Promise<Record<string, unknown> | null> {\n const table = tableFor(schema, model);\n const existing = await store.get(table, id);\n if (!existing) {\n return null;\n }\n const updated = await store.update(table, id, toDriverRow(update));\n return toModelRow(updated, datesFor(schema, model));\n}\n\nasync function opCreate(\n store: CrudStore,\n schema: AuthBridgeSchema,\n input: OpInput & { data: Record<string, unknown> },\n): Promise<Record<string, unknown>> {\n const table = tableFor(schema, input.model);\n const generate = schema.generateId ?? defaultGenerateId;\n const data =\n input.data[\"id\"] === undefined || input.data[\"id\"] === null\n ? { ...input.data, id: generate(input.model) }\n : input.data;\n const inserted = await store.insert(table, toDriverRow(data));\n return toModelRow(inserted, datesFor(schema, input.model));\n}\n\nasync function opUpdate(\n store: CrudStore,\n schema: AuthBridgeSchema,\n input: OpInput & { where: Where[]; update: Record<string, unknown> },\n): Promise<Record<string, unknown> | null> {\n const row = await findFirst(store, schema, input.model, input.where);\n if (!row || typeof row[\"id\"] !== \"string\") {\n return null;\n }\n return await updateById(store, schema, input.model, row[\"id\"], input.update);\n}\n\nasync function opUpdateMany(\n store: CrudStore,\n schema: AuthBridgeSchema,\n input: OpInput & { where: Where[]; update: Record<string, unknown> },\n): Promise<number> {\n const table = tableFor(schema, input.model);\n const rows = (await store.list(table, {\n where: whereOf(table, input.where),\n })) as Record<string, unknown>[];\n const ids = rows.map((row) => row[\"id\"]).filter((id): id is string => typeof id === \"string\");\n await Promise.all(\n ids.map(async (id) => await updateById(store, schema, input.model, id, input.update)),\n );\n return ids.length;\n}\n\nasync function opDeleteMany(\n store: CrudStore,\n schema: AuthBridgeSchema,\n input: OpInput & { where: Where[] },\n): Promise<number> {\n const table = tableFor(schema, input.model);\n const rows = (await store.list(table, {\n where: whereOf(table, input.where),\n })) as Record<string, unknown>[];\n const ids = rows.map((row) => row[\"id\"]).filter((id): id is string => typeof id === \"string\");\n await Promise.all(\n ids.map(async (id) => {\n await store.remove(table, id);\n }),\n );\n return ids.length;\n}\n\nasync function opConsumeOne(\n store: CrudStore,\n schema: AuthBridgeSchema,\n input: OpInput & { where: Where[] },\n): Promise<Record<string, unknown> | null> {\n // Runs directly on the given store: the top-level binding wraps this in\n // `transact` for atomicity, while transaction-scoped bindings pass the\n // ambient `tx` so Better Auth's own transaction wrapper never nests a\n // second `begin` on single-connection drivers (sqlite).\n const row = await findFirst(store, schema, input.model, input.where);\n if (!row || typeof row[\"id\"] !== \"string\") {\n return null;\n }\n await store.remove(tableFor(schema, input.model), row[\"id\"]);\n return row;\n}\n\nasync function opIncrementOne(\n store: CrudStore,\n schema: AuthBridgeSchema,\n input: OpInput & {\n where: Where[];\n increment: Record<string, number>;\n set?: Record<string, unknown>;\n },\n): Promise<Record<string, unknown> | null> {\n const row = await findFirst(store, schema, input.model, input.where);\n if (!row || typeof row[\"id\"] !== \"string\") {\n return null;\n }\n const deltas = Object.fromEntries(\n Object.entries(input.increment).map(([field, delta]) => {\n const current = row[field];\n return [field, (typeof current === \"number\" ? current : 0) + delta];\n }),\n );\n return await updateById(store, schema, input.model, row[\"id\"], {\n ...deltas,\n ...input.set,\n });\n}\n\nfunction hasColumn(table: Table, field: string): boolean {\n return getTableColumns(table)[field] !== undefined;\n}\n\nasync function joinMany(\n store: CrudStore,\n schema: AuthBridgeSchema,\n parentModel: string,\n parentId: unknown,\n joinedModel: string,\n limit: number | undefined,\n): Promise<Record<string, unknown>[]> {\n const table = tableFor(schema, joinedModel);\n const opts: ListOptions = { limit };\n const where = whereOf(table, [{ field: `${parentModel}Id`, value: parentId as never }]);\n if (where) {\n opts.where = where;\n }\n const rows = (await store.list(table, opts)) as Record<string, unknown>[];\n return rows.map((row) => toModelRow(row, datesFor(schema, joinedModel)));\n}\n\nasync function joinRelation(\n store: CrudStore,\n schema: AuthBridgeSchema,\n parentModel: string,\n parent: Record<string, unknown>,\n joinedModel: string,\n limit: number | undefined,\n): Promise<Record<string, unknown>[] | Record<string, unknown> | null> {\n const table = tableFor(schema, joinedModel);\n if (hasColumn(table, `${parentModel}Id`)) {\n return await joinMany(store, schema, parentModel, parent[\"id\"], joinedModel, limit);\n }\n if (typeof parent[`${joinedModel}Id`] === \"string\") {\n return await findFirst(store, schema, joinedModel, [\n { field: \"id\", value: parent[`${joinedModel}Id`] as never },\n ]);\n }\n throw new Error(`Unknown auth relation \"${parentModel}.${joinedModel}\"`);\n}\n\nasync function attachJoins(\n store: CrudStore,\n schema: AuthBridgeSchema,\n parentModel: string,\n parent: Record<string, unknown>,\n join: Record<string, boolean | { limit?: number }> | undefined,\n): Promise<Record<string, unknown>> {\n if (!join) {\n return {};\n }\n const attached: Record<string, unknown> = {};\n const entries = Object.entries(join);\n await Promise.all(\n entries.map(async ([joinedModel, joinAttr]) => {\n const limit = typeof joinAttr === \"object\" ? joinAttr.limit : undefined;\n attached[joinedModel] = await joinRelation(\n store,\n schema,\n parentModel,\n parent,\n joinedModel,\n limit,\n );\n }),\n );\n return attached;\n}\n\ntype FindManyInput = OpInput & {\n where?: Where[];\n limit?: number;\n offset?: number;\n sortBy?: { field: string; direction: \"asc\" | \"desc\" };\n select?: string[];\n join?: Record<string, boolean | { limit?: number }>;\n};\n\nasync function listRows(\n store: CrudStore,\n schema: AuthBridgeSchema,\n input: FindManyInput,\n): Promise<Record<string, unknown>[]> {\n const table = tableFor(schema, input.model);\n const opts: ListOptions = { limit: input.limit, offset: input.offset };\n const where = whereOf(table, input.where);\n if (where) {\n opts.where = where;\n }\n const orderBy = buildOrderBy(table, input.sortBy);\n if (orderBy) {\n opts.orderBy = orderBy;\n }\n const listed: Record<string, unknown>[] = await store.list(table, opts);\n return listed;\n}\n\nasync function opFindMany(\n store: CrudStore,\n schema: AuthBridgeSchema,\n input: FindManyInput,\n): Promise<Record<string, unknown>[]> {\n const rows = await listRows(store, schema, input);\n return await Promise.all(\n rows.map(async (row) => {\n const withJoins = input.join\n ? { ...row, ...(await attachJoins(store, schema, input.model, row, input.join)) }\n : row;\n return applySelect(toModelRow(withJoins, datesFor(schema, input.model)), input.select);\n }),\n );\n}\n\n/** One bound operation set (adapter top-level or transaction store). */\ninterface JoinSpec {\n join?: Record<string, boolean | { limit?: number }>;\n}\n\ninterface BoundOps {\n create(input: OpInput & { data: Record<string, unknown> }): Promise<Record<string, unknown>>;\n findOne(\n input: OpInput & { where: Where[]; select?: string[] } & JoinSpec,\n ): Promise<Record<string, unknown> | null>;\n findMany(\n input: OpInput & {\n where?: Where[];\n limit?: number;\n offset?: number;\n sortBy?: { field: string; direction: \"asc\" | \"desc\" };\n select?: string[];\n } & JoinSpec,\n ): Promise<Record<string, unknown>[]>;\n count(input: OpInput & { where?: Where[] }): Promise<number>;\n update(\n input: OpInput & { where: Where[]; update: Record<string, unknown> },\n ): Promise<Record<string, unknown> | null>;\n updateMany(input: OpInput & { where: Where[]; update: Record<string, unknown> }): Promise<number>;\n delete(input: OpInput & { where: Where[] }): Promise<void>;\n deleteMany(input: OpInput & { where: Where[] }): Promise<number>;\n consumeOne(input: OpInput & { where: Where[] }): Promise<Record<string, unknown> | null>;\n incrementOne(\n input: OpInput & {\n where: Where[];\n increment: Record<string, number>;\n set?: Record<string, unknown>;\n },\n ): Promise<Record<string, unknown> | null>;\n}\n\nasync function opFindOne(\n store: CrudStore,\n schema: AuthBridgeSchema,\n input: OpInput & { where: Where[]; select?: string[] } & JoinSpec,\n): Promise<Record<string, unknown> | null> {\n const row = await findFirst(store, schema, input.model, input.where);\n if (!row) {\n return null;\n }\n const withJoins = input.join\n ? { ...row, ...(await attachJoins(store, schema, input.model, row, input.join)) }\n : row;\n return applySelect(withJoins, input.select);\n}\n\n/** Read operations bound to one store. */\nfunction bindQueryOps(\n store: CrudStore,\n schema: AuthBridgeSchema,\n): Pick<BoundOps, \"findOne\" | \"findMany\" | \"count\"> {\n return {\n findOne: async (\n input: OpInput & { where: Where[]; select?: string[] } & JoinSpec,\n ): Promise<Record<string, unknown> | null> => await opFindOne(store, schema, input),\n findMany: async (\n input: OpInput & {\n where?: Where[];\n limit?: number;\n offset?: number;\n sortBy?: { field: string; direction: \"asc\" | \"desc\" };\n select?: string[];\n } & JoinSpec,\n ): Promise<Record<string, unknown>[]> => await opFindMany(store, schema, input),\n count: async (input: OpInput & { where?: Where[] }): Promise<number> => {\n const table = tableFor(schema, input.model);\n return await store.count(table, whereOf(table, input.where));\n },\n };\n}\n\n/** Write operations bound to one store (transaction-aware where marked). */\nfunction bindMutationOps(\n store: CrudStore,\n schema: AuthBridgeSchema,\n db: DatabaseAdapter,\n): Omit<BoundOps, \"findOne\" | \"findMany\" | \"count\"> {\n return {\n create: async (\n input: OpInput & { data: Record<string, unknown> },\n ): Promise<Record<string, unknown>> => await opCreate(store, schema, input),\n update: async (\n input: OpInput & { where: Where[]; update: Record<string, unknown> },\n ): Promise<Record<string, unknown> | null> => await opUpdate(store, schema, input),\n updateMany: async (\n input: OpInput & { where: Where[]; update: Record<string, unknown> },\n ): Promise<number> => await opUpdateMany(store, schema, input),\n delete: async (input: OpInput & { where: Where[] }): Promise<void> => {\n const row = await findFirst(store, schema, input.model, input.where);\n if (row && typeof row[\"id\"] === \"string\") {\n await store.remove(tableFor(schema, input.model), row[\"id\"]);\n }\n },\n deleteMany: async (input: OpInput & { where: Where[] }): Promise<number> =>\n await opDeleteMany(store, schema, input),\n consumeOne: async (\n input: OpInput & { where: Where[] },\n ): Promise<Record<string, unknown> | null> => {\n // Atomic at the top level when the driver offers transact; inside a\n // Better Auth transaction `store` is the ambient `tx`, which must run\n // directly (a second `begin` fails on single-connection sqlite).\n if (store === db && db.transact) {\n return await db.transact(async (tx) => await opConsumeOne(tx, schema, input));\n }\n return await opConsumeOne(store, schema, input);\n },\n incrementOne: async (\n input: OpInput & {\n where: Where[];\n increment: Record<string, number>;\n set?: Record<string, unknown>;\n },\n ): Promise<Record<string, unknown> | null> => {\n if (store === db && db.transact) {\n return await db.transact(async (tx) => await opIncrementOne(tx, schema, input));\n }\n return await opIncrementOne(store, schema, input);\n },\n };\n}\n\n/** Build the Better Auth database adapter over a shelf database. */\nexport function createShelfDbBridge(\n db: DatabaseAdapter,\n schema: AuthBridgeSchema,\n): DBAdapterInstance {\n return (_options: unknown) => {\n const ops: BoundOps = { ...bindQueryOps(db, schema), ...bindMutationOps(db, schema, db) };\n return {\n ...ops,\n transaction: async <R>(callback: (trx: DBTransactionAdapter) => Promise<R>): Promise<R> => {\n if (db.transact) {\n return await db.transact(async (tx) => {\n const txOps: BoundOps = {\n ...bindQueryOps(tx, schema),\n ...bindMutationOps(tx, schema, db),\n };\n return await callback(txOps as never);\n });\n }\n return await callback(ops as never);\n },\n options: { adapterName: \"storyshelf\", supportsDates: false, supportsJSON: false },\n } as never;\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
//#region src/db-values.ts
|
|
5
|
+
/**
|
|
6
|
+
* Value conversion between Better Auth models and driver rows.
|
|
7
|
+
*
|
|
8
|
+
* Better Auth speaks Dates; our TEXT columns store ISO strings. All
|
|
9
|
+
* conversion is explicit per-field (declared date fields) — no heuristics.
|
|
10
|
+
*/
|
|
11
|
+
/** Convert one inbound value to its driver representation. */
|
|
12
|
+
function toDriverValue(value) {
|
|
13
|
+
if (value instanceof Date) return value.toISOString();
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
/** Drop undefined keys (matches the repo's withoutUndefined convention). */
|
|
17
|
+
function stripUndefined(values) {
|
|
18
|
+
return Object.fromEntries(Object.entries(values).filter((pair) => pair[1] !== void 0));
|
|
19
|
+
}
|
|
20
|
+
/** Convert a model row to its driver representation (Dates to ISO). */
|
|
21
|
+
function toDriverRow(values) {
|
|
22
|
+
return stripUndefined(Object.fromEntries(Object.entries(values).map(([field, value]) => [field, toDriverValue(value)])));
|
|
23
|
+
}
|
|
24
|
+
/** Revive ISO strings back to Dates for declared date fields. */
|
|
25
|
+
function toModelRow(row, dateFields) {
|
|
26
|
+
const out = { ...row };
|
|
27
|
+
for (const field of dateFields) {
|
|
28
|
+
const value = out[field];
|
|
29
|
+
if (typeof value === "string") out[field] = new Date(value);
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
/** Project a row down to the requested model fields, if any. */
|
|
34
|
+
function applySelect(row, select) {
|
|
35
|
+
if (!select || select.length === 0) return row;
|
|
36
|
+
return Object.fromEntries(select.map((field) => [field, row[field]]));
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { applySelect, stripUndefined, toDriverRow, toDriverValue, toModelRow };
|
|
40
|
+
|
|
41
|
+
//# sourceMappingURL=db-values.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-values.mjs","names":[],"sources":["../src/db-values.ts"],"sourcesContent":["/**\n * Value conversion between Better Auth models and driver rows.\n *\n * Better Auth speaks Dates; our TEXT columns store ISO strings. All\n * conversion is explicit per-field (declared date fields) — no heuristics.\n */\n\n/** Convert one inbound value to its driver representation. */\nexport function toDriverValue(value: unknown): unknown {\n if (value instanceof Date) {\n return value.toISOString();\n }\n return value;\n}\n\n/** Drop undefined keys (matches the repo's withoutUndefined convention). */\nexport function stripUndefined(values: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(Object.entries(values).filter((pair) => pair[1] !== undefined));\n}\n\n/** Convert a model row to its driver representation (Dates to ISO). */\nexport function toDriverRow(values: Record<string, unknown>): Record<string, unknown> {\n return stripUndefined(\n Object.fromEntries(\n Object.entries(values).map(([field, value]) => [field, toDriverValue(value)]),\n ),\n );\n}\n\n/** Revive ISO strings back to Dates for declared date fields. */\nexport function toModelRow(\n row: Record<string, unknown>,\n dateFields: readonly string[],\n): Record<string, unknown> {\n const out = { ...row };\n for (const field of dateFields) {\n const value = out[field];\n if (typeof value === \"string\") {\n out[field] = new Date(value);\n }\n }\n return out;\n}\n\n/** Project a row down to the requested model fields, if any. */\nexport function applySelect(\n row: Record<string, unknown>,\n select: readonly string[] | undefined,\n): Record<string, unknown> {\n if (!select || select.length === 0) {\n return row;\n }\n return Object.fromEntries(select.map((field) => [field, row[field]]));\n}\n"],"mappings":";;;;;;;;;;;AAQA,SAAgB,cAAc,OAAyB;CACrD,IAAI,iBAAiB,MACnB,OAAO,MAAM,YAAY;CAE3B,OAAO;AACT;;AAGA,SAAgB,eAAe,QAA0D;CACvF,OAAO,OAAO,YAAY,OAAO,QAAQ,MAAM,CAAC,CAAC,QAAQ,SAAS,KAAK,OAAO,KAAA,CAAS,CAAC;AAC1F;;AAGA,SAAgB,YAAY,QAA0D;CACpF,OAAO,eACL,OAAO,YACL,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,OAAO,cAAc,KAAK,CAAC,CAAC,CAC9E,CACF;AACF;;AAGA,SAAgB,WACd,KACA,YACyB;CACzB,MAAM,MAAM,EAAE,GAAG,IAAI;CACrB,KAAK,MAAM,SAAS,YAAY;EAC9B,MAAM,QAAQ,IAAI;EAClB,IAAI,OAAO,UAAU,UACnB,IAAI,SAAS,IAAI,KAAK,KAAK;CAE/B;CACA,OAAO;AACT;;AAGA,SAAgB,YACd,KACA,QACyB;CACzB,IAAI,CAAC,UAAU,OAAO,WAAW,GAC/B,OAAO;CAET,OAAO,OAAO,YAAY,OAAO,KAAK,UAAU,CAAC,OAAO,IAAI,MAAM,CAAC,CAAC;AACtE"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { and, asc, desc, eq, getTableColumns, gt, gte, inArray, lt, lte, ne, not, or, sql } from "drizzle-orm";
|
|
5
|
+
//#region src/db-where.ts
|
|
6
|
+
function columnOf(table, field) {
|
|
7
|
+
const column = getTableColumns(table)[field];
|
|
8
|
+
if (!column) throw new Error(`Unknown auth field "${field}"`);
|
|
9
|
+
return column;
|
|
10
|
+
}
|
|
11
|
+
function lowered(column) {
|
|
12
|
+
return sql`lower(${column})`;
|
|
13
|
+
}
|
|
14
|
+
/** Escape LIKE metacharacters so user input matches literally. */
|
|
15
|
+
function escapeLike(value) {
|
|
16
|
+
return value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
|
|
17
|
+
}
|
|
18
|
+
function patternValue(operator, value) {
|
|
19
|
+
const literal = escapeLike(value);
|
|
20
|
+
if (operator === "contains") return `%${literal}%`;
|
|
21
|
+
if (operator === "starts_with") return `${literal}%`;
|
|
22
|
+
return `%${literal}`;
|
|
23
|
+
}
|
|
24
|
+
/** LIKE with an explicit escape (sqlite has no default escape character). */
|
|
25
|
+
function likeEscaped(target, pattern) {
|
|
26
|
+
return sql`${target} LIKE ${pattern} ESCAPE '\\'`;
|
|
27
|
+
}
|
|
28
|
+
function singleCondition(table, item) {
|
|
29
|
+
const column = columnOf(table, item.field);
|
|
30
|
+
const operator = item.operator ?? "eq";
|
|
31
|
+
const raw = typeof item.value === "string" && item.mode === "insensitive" ? item.value.toLowerCase() : item.value;
|
|
32
|
+
const target = typeof item.value === "string" && item.mode === "insensitive" ? lowered(column) : column;
|
|
33
|
+
const value = toDriverScalar(raw);
|
|
34
|
+
switch (operator) {
|
|
35
|
+
case "eq": return eq(target, value);
|
|
36
|
+
case "ne": return ne(target, value);
|
|
37
|
+
case "lt": return lt(target, value);
|
|
38
|
+
case "lte": return lte(target, value);
|
|
39
|
+
case "gt": return gt(target, value);
|
|
40
|
+
case "gte": return gte(target, value);
|
|
41
|
+
case "in": return inArray(target, toArray(value));
|
|
42
|
+
case "not_in": return not(inArray(target, toArray(value)));
|
|
43
|
+
case "contains":
|
|
44
|
+
case "starts_with":
|
|
45
|
+
case "ends_with": return likeEscaped(target, patternValue(operator, String(raw ?? "")));
|
|
46
|
+
default: throw new Error(`Unsupported auth where operator "${String(operator)}"`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function toArray(value) {
|
|
50
|
+
if (!Array.isArray(value)) throw new TypeError("Auth where `in` operator needs an array value");
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
function toDriverScalar(value) {
|
|
54
|
+
if (value instanceof Date) return value.toISOString();
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
/** Split items into AND-groups at OR connectors (SQL precedence). */
|
|
58
|
+
function splitOrGroups(table, where) {
|
|
59
|
+
const groups = [];
|
|
60
|
+
let current = [];
|
|
61
|
+
for (const [index, item] of where.entries()) {
|
|
62
|
+
if (index > 0 && item.connector === "OR") {
|
|
63
|
+
groups.push(current);
|
|
64
|
+
current = [];
|
|
65
|
+
}
|
|
66
|
+
current.push(singleCondition(table, item));
|
|
67
|
+
}
|
|
68
|
+
groups.push(current);
|
|
69
|
+
return groups;
|
|
70
|
+
}
|
|
71
|
+
/** Combine Where[] items with their connectors into one condition. */
|
|
72
|
+
function buildCondition(table, where) {
|
|
73
|
+
if (where.length === 0) return;
|
|
74
|
+
return or(...splitOrGroups(table, where).map((group) => and(...group)));
|
|
75
|
+
}
|
|
76
|
+
/** Build an order-by clause from a sort descriptor. */
|
|
77
|
+
function buildOrderBy(table, sortBy) {
|
|
78
|
+
if (!sortBy) return;
|
|
79
|
+
const column = columnOf(table, sortBy.field);
|
|
80
|
+
return sortBy.direction === "desc" ? desc(column) : asc(column);
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { buildCondition, buildOrderBy };
|
|
84
|
+
|
|
85
|
+
//# sourceMappingURL=db-where.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-where.mjs","names":[],"sources":["../src/db-where.ts"],"sourcesContent":["/**\n * Better Auth Where[] to drizzle conditions.\n *\n * Operators covered: eq, ne, lt, lte, gt, gte, in, not_in, contains,\n * starts_with, ends_with. Connectors AND/OR (default AND). Case-insensitive\n * mode lowers both sides (portable across sqlite and Postgres). Unknown\n * fields fail fast — silent ignores would corrupt auth lookups.\n */\nimport type { Where } from \"better-auth\";\nimport type { SQL, Table } from \"drizzle-orm\";\nimport {\n and,\n asc,\n desc,\n eq,\n getTableColumns,\n gt,\n gte,\n inArray,\n lt,\n lte,\n ne,\n not,\n or,\n sql,\n} from \"drizzle-orm\";\n\nfunction columnOf(table: Table, field: string): SQL {\n const column = getTableColumns(table)[field] as SQL | undefined;\n if (!column) {\n throw new Error(`Unknown auth field \"${field}\"`);\n }\n return column;\n}\n\nfunction lowered(column: SQL): SQL {\n return sql`lower(${column})`;\n}\n\n/** Escape LIKE metacharacters so user input matches literally. */\nfunction escapeLike(value: string): string {\n return value.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll(\"%\", \"\\\\%\").replaceAll(\"_\", \"\\\\_\");\n}\n\nfunction patternValue(operator: string, value: string): string {\n const literal = escapeLike(value);\n if (operator === \"contains\") {\n return `%${literal}%`;\n }\n if (operator === \"starts_with\") {\n return `${literal}%`;\n }\n return `%${literal}`;\n}\n\n/** LIKE with an explicit escape (sqlite has no default escape character). */\nfunction likeEscaped(target: SQL, pattern: string): SQL {\n return sql`${target} LIKE ${pattern} ESCAPE '\\\\'`;\n}\n\nfunction singleCondition(table: Table, item: Where): SQL {\n const column = columnOf(table, item.field);\n const operator = item.operator ?? \"eq\";\n const raw =\n typeof item.value === \"string\" && item.mode === \"insensitive\"\n ? item.value.toLowerCase()\n : item.value;\n const target =\n typeof item.value === \"string\" && item.mode === \"insensitive\" ? lowered(column) : column;\n const value = toDriverScalar(raw);\n switch (operator) {\n case \"eq\":\n return eq(target, value);\n case \"ne\":\n return ne(target, value);\n case \"lt\":\n return lt(target, value as string | number);\n case \"lte\":\n return lte(target, value as string | number);\n case \"gt\":\n return gt(target, value as string | number);\n case \"gte\":\n return gte(target, value as string | number);\n case \"in\":\n return inArray(target, toArray(value));\n case \"not_in\":\n return not(inArray(target, toArray(value)));\n case \"contains\":\n case \"starts_with\":\n case \"ends_with\":\n return likeEscaped(target, patternValue(operator, String(raw ?? \"\")));\n default:\n throw new Error(`Unsupported auth where operator \"${String(operator)}\"`);\n }\n}\n\nfunction toArray(value: unknown): (string | number)[] {\n if (!Array.isArray(value)) {\n throw new TypeError(\"Auth where `in` operator needs an array value\");\n }\n return value as (string | number)[];\n}\n\nfunction toDriverScalar(value: Where[\"value\"]): unknown {\n if (value instanceof Date) {\n return value.toISOString();\n }\n return value;\n}\n\n/** Split items into AND-groups at OR connectors (SQL precedence). */\nfunction splitOrGroups(table: Table, where: readonly Where[]): SQL[][] {\n const groups: SQL[][] = [];\n let current: SQL[] = [];\n for (const [index, item] of where.entries()) {\n if (index > 0 && item.connector === \"OR\") {\n groups.push(current);\n current = [];\n }\n current.push(singleCondition(table, item));\n }\n groups.push(current);\n return groups;\n}\n\n/** Combine Where[] items with their connectors into one condition. */\nexport function buildCondition(table: Table, where: readonly Where[]): SQL | undefined {\n if (where.length === 0) {\n return undefined;\n }\n return or(...splitOrGroups(table, where).map((group) => and(...group)));\n}\n\n/** Build an order-by clause from a sort descriptor. */\nexport function buildOrderBy(\n table: Table,\n sortBy: { field: string; direction: \"asc\" | \"desc\" } | undefined,\n): SQL | undefined {\n if (!sortBy) {\n return undefined;\n }\n const column = columnOf(table, sortBy.field);\n return sortBy.direction === \"desc\" ? desc(column) : asc(column);\n}\n"],"mappings":";;;;;AA2BA,SAAS,SAAS,OAAc,OAAoB;CAClD,MAAM,SAAS,gBAAgB,KAAK,CAAC,CAAC;CACtC,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,uBAAuB,MAAM,EAAE;CAEjD,OAAO;AACT;AAEA,SAAS,QAAQ,QAAkB;CACjC,OAAO,GAAG,SAAS,OAAO;AAC5B;;AAGA,SAAS,WAAW,OAAuB;CACzC,OAAO,MAAM,WAAW,MAAM,MAAM,CAAC,CAAC,WAAW,KAAK,KAAK,CAAC,CAAC,WAAW,KAAK,KAAK;AACpF;AAEA,SAAS,aAAa,UAAkB,OAAuB;CAC7D,MAAM,UAAU,WAAW,KAAK;CAChC,IAAI,aAAa,YACf,OAAO,IAAI,QAAQ;CAErB,IAAI,aAAa,eACf,OAAO,GAAG,QAAQ;CAEpB,OAAO,IAAI;AACb;;AAGA,SAAS,YAAY,QAAa,SAAsB;CACtD,OAAO,GAAG,GAAG,OAAO,QAAQ,QAAQ;AACtC;AAEA,SAAS,gBAAgB,OAAc,MAAkB;CACvD,MAAM,SAAS,SAAS,OAAO,KAAK,KAAK;CACzC,MAAM,WAAW,KAAK,YAAY;CAClC,MAAM,MACJ,OAAO,KAAK,UAAU,YAAY,KAAK,SAAS,gBAC5C,KAAK,MAAM,YAAY,IACvB,KAAK;CACX,MAAM,SACJ,OAAO,KAAK,UAAU,YAAY,KAAK,SAAS,gBAAgB,QAAQ,MAAM,IAAI;CACpF,MAAM,QAAQ,eAAe,GAAG;CAChC,QAAQ,UAAR;EACE,KAAK,MACH,OAAO,GAAG,QAAQ,KAAK;EACzB,KAAK,MACH,OAAO,GAAG,QAAQ,KAAK;EACzB,KAAK,MACH,OAAO,GAAG,QAAQ,KAAwB;EAC5C,KAAK,OACH,OAAO,IAAI,QAAQ,KAAwB;EAC7C,KAAK,MACH,OAAO,GAAG,QAAQ,KAAwB;EAC5C,KAAK,OACH,OAAO,IAAI,QAAQ,KAAwB;EAC7C,KAAK,MACH,OAAO,QAAQ,QAAQ,QAAQ,KAAK,CAAC;EACvC,KAAK,UACH,OAAO,IAAI,QAAQ,QAAQ,QAAQ,KAAK,CAAC,CAAC;EAC5C,KAAK;EACL,KAAK;EACL,KAAK,aACH,OAAO,YAAY,QAAQ,aAAa,UAAU,OAAO,OAAO,EAAE,CAAC,CAAC;EACtE,SACE,MAAM,IAAI,MAAM,oCAAoC,OAAO,QAAQ,EAAE,EAAE;CAC3E;AACF;AAEA,SAAS,QAAQ,OAAqC;CACpD,IAAI,CAAC,MAAM,QAAQ,KAAK,GACtB,MAAM,IAAI,UAAU,+CAA+C;CAErE,OAAO;AACT;AAEA,SAAS,eAAe,OAAgC;CACtD,IAAI,iBAAiB,MACnB,OAAO,MAAM,YAAY;CAE3B,OAAO;AACT;;AAGA,SAAS,cAAc,OAAc,OAAkC;CACrE,MAAM,SAAkB,CAAC;CACzB,IAAI,UAAiB,CAAC;CACtB,KAAK,MAAM,CAAC,OAAO,SAAS,MAAM,QAAQ,GAAG;EAC3C,IAAI,QAAQ,KAAK,KAAK,cAAc,MAAM;GACxC,OAAO,KAAK,OAAO;GACnB,UAAU,CAAC;EACb;EACA,QAAQ,KAAK,gBAAgB,OAAO,IAAI,CAAC;CAC3C;CACA,OAAO,KAAK,OAAO;CACnB,OAAO;AACT;;AAGA,SAAgB,eAAe,OAAc,OAA0C;CACrF,IAAI,MAAM,WAAW,GACnB;CAEF,OAAO,GAAG,GAAG,cAAc,OAAO,KAAK,CAAC,CAAC,KAAK,UAAU,IAAI,GAAG,KAAK,CAAC,CAAC;AACxE;;AAGA,SAAgB,aACd,OACA,QACiB;CACjB,IAAI,CAAC,QACH;CAEF,MAAM,SAAS,SAAS,OAAO,OAAO,KAAK;CAC3C,OAAO,OAAO,cAAc,SAAS,KAAK,MAAM,IAAI,IAAI,MAAM;AAChE"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { BetterAuthPlugin } from "better-auth";
|
|
4
|
+
import { DatabaseAdapter } from "@storyshelf/core/adapter/database";
|
|
5
|
+
import { EmailSender } from "@storyshelf/core/adapter/email-sender";
|
|
6
|
+
import { Auth } from "@storyshelf/core/auth";
|
|
7
|
+
import { Logger } from "@storyshelf/core/logger";
|
|
8
|
+
//#region src/engine.d.ts
|
|
9
|
+
/** Provider id minted on every engine identity (method attribution is future work). */
|
|
10
|
+
declare const ENGINE_PROVIDER_ID = "better-auth";
|
|
11
|
+
/** Options for {@link createShelfAuth}. */
|
|
12
|
+
interface ShelfAuthOptions {
|
|
13
|
+
/** Shelf database (any driver; auth tables migrate via driver DDL). */
|
|
14
|
+
db: DatabaseAdapter;
|
|
15
|
+
/** Session/cookie signing secret (non-empty; Better Auth validates strength). */
|
|
16
|
+
secret: string;
|
|
17
|
+
/** Public base URL issuing auth responses (callback + cookie scope). */
|
|
18
|
+
baseURL: string;
|
|
19
|
+
/** Enable email/password login. Defaults to true. */
|
|
20
|
+
emailPassword?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Email transport shared with notification channels. When set, invite
|
|
23
|
+
* links are emailed best-effort and password-reset requests send mail;
|
|
24
|
+
* when unset, invites stay out-of-band links and resets stay re-invite.
|
|
25
|
+
*/
|
|
26
|
+
emailSender?: EmailSender;
|
|
27
|
+
/** Sender address for auth mail (defaults to `storyshelf@<baseURL host>`). */
|
|
28
|
+
fromEmail?: string;
|
|
29
|
+
/**
|
|
30
|
+
* System-event hook for host-owned fan-out (e.g. `sys:user-created`,
|
|
31
|
+
* `sys:invite-issued` to admin channels). Best-effort: failures are
|
|
32
|
+
* logged and swallowed, never break auth flows.
|
|
33
|
+
*/
|
|
34
|
+
onAuthSystemEvent?: (event: "sys:user-created" | "sys:invite-issued", data: Record<string, unknown>) => unknown;
|
|
35
|
+
/** Host-owned logger for auth diagnostics (explicit override only). */
|
|
36
|
+
logger?: Logger;
|
|
37
|
+
/** Native social providers (GitHub, Google, Entra, Cognito...), each one button. */
|
|
38
|
+
social?: ShelfSocialProvider[];
|
|
39
|
+
/** OAuth/OIDC providers, each rendered as one login button. Defaults to none. */
|
|
40
|
+
oauth?: ShelfOAuthProvider[];
|
|
41
|
+
/** WebAuthn passkeys (profile enrollment; sign-in ceremony is E8). Defaults to off. */
|
|
42
|
+
passkeys?: ShelfPasskeyOptions;
|
|
43
|
+
/** Domain-routed SSO (OIDC discovery + SAML via samlify). Defaults to none. */
|
|
44
|
+
sso?: ShelfSSOOptions;
|
|
45
|
+
/** Extra Better Auth plugins (passkey, SSO, ...). Defaults to none. */
|
|
46
|
+
plugins?: BetterAuthPlugin[];
|
|
47
|
+
/** Session lifetime in days. Defaults to 7 (current parity). */
|
|
48
|
+
sessionExpiresInDays?: number;
|
|
49
|
+
}
|
|
50
|
+
/** One OAuth/OIDC login method (Keycloak preset or explicit endpoints). */
|
|
51
|
+
interface ShelfOAuthProvider {
|
|
52
|
+
/** Stable id used in routes and descriptors (e.g. "keycloak"). */
|
|
53
|
+
id: string;
|
|
54
|
+
/** Human label rendered on the login button. */
|
|
55
|
+
label: string;
|
|
56
|
+
/** OAuth client ID registered with the provider. */
|
|
57
|
+
clientId: string;
|
|
58
|
+
/** OAuth client secret (omit for public clients). */
|
|
59
|
+
clientSecret?: string;
|
|
60
|
+
/** OIDC discovery URL; prefer over explicit endpoints when available. */
|
|
61
|
+
discoveryUrl?: string;
|
|
62
|
+
/** Explicit endpoint overrides (skip discovery for these). */
|
|
63
|
+
authorizationUrl?: string;
|
|
64
|
+
/** Explicit endpoint overrides (skip discovery for these). */
|
|
65
|
+
tokenUrl?: string;
|
|
66
|
+
/** Explicit endpoint overrides (skip discovery for these). */
|
|
67
|
+
userInfoUrl?: string;
|
|
68
|
+
/** OAuth scopes. Defaults to `["openid", "email", "profile"]`. */
|
|
69
|
+
scopes?: string[];
|
|
70
|
+
}
|
|
71
|
+
/** One native social login method (Better Auth built-in provider). */
|
|
72
|
+
interface ShelfSocialProvider {
|
|
73
|
+
/** Better Auth provider key (e.g. "github", "microsoft", "cognito"). */
|
|
74
|
+
id: "github" | "gitlab" | "google" | "microsoft" | "cognito";
|
|
75
|
+
/** Human label rendered on the login button. */
|
|
76
|
+
label: string;
|
|
77
|
+
/** OAuth client ID registered with the provider. */
|
|
78
|
+
clientId: string;
|
|
79
|
+
/** OAuth client secret (omit for public clients). */
|
|
80
|
+
clientSecret?: string;
|
|
81
|
+
/** Provider-specific options (tenantId, domain, region, userPoolId, issuer...). */
|
|
82
|
+
extra?: Record<string, string | undefined>;
|
|
83
|
+
}
|
|
84
|
+
/** WebAuthn passkey config (profile enrollment; sign-in ceremony is E8). */
|
|
85
|
+
interface ShelfPasskeyOptions {
|
|
86
|
+
/**
|
|
87
|
+
* Relying-party id. Defaults to the baseURL hostname.
|
|
88
|
+
*
|
|
89
|
+
* WebAuthn needs a secure context: `localhost` (any port) or HTTPS.
|
|
90
|
+
* Plain-HTTP LAN/IP deployments cannot use passkeys — the UI hides
|
|
91
|
+
* passkey affordances there (see `passkey-ceremony.ts`).
|
|
92
|
+
*/
|
|
93
|
+
rpID?: string;
|
|
94
|
+
/** Human-readable site title. Defaults to "StoryShelf". */
|
|
95
|
+
rpName?: string;
|
|
96
|
+
/** Allowed origins (no trailing slash). Defaults to the baseURL origin. */
|
|
97
|
+
origin?: string | string[];
|
|
98
|
+
}
|
|
99
|
+
/** OIDC SSO connection (discovery-based, resolved by email domain). */
|
|
100
|
+
interface ShelfSSOOidc {
|
|
101
|
+
/** IdP issuer (e.g. the Keycloak realm URL). */
|
|
102
|
+
issuer: string;
|
|
103
|
+
/** Discovery document URL. Defaults to `issuer + /.well-known/openid-configuration`. */
|
|
104
|
+
discoveryUrl?: string;
|
|
105
|
+
/** OAuth client ID registered with the provider. */
|
|
106
|
+
clientId: string;
|
|
107
|
+
/** OAuth client secret (omit for public clients). */
|
|
108
|
+
clientSecret?: string;
|
|
109
|
+
/** OAuth scopes. Defaults to `["openid", "email", "profile"]`. */
|
|
110
|
+
scopes?: string[];
|
|
111
|
+
/** Use PKCE. Defaults to true. */
|
|
112
|
+
pkce?: boolean;
|
|
113
|
+
}
|
|
114
|
+
/** SAML SSO connection (samlify via the SSO plugin; never hand-rolled XML). */
|
|
115
|
+
interface ShelfSSOSaml {
|
|
116
|
+
/** SP entity ID (our audience; also the metadata default). */
|
|
117
|
+
issuer: string;
|
|
118
|
+
/** IdP SSO URL (required by the plugin type; unused when `metadataXml` is set). */
|
|
119
|
+
entryPoint: string;
|
|
120
|
+
/** IdP signing cert(s), PEM (ignored when `metadataXml` is set). */
|
|
121
|
+
cert?: string | string[];
|
|
122
|
+
/** IdP metadata XML (preferred: carries entity ID, endpoints, and certs). */
|
|
123
|
+
metadataXml?: string;
|
|
124
|
+
/** IdP entity ID (required without `metadataXml`). */
|
|
125
|
+
entityID?: string;
|
|
126
|
+
/** Expected audience (defaults to the SP issuer). */
|
|
127
|
+
audience?: string;
|
|
128
|
+
/** Reject unsigned assertions. Defaults to false. */
|
|
129
|
+
wantAssertionsSigned?: boolean;
|
|
130
|
+
}
|
|
131
|
+
/** IdP group sync for one SSO provider (site role + project memberships). */
|
|
132
|
+
interface ShelfSSOGroupSync {
|
|
133
|
+
/**
|
|
134
|
+
* Claim (OIDC) or attribute (SAML) name(s) carrying group memberships.
|
|
135
|
+
* First hit wins across names; arrays pass through, a lone string counts
|
|
136
|
+
* as one group (never whitespace-split). Defaults to `["groups"]`.
|
|
137
|
+
*/
|
|
138
|
+
claim?: string | string[];
|
|
139
|
+
/**
|
|
140
|
+
* Exact group names granting the site `admin` role. Full reconcile:
|
|
141
|
+
* members not matched reset to `member` on every SSO sign-in.
|
|
142
|
+
* Defaults to none (roles untouched).
|
|
143
|
+
*/
|
|
144
|
+
admins?: string[];
|
|
145
|
+
}
|
|
146
|
+
/** One domain-routed SSO login method (OIDC, SAML, or both). */
|
|
147
|
+
interface ShelfSSOProvider {
|
|
148
|
+
/** Stable id used in routes and descriptors (e.g. "acme-sso"). */
|
|
149
|
+
id: string;
|
|
150
|
+
/** Human label rendered on the login button. */
|
|
151
|
+
label: string;
|
|
152
|
+
/** Email domain routing key (e.g. "acme.example"). */
|
|
153
|
+
domain: string;
|
|
154
|
+
/** OIDC connection. Defaults to none. */
|
|
155
|
+
oidc?: ShelfSSOOidc;
|
|
156
|
+
/** SAML connection. Defaults to none. */
|
|
157
|
+
saml?: ShelfSSOSaml;
|
|
158
|
+
/** IdP group sync. Defaults to none (no group reads, roles untouched). */
|
|
159
|
+
groups?: ShelfSSOGroupSync;
|
|
160
|
+
}
|
|
161
|
+
/** Domain-routed SSO (OIDC discovery + SAML). Defaults to none. */
|
|
162
|
+
interface ShelfSSOOptions {
|
|
163
|
+
/** SSO providers, each rendered as one login button. */
|
|
164
|
+
providers: ShelfSSOProvider[];
|
|
165
|
+
/** Require explicit sign-up confirmation for new SSO users. Defaults to false. */
|
|
166
|
+
disableImplicitSignUp?: boolean;
|
|
167
|
+
}
|
|
168
|
+
/** Session user fields the shelf reads back. */
|
|
169
|
+
interface EngineSessionUser {
|
|
170
|
+
id: string;
|
|
171
|
+
email: string;
|
|
172
|
+
name: string;
|
|
173
|
+
image?: string | null;
|
|
174
|
+
}
|
|
175
|
+
/** Subset of the Better Auth instance surface the shelf uses. */
|
|
176
|
+
interface ShelfAuthInstance {
|
|
177
|
+
/** HTTP handler (mount at `/api/auth/*`). */
|
|
178
|
+
handler: (request: Request) => Response | Promise<Response>;
|
|
179
|
+
api: {
|
|
180
|
+
/** Resolve the session user from request headers (null when anonymous). */
|
|
181
|
+
getSession(input: {
|
|
182
|
+
headers: Headers;
|
|
183
|
+
}): Promise<{
|
|
184
|
+
user: EngineSessionUser;
|
|
185
|
+
} | null>;
|
|
186
|
+
/** Revoke the session carried by the headers. */
|
|
187
|
+
signOut(input: {
|
|
188
|
+
headers: Headers;
|
|
189
|
+
}): Promise<unknown>;
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/** Engine handle: raw instance for mounting plus the Auth singleton. */
|
|
193
|
+
interface ShelfAuth {
|
|
194
|
+
/** Better Auth instance (mount `auth.handler`, call `auth.api.*`). */
|
|
195
|
+
auth: ShelfAuthInstance;
|
|
196
|
+
/** Auth singleton (check/destroySession, invites, sessions, descriptors). */
|
|
197
|
+
adapter: Auth;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Create the shelf auth engine over any DatabaseAdapter.
|
|
201
|
+
*
|
|
202
|
+
* Returns the raw instance (mount `auth.handler`, call `auth.api.*`) plus
|
|
203
|
+
* the Auth singleton (check/destroySession, invites, descriptors).
|
|
204
|
+
*/
|
|
205
|
+
declare function createShelfAuth(options: ShelfAuthOptions): ShelfAuth;
|
|
206
|
+
//#endregion
|
|
207
|
+
export { ENGINE_PROVIDER_ID, EngineSessionUser, ShelfAuth, ShelfAuthInstance, ShelfAuthOptions, ShelfOAuthProvider, ShelfPasskeyOptions, ShelfSSOGroupSync, ShelfSSOOidc, ShelfSSOOptions, ShelfSSOProvider, ShelfSSOSaml, ShelfSocialProvider, createShelfAuth };
|
|
208
|
+
//# sourceMappingURL=engine.d.mts.map
|