@datar-platform/better-auth-dynamodb 0.1.0 → 0.1.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @datar-platform/better-auth-dynamodb
2
2
 
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Implemented `consumeOne` and `incrementOne` on the adapter and the built-in
8
+ single-table store. Better Auth's core adapter factory (`@better-auth/core`
9
+ 1.7.x) requires `consumeOne` for atomic single-use credential consumption —
10
+ used by the email-OTP verify flow — and `incrementOne` for atomic guarded
11
+ counter updates. Without them, any consumer on better-auth >=1.7 hit
12
+ `BetterAuthError: Adapter "dynamodb" must implement consumeOne for atomic
13
+ single-use credential consumption` the first time a plugin exercised that
14
+ path (e.g. `emailOTP().signIn`).
15
+ - `consumeOne` is implemented on the built-in store as a native
16
+ `DeleteCommand` with `ReturnValues: "ALL_OLD"` (atomic delete-and-return);
17
+ `incrementOne` as a native `UpdateCommand` with `ADD`/`SET` expressions and
18
+ `ReturnValues: "ALL_NEW"`.
19
+ - Both are optional on the `DynamoStore` interface — a custom store that
20
+ doesn't implement them still works via a non-atomic get-then-write fallback
21
+ in the adapter, so this is not a breaking change for existing custom
22
+ stores.
23
+ - Added LocalStack e2e coverage for both.
24
+
3
25
  ## 0.1.0
4
26
 
5
27
  First stable release. No code changes since `0.1.0-alpha.1` — the adapter, the
package/dist/index.d.ts CHANGED
@@ -94,6 +94,27 @@ interface DynamoStore {
94
94
  index?: string;
95
95
  key?: StoreItem;
96
96
  }): Promise<number | null>;
97
+ /**
98
+ * Optional atomic single-use consume: delete the record with the given `id`
99
+ * and return what was deleted (or `null` if it was already gone), in one
100
+ * operation. Better Auth's email-OTP/verification-token flows require this
101
+ * for correctness under concurrent verify attempts. When a store doesn't
102
+ * provide it, the adapter falls back to a non-atomic get-then-delete.
103
+ */
104
+ consumeOne?(model: string, id: string): Promise<StoreItem | null>;
105
+ /**
106
+ * Optional atomic increment/set: apply `increment` (field -> delta) and
107
+ * `set` (field -> value) to the record with the given `id` in one operation
108
+ * and return the updated record (or `null` if it doesn't exist). Better
109
+ * Auth's guarded-counter flows (e.g. rate-limit style attempt counters)
110
+ * require this for correctness under concurrent updates. When a store
111
+ * doesn't provide it, the adapter falls back to a non-atomic
112
+ * get-then-merge-then-update.
113
+ */
114
+ incrementOne?(model: string, id: string, req: {
115
+ increment: Record<string, number>;
116
+ set?: StoreItem;
117
+ }): Promise<StoreItem | null>;
97
118
  /**
98
119
  * Optional schema generator, wired to the Better Auth CLI `generate` command.
99
120
  * The built-in store emits a portable CloudFormation template; a custom store
package/dist/index.js CHANGED
@@ -143,7 +143,8 @@ import {
143
143
  DynamoDBDocumentClient,
144
144
  GetCommand,
145
145
  PutCommand,
146
- QueryCommand
146
+ QueryCommand,
147
+ UpdateCommand
147
148
  } from "@aws-sdk/lib-dynamodb";
148
149
 
149
150
  // src/stores/default/key-codec.ts
@@ -373,6 +374,54 @@ function createSingleTableStore(opts) {
373
374
  new DeleteCommand({ TableName: tableName, Key: primaryKey(model, id) })
374
375
  );
375
376
  },
377
+ async consumeOne(model, id) {
378
+ const res = await doc.send(
379
+ new DeleteCommand({
380
+ TableName: tableName,
381
+ Key: primaryKey(model, id),
382
+ ReturnValues: "ALL_OLD"
383
+ })
384
+ );
385
+ return stripReserved(res.Attributes);
386
+ },
387
+ async incrementOne(model, id, { increment, set }) {
388
+ const names = {};
389
+ const values = {};
390
+ const addClauses = [];
391
+ const setClauses = [];
392
+ let i = 0;
393
+ for (const [field, delta] of Object.entries(increment)) {
394
+ const nameKey = `#f${i}`;
395
+ const valueKey = `:v${i}`;
396
+ names[nameKey] = field;
397
+ values[valueKey] = delta;
398
+ addClauses.push(`${nameKey} ${valueKey}`);
399
+ i++;
400
+ }
401
+ for (const [field, value] of Object.entries(set ?? {})) {
402
+ const nameKey = `#f${i}`;
403
+ const valueKey = `:v${i}`;
404
+ names[nameKey] = field;
405
+ values[valueKey] = value;
406
+ setClauses.push(`${nameKey} = ${valueKey}`);
407
+ i++;
408
+ }
409
+ const expression = [
410
+ setClauses.length ? `SET ${setClauses.join(", ")}` : null,
411
+ addClauses.length ? `ADD ${addClauses.join(", ")}` : null
412
+ ].filter(Boolean).join(" ");
413
+ const res = await doc.send(
414
+ new UpdateCommand({
415
+ TableName: tableName,
416
+ Key: primaryKey(model, id),
417
+ UpdateExpression: expression,
418
+ ExpressionAttributeNames: names,
419
+ ExpressionAttributeValues: values,
420
+ ReturnValues: "ALL_NEW"
421
+ })
422
+ );
423
+ return stripReserved(res.Attributes);
424
+ },
376
425
  async queryIndex({ model, index, key, cursor }) {
377
426
  const lookup = encodeLookupQuery(model, index, key, indexMap, assignment);
378
427
  const names = { "#pk": lookup.pkAttr };
@@ -599,6 +648,39 @@ var dynamoAdapter = (config = {}) => {
599
648
  }
600
649
  return (await queryAll(m, clauses)).length;
601
650
  },
651
+ async consumeOne({
652
+ model,
653
+ where
654
+ }) {
655
+ const m = getDefaultModelName(model);
656
+ const id = await resolveId(m, where);
657
+ if (!id) return null;
658
+ if (store.consumeOne) return await store.consumeOne(m, id);
659
+ const item = await store.getById(m, id);
660
+ if (!item) return null;
661
+ await store.deleteById(m, id);
662
+ return item;
663
+ },
664
+ async incrementOne({
665
+ model,
666
+ where,
667
+ increment,
668
+ set
669
+ }) {
670
+ const m = getDefaultModelName(model);
671
+ const id = await resolveId(m, where);
672
+ if (!id) return null;
673
+ if (store.incrementOne) {
674
+ return await store.incrementOne(m, id, { increment, set });
675
+ }
676
+ const current = await store.getById(m, id);
677
+ if (!current) return null;
678
+ const patch = { ...set };
679
+ for (const [field, delta] of Object.entries(increment)) {
680
+ patch[field] = (Number(current[field]) || 0) + delta;
681
+ }
682
+ return await store.update(m, id, patch);
683
+ },
602
684
  ...store.createSchema ? {
603
685
  createSchema: (props) => store.createSchema(props)
604
686
  } : {}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/adapter.ts","../src/pagination.ts","../src/planner.ts","../src/stores/default/derive-index-map.ts","../src/stores/default/single-table-store.ts","../src/stores/default/key-codec.ts","../src/stores/default/schema.ts"],"sourcesContent":["import type { CleanedWhere } from \"@better-auth/core/db/adapter\";\nimport type { BetterAuthOptions } from \"better-auth\";\nimport type { AdapterFactory } from \"better-auth/adapters\";\nimport { createAdapterFactory } from \"better-auth/adapters\";\n\nimport type { QueryPlan } from \"./planner\";\nimport type { DynamoAdapterConfig, DynamoStore, StoreItem } from \"./types\";\nimport {\n applySort,\n applyWindow,\n drainPages,\n matchesResidual,\n} from \"./pagination\";\nimport { planQuery } from \"./planner\";\nimport { deriveIndexMap } from \"./stores/default/derive-index-map\";\nimport { createSingleTableStore } from \"./stores/default/single-table-store\";\n\n/**\n * A generic DynamoDB adapter for Better Auth.\n *\n * The factory handles field mapping, JSON/date/boolean coercion, ID generation\n * and `where`-clause normalization; this adapter only implements the raw DB\n * operations. Those operations are expressed against a pluggable\n * {@link DynamoStore} and a declarative access-pattern map, so the same core\n * drives both the built-in single-table store and any custom store.\n *\n * @example\n * ```ts\n * betterAuth({ database: dynamoAdapter({ tableName: \"auth\", region: \"us-east-1\" }) });\n * ```\n */\nexport const dynamoAdapter = (\n config: DynamoAdapterConfig = {},\n): AdapterFactory<BetterAuthOptions> => {\n return createAdapterFactory({\n config: {\n adapterId: \"dynamodb\",\n adapterName: \"DynamoDB\",\n // DynamoDB keys are strings; let Better Auth generate string ids.\n supportsNumericIds: false,\n // We serialize JSON ourselves via the SDK document client.\n supportsJSON: true,\n // No native Date type — stored as ISO strings (see transforms below).\n supportsDates: false,\n supportsBooleans: true,\n usePlural: false,\n debugLogs: config.debugLogs,\n\n // Dates <-> ISO strings, since supportsDates is false.\n customTransformInput: ({ data, fieldAttributes }) =>\n fieldAttributes.type === \"date\" && data instanceof Date\n ? data.toISOString()\n : data,\n customTransformOutput: ({ data, fieldAttributes }) =>\n fieldAttributes.type === \"date\" && typeof data === \"string\"\n ? new Date(data)\n : data,\n },\n\n adapter: ({ schema, debugLog, getDefaultModelName }) => {\n const indexMap = config.indexMap ?? deriveIndexMap(schema);\n const store: DynamoStore =\n config.store ??\n createSingleTableStore({\n tableName: config.tableName,\n region: config.region,\n endpoint: config.endpoint,\n indexMap,\n });\n\n /** Resolve every row matching `where`, using the best access path. */\n const queryAll = async (\n model: string,\n where?: CleanedWhere[],\n ): Promise<StoreItem[]> => {\n const clauses = where ?? [];\n const plan = planQuery(model, clauses, indexMap);\n debugLog(\"queryAll\", { model, plan: plan.kind });\n\n let candidates: StoreItem[];\n if (plan.kind === \"byId\") {\n const one = await store.getById(model, plan.id);\n candidates = one ? [one] : [];\n } else if (plan.kind === \"index\") {\n candidates = await drainPages((cursor) =>\n store.queryIndex({\n model,\n index: plan.index,\n key: plan.key,\n cursor,\n }),\n );\n } else {\n candidates = await drainPages((cursor) =>\n store.listByType({ model, cursor }),\n );\n }\n return candidates.filter((item) =>\n matchesResidual(item, plan.residual),\n );\n };\n\n /** Find the primary id for a `where`, cheaply if it names `id` directly. */\n const resolveId = async (\n model: string,\n where: CleanedWhere[],\n ): Promise<string | null> => {\n const plan: QueryPlan = planQuery(model, where, indexMap);\n if (plan.kind === \"byId\") return plan.id;\n const [first] = await queryAll(model, where);\n return first ? String(first.id) : null;\n };\n\n return {\n async create<T extends Record<string, any>>({\n model,\n data,\n }: {\n model: string;\n data: T;\n }) {\n const m = getDefaultModelName(model);\n const created = await store.put(m, data);\n return created as T;\n },\n\n async update<T>({\n model,\n where,\n update,\n }: {\n model: string;\n where: CleanedWhere[];\n update: T;\n }) {\n const m = getDefaultModelName(model);\n const id = await resolveId(m, where);\n if (!id) return null;\n const updated = await store.update(m, id, update as StoreItem);\n return updated as T | null;\n },\n\n async updateMany({\n model,\n where,\n update,\n }: {\n model: string;\n where: CleanedWhere[];\n update: Record<string, any>;\n }) {\n const m = getDefaultModelName(model);\n const items = await queryAll(m, where);\n await runBatched(items, (item) =>\n store.update(m, String(item.id), update),\n );\n return items.length;\n },\n\n async delete({\n model,\n where,\n }: {\n model: string;\n where: CleanedWhere[];\n }) {\n const m = getDefaultModelName(model);\n const id = await resolveId(m, where);\n if (id) await store.deleteById(m, id);\n },\n\n async deleteMany({\n model,\n where,\n }: {\n model: string;\n where: CleanedWhere[];\n }) {\n const m = getDefaultModelName(model);\n const items = await queryAll(m, where);\n await runBatched(items, (item) =>\n store.deleteById(m, String(item.id)),\n );\n return items.length;\n },\n\n async findOne<T>({\n model,\n where,\n }: {\n model: string;\n where: CleanedWhere[];\n }) {\n const m = getDefaultModelName(model);\n const [first] = await queryAll(m, where);\n return (first ?? null) as T | null;\n },\n\n async findMany<T>({\n model,\n where,\n limit,\n sortBy,\n offset,\n }: {\n model: string;\n where?: CleanedWhere[];\n limit: number;\n sortBy?: { field: string; direction: \"asc\" | \"desc\" };\n offset?: number;\n }) {\n const m = getDefaultModelName(model);\n const items = applyWindow(\n applySort(await queryAll(m, where), sortBy),\n offset,\n limit,\n );\n return items as T[];\n },\n\n async count({\n model,\n where,\n }: {\n model: string;\n where?: CleanedWhere[];\n }) {\n const m = getDefaultModelName(model);\n const clauses = where ?? [];\n const plan = planQuery(m, clauses, indexMap);\n // Fast path: no residual predicates and the store can count natively.\n if (plan.residual.length === 0 && store.count) {\n const fast = await store.count(\n plan.kind === \"index\"\n ? { model: m, index: plan.index, key: plan.key }\n : { model: m },\n );\n if (fast != null) return fast;\n }\n return (await queryAll(m, clauses)).length;\n },\n\n ...(store.createSchema\n ? {\n createSchema: (props: { file?: string; tables: unknown }) =>\n store.createSchema!(props),\n }\n : {}),\n };\n },\n });\n};\n\n/** Run an async op over items in small concurrent chunks to avoid throttling. */\nasync function runBatched<T>(\n items: T[],\n op: (item: T) => Promise<unknown>,\n chunkSize = 10,\n): Promise<void> {\n for (let i = 0; i < items.length; i += chunkSize) {\n await Promise.all(items.slice(i, i + chunkSize).map(op));\n }\n}\n","import type { CleanedWhere } from \"@better-auth/core/db/adapter\";\n\nimport type { QueryPage, StoreItem } from \"./types\";\n\n/**\n * Drain every page of a paginated store query into a single array.\n *\n * DynamoDB paginates at 1 MB / index-page boundaries regardless of match count,\n * so a single `.query()` can silently return a partial result. Draining here —\n * rather than trusting one page — is what makes `findMany`/`count` correct\n * (and removes the old adapter's implicit 10 000-row cap).\n */\nexport async function drainPages(\n fetch: (cursor: unknown) => Promise<QueryPage>,\n): Promise<StoreItem[]> {\n const out: StoreItem[] = [];\n let cursor: unknown = undefined;\n do {\n const page = await fetch(cursor);\n out.push(...page.items);\n cursor = page.cursor;\n } while (cursor);\n return out;\n}\n\nfunction matchOne(item: StoreItem, w: CleanedWhere): boolean {\n const actual = item[w.field];\n const expected = w.value;\n switch (w.operator) {\n case \"eq\":\n return actual === expected;\n case \"ne\":\n return actual !== expected;\n case \"lt\":\n return actual < (expected as any);\n case \"lte\":\n return actual <= (expected as any);\n case \"gt\":\n return actual > (expected as any);\n case \"gte\":\n return actual >= (expected as any);\n case \"in\":\n return Array.isArray(expected) && (expected as any[]).includes(actual);\n case \"not_in\":\n return Array.isArray(expected) && !(expected as any[]).includes(actual);\n case \"contains\":\n return String(actual).includes(String(expected));\n case \"starts_with\":\n return String(actual).startsWith(String(expected));\n case \"ends_with\":\n return String(actual).endsWith(String(expected));\n default:\n return actual === expected;\n }\n}\n\n/**\n * Apply the residual `where` clauses in memory, honoring Better Auth's\n * connector semantics: all `AND` clauses must match, and — if any `OR` clauses\n * are present — at least one of them must also match. An empty residual passes.\n */\nexport function matchesResidual(\n item: StoreItem,\n residual: CleanedWhere[],\n): boolean {\n const andOk = residual\n .filter((w) => w.connector !== \"OR\")\n .every((w) => matchOne(item, w));\n if (!andOk) return false;\n\n const or = residual.filter((w) => w.connector === \"OR\");\n return or.length === 0 || or.some((w) => matchOne(item, w));\n}\n\n/** Stable in-memory sort matching Better Auth's `sortBy` contract. */\nexport function applySort(\n items: StoreItem[],\n sortBy?: { field: string; direction: \"asc\" | \"desc\" },\n): StoreItem[] {\n if (!sortBy) return items;\n const { field, direction } = sortBy;\n const dir = direction === \"asc\" ? 1 : -1;\n return [...items].sort((a, b) => {\n if (a[field] < b[field]) return -dir;\n if (a[field] > b[field]) return dir;\n return 0;\n });\n}\n\n/** Apply `offset`/`limit` (a `limit` of 0 or undefined means \"no cap\"). */\nexport function applyWindow(\n items: StoreItem[],\n offset?: number,\n limit?: number,\n): StoreItem[] {\n let out = items;\n if (offset && offset > 0) out = out.slice(offset);\n if (limit && limit > 0) out = out.slice(0, limit);\n return out;\n}\n","import type { CleanedWhere } from \"@better-auth/core/db/adapter\";\n\nimport type { IndexMap } from \"./index-map\";\nimport type { StoreItem } from \"./types\";\n\n/**\n * A resolved query plan: how the adapter will fetch candidate rows for a\n * `where` clause before applying any residual (in-memory) filtering.\n *\n * - `byId` — a direct primary-key get (the cheapest path).\n * - `index` — a logical index query with a resolved partition/sort key.\n * - `listByType`— no index matched; list all rows of the model and filter.\n *\n * `residual` holds the `where` clauses NOT satisfied by the chosen access path;\n * they are applied in memory by {@link matchesResidual}.\n */\nexport type QueryPlan =\n | { kind: \"byId\"; id: string; residual: CleanedWhere[] }\n | { kind: \"index\"; index: string; key: StoreItem; residual: CleanedWhere[] }\n | { kind: \"listByType\"; residual: CleanedWhere[] };\n\n/** A clause usable for index selection: `eq`, `AND`-connected. */\nfunction isKeyable(w: CleanedWhere): boolean {\n return w.operator === \"eq\" && w.connector === \"AND\" && w.value != null;\n}\n\n/**\n * Choose the most selective access path for `where` against a model's declared\n * access patterns. Falls back to `listByType` when nothing matches.\n *\n * Index selection only ever consumes `eq`/`AND` clauses; everything else (and\n * any `eq` clause not part of the chosen key) becomes residual.\n */\nexport function planQuery(\n model: string,\n where: CleanedWhere[],\n indexMap: IndexMap,\n): QueryPlan {\n const eqByField = new Map<string, CleanedWhere>();\n for (const w of where) {\n if (isKeyable(w) && !eqByField.has(w.field)) eqByField.set(w.field, w);\n }\n\n // Cheapest: a direct id lookup. Everything else filters in memory.\n const idClause = eqByField.get(\"id\");\n if (idClause) {\n return {\n kind: \"byId\",\n id: String(idClause.value),\n residual: where.filter((w) => w !== idClause),\n };\n }\n\n const patterns = indexMap[model] ?? [];\n for (const pattern of patterns) {\n if (!pattern.pk.every((f) => eqByField.has(f))) continue;\n\n const key: StoreItem = {};\n const consumed = new Set<CleanedWhere>();\n for (const f of pattern.pk) {\n const clause = eqByField.get(f)!;\n key[f] = clause.value;\n consumed.add(clause);\n }\n // Attach as many leading sort-key fields as are present (prefix match).\n for (const f of pattern.sk ?? []) {\n const clause = eqByField.get(f);\n if (!clause) break;\n key[f] = clause.value;\n consumed.add(clause);\n }\n\n return {\n kind: \"index\",\n index: pattern.index,\n key,\n residual: where.filter((w) => !consumed.has(w)),\n };\n }\n\n return { kind: \"listByType\", residual: where };\n}\n","import type { BetterAuthDBSchema } from \"@better-auth/core/db\";\n\nimport type { AccessPattern, IndexMap } from \"../../index-map\";\n\n/** Logical index name for a single-field lookup (e.g. `email` -> `by_email`). */\nexport function lookupIndexName(field: string): string {\n return `by_${field}`;\n}\n\n/**\n * Derive a logical access-pattern map from a Better Auth schema.\n *\n * A single-field lookup index is created for every field that is either:\n * - `unique` (e.g. `user.email`, `session.token`) — direct lookups, or\n * - a `references` foreign key (e.g. `account.userId`) — by-parent lookups, or\n * - explicitly flagged `index: true`.\n *\n * This is what lets the built-in store serve any Better Auth model or plugin\n * (two-factor, passkey, api-key, …) out of the box: whatever fields the schema\n * marks as looked-up get an index, with no hand-maintained per-model table.\n */\nexport function deriveIndexMap(schema: BetterAuthDBSchema): IndexMap {\n const map: IndexMap = {};\n\n for (const [model, table] of Object.entries(schema)) {\n const patterns: AccessPattern[] = [];\n const seen = new Set<string>();\n\n const add = (field: string) => {\n if (field === \"id\" || seen.has(field)) return;\n seen.add(field);\n patterns.push({ index: lookupIndexName(field), pk: [field] });\n };\n\n // `unique` fields first (most selective), then foreign keys, then `index`.\n for (const [field, attr] of Object.entries(table.fields)) {\n if (attr.unique) add(field);\n }\n for (const [field, attr] of Object.entries(table.fields)) {\n if (attr.references) add(field);\n }\n for (const [field, attr] of Object.entries(table.fields)) {\n if (attr.index) add(field);\n }\n\n if (patterns.length > 0) map[model] = patterns;\n }\n\n return map;\n}\n","import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport {\n DeleteCommand,\n DynamoDBDocumentClient,\n GetCommand,\n PutCommand,\n QueryCommand,\n} from \"@aws-sdk/lib-dynamodb\";\n\nimport type { IndexMap } from \"../../index-map\";\nimport type { DynamoStore, QueryPage, StoreItem } from \"../../types\";\nimport type { SlotAssignment } from \"./key-codec\";\nimport {\n assignSlots,\n encodeKeys,\n encodeLookupQuery,\n primaryKey,\n stripReserved,\n TYPE_INDEX,\n TYPE_PK,\n} from \"./key-codec\";\nimport { generateSchemaFile } from \"./schema\";\n\nexport interface SingleTableStoreOptions {\n /** DynamoDB table name. Falls back to `DYNAMODB_TABLE_NAME`, then `\"better-auth\"`. */\n tableName?: string;\n /** AWS region (passed to the DynamoDB client). */\n region?: string;\n /** Endpoint override, e.g. DynamoDB Local or LocalStack (`http://localhost:4566`). */\n endpoint?: string;\n /** Resolved logical access-pattern map (derived or user-supplied). */\n indexMap: IndexMap;\n /** Pre-built document client (used by tests, e.g. against DynamoDB Local). */\n documentClient?: DynamoDBDocumentClient;\n}\n\n/**\n * A zero-dependency (beyond the AWS SDK) DynamoDB store for Better Auth.\n *\n * Uses a single table with a `byType` GSI plus generic lookup GSIs. It owns all\n * physical key encoding via the key codec, so the generic adapter core drives it\n * through logical index names only. Works with any Better Auth model or plugin\n * whose looked-up fields are described by the (typically schema-derived) index\n * map.\n */\nexport function createSingleTableStore(\n opts: SingleTableStoreOptions,\n): DynamoStore {\n const tableName =\n opts.tableName ?? process.env.DYNAMODB_TABLE_NAME ?? \"better-auth\";\n const doc =\n opts.documentClient ??\n DynamoDBDocumentClient.from(\n new DynamoDBClient({\n ...(opts.region ? { region: opts.region } : {}),\n ...(opts.endpoint ? { endpoint: opts.endpoint } : {}),\n }),\n { marshallOptions: { removeUndefinedValues: true } },\n );\n const indexMap = opts.indexMap;\n const assignment: SlotAssignment = assignSlots(indexMap);\n\n const query = async (\n input: Omit<\n ConstructorParameters<typeof QueryCommand>[0],\n \"TableName\" | \"ExclusiveStartKey\"\n >,\n cursor?: unknown,\n countOnly = false,\n ): Promise<{ items: StoreItem[]; count: number; cursor?: unknown }> => {\n const res = await doc.send(\n new QueryCommand({\n TableName: tableName,\n ExclusiveStartKey: cursor as Record<string, unknown> | undefined,\n ...(countOnly ? { Select: \"COUNT\" } : {}),\n ...input,\n }),\n );\n return {\n items: countOnly ? [] : (res.Items ?? []).map((i) => stripReserved(i)!),\n count: res.Count ?? 0,\n cursor: res.LastEvaluatedKey,\n };\n };\n\n return {\n async put(model, item) {\n await doc.send(\n new PutCommand({\n TableName: tableName,\n Item: { ...item, ...encodeKeys(model, item, indexMap, assignment) },\n }),\n );\n return item;\n },\n\n async getById(model, id) {\n const res = await doc.send(\n new GetCommand({ TableName: tableName, Key: primaryKey(model, id) }),\n );\n return stripReserved(res.Item);\n },\n\n async update(model, id, patch) {\n const res = await doc.send(\n new GetCommand({ TableName: tableName, Key: primaryKey(model, id) }),\n );\n const existing = stripReserved(res.Item);\n if (!existing) return null;\n const merged = { ...existing, ...patch };\n await doc.send(\n new PutCommand({\n TableName: tableName,\n Item: {\n ...merged,\n ...encodeKeys(model, merged, indexMap, assignment),\n },\n }),\n );\n return merged;\n },\n\n async deleteById(model, id) {\n await doc.send(\n new DeleteCommand({ TableName: tableName, Key: primaryKey(model, id) }),\n );\n },\n\n async queryIndex({ model, index, key, cursor }): Promise<QueryPage> {\n const lookup = encodeLookupQuery(model, index, key, indexMap, assignment);\n const names: Record<string, string> = { \"#pk\": lookup.pkAttr };\n const values: Record<string, unknown> = { \":pk\": lookup.pkValue };\n let condition = \"#pk = :pk\";\n if (lookup.skPrefix) {\n names[\"#sk\"] = lookup.skAttr;\n values[\":skp\"] = lookup.skPrefix;\n condition += \" AND begins_with(#sk, :skp)\";\n }\n const { items, cursor: next } = await query(\n {\n IndexName: lookup.indexName,\n KeyConditionExpression: condition,\n ExpressionAttributeNames: names,\n ExpressionAttributeValues: values,\n },\n cursor,\n );\n return { items, cursor: next };\n },\n\n async listByType({ model, cursor }): Promise<QueryPage> {\n const { items, cursor: next } = await query(\n {\n IndexName: TYPE_INDEX,\n KeyConditionExpression: \"#tpk = :tpk\",\n ExpressionAttributeNames: { \"#tpk\": TYPE_PK },\n ExpressionAttributeValues: { \":tpk\": model },\n },\n cursor,\n );\n return { items, cursor: next };\n },\n\n async count({ model, index, key }) {\n let base: Parameters<typeof query>[0];\n if (index && key) {\n const lookup = encodeLookupQuery(\n model,\n index,\n key,\n indexMap,\n assignment,\n );\n base = {\n IndexName: lookup.indexName,\n KeyConditionExpression: \"#pk = :pk\",\n ExpressionAttributeNames: { \"#pk\": lookup.pkAttr },\n ExpressionAttributeValues: { \":pk\": lookup.pkValue },\n };\n } else {\n base = {\n IndexName: TYPE_INDEX,\n KeyConditionExpression: \"#tpk = :tpk\",\n ExpressionAttributeNames: { \"#tpk\": TYPE_PK },\n ExpressionAttributeValues: { \":tpk\": model },\n };\n }\n let total = 0;\n let cursor: unknown = undefined;\n do {\n const page = await query(base, cursor, true);\n total += page.count;\n cursor = page.cursor;\n } while (cursor);\n return total;\n },\n\n createSchema: ({ file }) =>\n Promise.resolve(\n generateSchemaFile({\n tableName,\n lookupSlots: assignment.maxSlots,\n file,\n }),\n ),\n };\n}\n","import type { IndexMap } from \"../../index-map\";\nimport type { StoreItem } from \"../../types\";\n\n/**\n * Physical key encoding for the built-in single-table store.\n *\n * All reserved attributes are namespaced under `__ba_` so they never collide\n * with Better Auth field names, and are stripped before items are returned. The\n * table has a `PK`/`SK` primary key, one `byType` GSI (to list a whole model),\n * and N generic lookup GSIs — one physical slot per logical index a model uses.\n */\nexport const PK = \"__ba_pk\";\nexport const SK = \"__ba_sk\";\nexport const TYPE_PK = \"__ba_tpk\";\nexport const TYPE_SK = \"__ba_tsk\";\nexport const TYPE_INDEX = \"byType\";\nconst SK_CONST = \"#\";\nconst RESERVED_PREFIX = \"__ba_\";\n\nexport const gsiName = (slot: number): string => `lookup${slot}`;\nexport const gsiPk = (slot: number): string => `__ba_g${slot}pk`;\nexport const gsiSk = (slot: number): string => `__ba_g${slot}sk`;\n\nconst enc = (v: unknown): string => String(v);\nconst joinValues = (values: unknown[]): string => values.map(enc).join(\"#\");\n\n/** Physical slot assignment: model -> (logical index name -> GSI slot number). */\nexport interface SlotAssignment {\n slots: Record<string, Record<string, number>>;\n maxSlots: number;\n}\n\n/**\n * Assign each logical index to a physical GSI slot. Because indexes on\n * different models never coexist on one item, a model's Nth index always maps\n * to slot N — so the table needs only `max(indexes per model)` lookup GSIs.\n */\nexport function assignSlots(indexMap: IndexMap): SlotAssignment {\n const slots: Record<string, Record<string, number>> = {};\n let maxSlots = 0;\n for (const [model, patterns] of Object.entries(indexMap)) {\n const modelSlots: Record<string, number> = {};\n patterns.forEach((pattern, i) => {\n modelSlots[pattern.index] = i + 1;\n });\n slots[model] = modelSlots;\n maxSlots = Math.max(maxSlots, patterns.length);\n }\n return { slots, maxSlots };\n}\n\nexport const primaryKey = (model: string, id: string) => ({\n [PK]: `${model}#${id}`,\n [SK]: SK_CONST,\n});\n\nconst lookupPkValue = (model: string, index: string, values: unknown[]) =>\n `${model}#${index}#${joinValues(values)}`;\n\n/**\n * Compute the reserved key attributes to store alongside an item: the primary\n * key, the type-index key, and a lookup key for every index whose partition\n * fields are all present on the item.\n */\nexport function encodeKeys(\n model: string,\n item: StoreItem,\n indexMap: IndexMap,\n assignment: SlotAssignment,\n): StoreItem {\n const id = String(item.id);\n const keys: StoreItem = {\n ...primaryKey(model, id),\n [TYPE_PK]: model,\n [TYPE_SK]: `${enc(item.createdAt ?? \"\")}#${id}`,\n };\n\n for (const pattern of indexMap[model] ?? []) {\n const pkValues = pattern.pk.map((f) => item[f]);\n if (pkValues.some((v) => v == null)) continue; // index doesn't apply to this row\n const slot = assignment.slots[model]?.[pattern.index];\n if (!slot) continue;\n keys[gsiPk(slot)] = lookupPkValue(model, pattern.index, pkValues);\n const skValues = (pattern.sk ?? []).map((f) => item[f]);\n keys[gsiSk(slot)] = skValues.some((v) => v == null)\n ? `${id}`\n : `${joinValues(skValues)}#${id}`;\n }\n\n return keys;\n}\n\n/** A resolved lookup query against one GSI slot. */\nexport interface LookupQuery {\n indexName: string;\n pkAttr: string;\n pkValue: string;\n skAttr: string;\n /** When set, narrow with `begins_with(skAttr, skPrefix)`. */\n skPrefix?: string;\n}\n\n/**\n * Translate a logical index query into a concrete GSI query. Uses the\n * partition key always, and a `begins_with` sort-key prefix when the caller\n * supplied any leading sort-key field values.\n */\nexport function encodeLookupQuery(\n model: string,\n index: string,\n key: StoreItem,\n indexMap: IndexMap,\n assignment: SlotAssignment,\n): LookupQuery {\n const slot = assignment.slots[model]?.[index];\n if (!slot) {\n throw new Error(\n `No physical slot for index \"${index}\" on model \"${model}\". ` +\n `Ensure the index is present in the adapter's index map.`,\n );\n }\n const pattern = (indexMap[model] ?? []).find((p) => p.index === index)!;\n const pkValues = pattern.pk.map((f) => key[f]);\n const skPresent = (pattern.sk ?? [])\n .map((f) => key[f])\n .filter((v) => v != null);\n\n return {\n indexName: gsiName(slot),\n pkAttr: gsiPk(slot),\n pkValue: lookupPkValue(model, index, pkValues),\n skAttr: gsiSk(slot),\n skPrefix: skPresent.length > 0 ? `${joinValues(skPresent)}#` : undefined,\n };\n}\n\n/** Remove all reserved (`__ba_`) attributes, yielding clean Better Auth data. */\nexport function stripReserved(\n item: StoreItem | undefined | null,\n): StoreItem | null {\n if (!item) return null;\n const clean: StoreItem = {};\n for (const [k, v] of Object.entries(item)) {\n if (!k.startsWith(RESERVED_PREFIX)) clean[k] = v;\n }\n return clean;\n}\n","import type {\n CreateTableCommandInput,\n DynamoDBClient,\n} from \"@aws-sdk/client-dynamodb\";\nimport {\n CreateTableCommand,\n ResourceInUseException,\n waitUntilTableExists,\n} from \"@aws-sdk/client-dynamodb\";\n\nimport {\n gsiName,\n gsiPk,\n gsiSk,\n PK,\n SK,\n TYPE_INDEX,\n TYPE_PK,\n TYPE_SK,\n} from \"./key-codec\";\n\n/** Build the CreateTable input for the built-in store's single-table layout. */\nexport function buildTableDefinition(\n tableName: string,\n lookupSlots: number,\n): CreateTableCommandInput {\n const attr = (name: string) => ({\n AttributeName: name,\n AttributeType: \"S\" as const,\n });\n\n const attributeDefinitions = [\n attr(PK),\n attr(SK),\n attr(TYPE_PK),\n attr(TYPE_SK),\n ];\n const globalSecondaryIndexes = [\n {\n IndexName: TYPE_INDEX,\n KeySchema: [\n { AttributeName: TYPE_PK, KeyType: \"HASH\" as const },\n { AttributeName: TYPE_SK, KeyType: \"RANGE\" as const },\n ],\n Projection: { ProjectionType: \"ALL\" as const },\n },\n ];\n\n for (let slot = 1; slot <= lookupSlots; slot++) {\n attributeDefinitions.push(attr(gsiPk(slot)), attr(gsiSk(slot)));\n globalSecondaryIndexes.push({\n IndexName: gsiName(slot),\n KeySchema: [\n { AttributeName: gsiPk(slot), KeyType: \"HASH\" as const },\n { AttributeName: gsiSk(slot), KeyType: \"RANGE\" as const },\n ],\n Projection: { ProjectionType: \"ALL\" as const },\n });\n }\n\n return {\n TableName: tableName,\n BillingMode: \"PAY_PER_REQUEST\",\n KeySchema: [\n { AttributeName: PK, KeyType: \"HASH\" },\n { AttributeName: SK, KeyType: \"RANGE\" },\n ],\n AttributeDefinitions: attributeDefinitions,\n GlobalSecondaryIndexes: globalSecondaryIndexes,\n };\n}\n\n/**\n * Create the table if it does not already exist and wait until it is active.\n * Idempotent — safe to call at startup or in test `beforeAll` hooks.\n */\nexport async function ensureSchema(opts: {\n client: DynamoDBClient;\n tableName: string;\n lookupSlots: number;\n}): Promise<void> {\n const input = buildTableDefinition(opts.tableName, opts.lookupSlots);\n try {\n await opts.client.send(new CreateTableCommand(input));\n } catch (error) {\n if (!(error instanceof ResourceInUseException)) throw error; // already exists\n }\n await waitUntilTableExists(\n { client: opts.client, maxWaitTime: 60 },\n { TableName: opts.tableName },\n );\n}\n\n/**\n * Better Auth CLI `generate` hook: emit a portable CloudFormation template for\n * the built-in store's table. Table name and lookup-GSI count are configurable\n * (unlike a hardcoded layout), so the generated stack matches the adapter's\n * actual access patterns.\n */\nexport function generateSchemaFile(opts: {\n tableName: string;\n lookupSlots: number;\n file?: string;\n}): { code: string; path: string; overwrite: boolean } {\n const table = buildTableDefinition(opts.tableName, opts.lookupSlots);\n const template = {\n AWSTemplateFormatVersion: \"2010-09-09\",\n Resources: {\n BetterAuthTable: { Type: \"AWS::DynamoDB::Table\", Properties: table },\n },\n };\n const code = `/**\n * Auto-generated DynamoDB CloudFormation template for @datar-platform/better-auth-dynamodb.\n * Table: ${opts.tableName} — ${opts.lookupSlots} lookup GSI(s) + 1 byType GSI.\n * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html\n */\nexport const dynamoDBSchema = ${JSON.stringify(template, null, 2)} as const;\n\nexport default dynamoDBSchema;\n`;\n return {\n code,\n path: opts.file ?? \"dynamodb-cloudformation.ts\",\n overwrite: true,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/adapter.ts","../src/pagination.ts","../src/planner.ts","../src/stores/default/derive-index-map.ts","../src/stores/default/single-table-store.ts","../src/stores/default/key-codec.ts","../src/stores/default/schema.ts"],"sourcesContent":["import type { CleanedWhere } from \"@better-auth/core/db/adapter\";\nimport type { BetterAuthOptions } from \"better-auth\";\nimport type { AdapterFactory } from \"better-auth/adapters\";\nimport { createAdapterFactory } from \"better-auth/adapters\";\n\nimport type { QueryPlan } from \"./planner\";\nimport type { DynamoAdapterConfig, DynamoStore, StoreItem } from \"./types\";\nimport {\n applySort,\n applyWindow,\n drainPages,\n matchesResidual,\n} from \"./pagination\";\nimport { planQuery } from \"./planner\";\nimport { deriveIndexMap } from \"./stores/default/derive-index-map\";\nimport { createSingleTableStore } from \"./stores/default/single-table-store\";\n\n/**\n * A generic DynamoDB adapter for Better Auth.\n *\n * The factory handles field mapping, JSON/date/boolean coercion, ID generation\n * and `where`-clause normalization; this adapter only implements the raw DB\n * operations. Those operations are expressed against a pluggable\n * {@link DynamoStore} and a declarative access-pattern map, so the same core\n * drives both the built-in single-table store and any custom store.\n *\n * @example\n * ```ts\n * betterAuth({ database: dynamoAdapter({ tableName: \"auth\", region: \"us-east-1\" }) });\n * ```\n */\nexport const dynamoAdapter = (\n config: DynamoAdapterConfig = {},\n): AdapterFactory<BetterAuthOptions> => {\n return createAdapterFactory({\n config: {\n adapterId: \"dynamodb\",\n adapterName: \"DynamoDB\",\n // DynamoDB keys are strings; let Better Auth generate string ids.\n supportsNumericIds: false,\n // We serialize JSON ourselves via the SDK document client.\n supportsJSON: true,\n // No native Date type — stored as ISO strings (see transforms below).\n supportsDates: false,\n supportsBooleans: true,\n usePlural: false,\n debugLogs: config.debugLogs,\n\n // Dates <-> ISO strings, since supportsDates is false.\n customTransformInput: ({ data, fieldAttributes }) =>\n fieldAttributes.type === \"date\" && data instanceof Date\n ? data.toISOString()\n : data,\n customTransformOutput: ({ data, fieldAttributes }) =>\n fieldAttributes.type === \"date\" && typeof data === \"string\"\n ? new Date(data)\n : data,\n },\n\n adapter: ({ schema, debugLog, getDefaultModelName }) => {\n const indexMap = config.indexMap ?? deriveIndexMap(schema);\n const store: DynamoStore =\n config.store ??\n createSingleTableStore({\n tableName: config.tableName,\n region: config.region,\n endpoint: config.endpoint,\n indexMap,\n });\n\n /** Resolve every row matching `where`, using the best access path. */\n const queryAll = async (\n model: string,\n where?: CleanedWhere[],\n ): Promise<StoreItem[]> => {\n const clauses = where ?? [];\n const plan = planQuery(model, clauses, indexMap);\n debugLog(\"queryAll\", { model, plan: plan.kind });\n\n let candidates: StoreItem[];\n if (plan.kind === \"byId\") {\n const one = await store.getById(model, plan.id);\n candidates = one ? [one] : [];\n } else if (plan.kind === \"index\") {\n candidates = await drainPages((cursor) =>\n store.queryIndex({\n model,\n index: plan.index,\n key: plan.key,\n cursor,\n }),\n );\n } else {\n candidates = await drainPages((cursor) =>\n store.listByType({ model, cursor }),\n );\n }\n return candidates.filter((item) =>\n matchesResidual(item, plan.residual),\n );\n };\n\n /** Find the primary id for a `where`, cheaply if it names `id` directly. */\n const resolveId = async (\n model: string,\n where: CleanedWhere[],\n ): Promise<string | null> => {\n const plan: QueryPlan = planQuery(model, where, indexMap);\n if (plan.kind === \"byId\") return plan.id;\n const [first] = await queryAll(model, where);\n return first ? String(first.id) : null;\n };\n\n return {\n async create<T extends Record<string, any>>({\n model,\n data,\n }: {\n model: string;\n data: T;\n }) {\n const m = getDefaultModelName(model);\n const created = await store.put(m, data);\n return created as T;\n },\n\n async update<T>({\n model,\n where,\n update,\n }: {\n model: string;\n where: CleanedWhere[];\n update: T;\n }) {\n const m = getDefaultModelName(model);\n const id = await resolveId(m, where);\n if (!id) return null;\n const updated = await store.update(m, id, update as StoreItem);\n return updated as T | null;\n },\n\n async updateMany({\n model,\n where,\n update,\n }: {\n model: string;\n where: CleanedWhere[];\n update: Record<string, any>;\n }) {\n const m = getDefaultModelName(model);\n const items = await queryAll(m, where);\n await runBatched(items, (item) =>\n store.update(m, String(item.id), update),\n );\n return items.length;\n },\n\n async delete({\n model,\n where,\n }: {\n model: string;\n where: CleanedWhere[];\n }) {\n const m = getDefaultModelName(model);\n const id = await resolveId(m, where);\n if (id) await store.deleteById(m, id);\n },\n\n async deleteMany({\n model,\n where,\n }: {\n model: string;\n where: CleanedWhere[];\n }) {\n const m = getDefaultModelName(model);\n const items = await queryAll(m, where);\n await runBatched(items, (item) =>\n store.deleteById(m, String(item.id)),\n );\n return items.length;\n },\n\n async findOne<T>({\n model,\n where,\n }: {\n model: string;\n where: CleanedWhere[];\n }) {\n const m = getDefaultModelName(model);\n const [first] = await queryAll(m, where);\n return (first ?? null) as T | null;\n },\n\n async findMany<T>({\n model,\n where,\n limit,\n sortBy,\n offset,\n }: {\n model: string;\n where?: CleanedWhere[];\n limit: number;\n sortBy?: { field: string; direction: \"asc\" | \"desc\" };\n offset?: number;\n }) {\n const m = getDefaultModelName(model);\n const items = applyWindow(\n applySort(await queryAll(m, where), sortBy),\n offset,\n limit,\n );\n return items as T[];\n },\n\n async count({\n model,\n where,\n }: {\n model: string;\n where?: CleanedWhere[];\n }) {\n const m = getDefaultModelName(model);\n const clauses = where ?? [];\n const plan = planQuery(m, clauses, indexMap);\n // Fast path: no residual predicates and the store can count natively.\n if (plan.residual.length === 0 && store.count) {\n const fast = await store.count(\n plan.kind === \"index\"\n ? { model: m, index: plan.index, key: plan.key }\n : { model: m },\n );\n if (fast != null) return fast;\n }\n return (await queryAll(m, clauses)).length;\n },\n\n async consumeOne<T>({\n model,\n where,\n }: {\n model: string;\n where: CleanedWhere[];\n }) {\n const m = getDefaultModelName(model);\n const id = await resolveId(m, where);\n if (!id) return null;\n if (store.consumeOne) return (await store.consumeOne(m, id)) as T | null;\n // Fallback for stores without a native atomic delete-and-return: not\n // safe under true concurrency, but correct for the common single\n // in-flight verification case.\n const item = await store.getById(m, id);\n if (!item) return null;\n await store.deleteById(m, id);\n return item as T;\n },\n\n async incrementOne<T>({\n model,\n where,\n increment,\n set,\n }: {\n model: string;\n where: CleanedWhere[];\n increment: Record<string, number>;\n set?: Record<string, any>;\n }) {\n const m = getDefaultModelName(model);\n const id = await resolveId(m, where);\n if (!id) return null;\n if (store.incrementOne) {\n return (await store.incrementOne(m, id, { increment, set })) as\n | T\n | null;\n }\n // Fallback for stores without a native atomic add/set: not safe\n // under true concurrency, but correct for the common single\n // in-flight update case.\n const current = await store.getById(m, id);\n if (!current) return null;\n const patch: StoreItem = { ...set };\n for (const [field, delta] of Object.entries(increment)) {\n patch[field] = (Number(current[field]) || 0) + delta;\n }\n return (await store.update(m, id, patch)) as T | null;\n },\n\n ...(store.createSchema\n ? {\n createSchema: (props: { file?: string; tables: unknown }) =>\n store.createSchema!(props),\n }\n : {}),\n };\n },\n });\n};\n\n/** Run an async op over items in small concurrent chunks to avoid throttling. */\nasync function runBatched<T>(\n items: T[],\n op: (item: T) => Promise<unknown>,\n chunkSize = 10,\n): Promise<void> {\n for (let i = 0; i < items.length; i += chunkSize) {\n await Promise.all(items.slice(i, i + chunkSize).map(op));\n }\n}\n","import type { CleanedWhere } from \"@better-auth/core/db/adapter\";\n\nimport type { QueryPage, StoreItem } from \"./types\";\n\n/**\n * Drain every page of a paginated store query into a single array.\n *\n * DynamoDB paginates at 1 MB / index-page boundaries regardless of match count,\n * so a single `.query()` can silently return a partial result. Draining here —\n * rather than trusting one page — is what makes `findMany`/`count` correct\n * (and removes the old adapter's implicit 10 000-row cap).\n */\nexport async function drainPages(\n fetch: (cursor: unknown) => Promise<QueryPage>,\n): Promise<StoreItem[]> {\n const out: StoreItem[] = [];\n let cursor: unknown = undefined;\n do {\n const page = await fetch(cursor);\n out.push(...page.items);\n cursor = page.cursor;\n } while (cursor);\n return out;\n}\n\nfunction matchOne(item: StoreItem, w: CleanedWhere): boolean {\n const actual = item[w.field];\n const expected = w.value;\n switch (w.operator) {\n case \"eq\":\n return actual === expected;\n case \"ne\":\n return actual !== expected;\n case \"lt\":\n return actual < (expected as any);\n case \"lte\":\n return actual <= (expected as any);\n case \"gt\":\n return actual > (expected as any);\n case \"gte\":\n return actual >= (expected as any);\n case \"in\":\n return Array.isArray(expected) && (expected as any[]).includes(actual);\n case \"not_in\":\n return Array.isArray(expected) && !(expected as any[]).includes(actual);\n case \"contains\":\n return String(actual).includes(String(expected));\n case \"starts_with\":\n return String(actual).startsWith(String(expected));\n case \"ends_with\":\n return String(actual).endsWith(String(expected));\n default:\n return actual === expected;\n }\n}\n\n/**\n * Apply the residual `where` clauses in memory, honoring Better Auth's\n * connector semantics: all `AND` clauses must match, and — if any `OR` clauses\n * are present — at least one of them must also match. An empty residual passes.\n */\nexport function matchesResidual(\n item: StoreItem,\n residual: CleanedWhere[],\n): boolean {\n const andOk = residual\n .filter((w) => w.connector !== \"OR\")\n .every((w) => matchOne(item, w));\n if (!andOk) return false;\n\n const or = residual.filter((w) => w.connector === \"OR\");\n return or.length === 0 || or.some((w) => matchOne(item, w));\n}\n\n/** Stable in-memory sort matching Better Auth's `sortBy` contract. */\nexport function applySort(\n items: StoreItem[],\n sortBy?: { field: string; direction: \"asc\" | \"desc\" },\n): StoreItem[] {\n if (!sortBy) return items;\n const { field, direction } = sortBy;\n const dir = direction === \"asc\" ? 1 : -1;\n return [...items].sort((a, b) => {\n if (a[field] < b[field]) return -dir;\n if (a[field] > b[field]) return dir;\n return 0;\n });\n}\n\n/** Apply `offset`/`limit` (a `limit` of 0 or undefined means \"no cap\"). */\nexport function applyWindow(\n items: StoreItem[],\n offset?: number,\n limit?: number,\n): StoreItem[] {\n let out = items;\n if (offset && offset > 0) out = out.slice(offset);\n if (limit && limit > 0) out = out.slice(0, limit);\n return out;\n}\n","import type { CleanedWhere } from \"@better-auth/core/db/adapter\";\n\nimport type { IndexMap } from \"./index-map\";\nimport type { StoreItem } from \"./types\";\n\n/**\n * A resolved query plan: how the adapter will fetch candidate rows for a\n * `where` clause before applying any residual (in-memory) filtering.\n *\n * - `byId` — a direct primary-key get (the cheapest path).\n * - `index` — a logical index query with a resolved partition/sort key.\n * - `listByType`— no index matched; list all rows of the model and filter.\n *\n * `residual` holds the `where` clauses NOT satisfied by the chosen access path;\n * they are applied in memory by {@link matchesResidual}.\n */\nexport type QueryPlan =\n | { kind: \"byId\"; id: string; residual: CleanedWhere[] }\n | { kind: \"index\"; index: string; key: StoreItem; residual: CleanedWhere[] }\n | { kind: \"listByType\"; residual: CleanedWhere[] };\n\n/** A clause usable for index selection: `eq`, `AND`-connected. */\nfunction isKeyable(w: CleanedWhere): boolean {\n return w.operator === \"eq\" && w.connector === \"AND\" && w.value != null;\n}\n\n/**\n * Choose the most selective access path for `where` against a model's declared\n * access patterns. Falls back to `listByType` when nothing matches.\n *\n * Index selection only ever consumes `eq`/`AND` clauses; everything else (and\n * any `eq` clause not part of the chosen key) becomes residual.\n */\nexport function planQuery(\n model: string,\n where: CleanedWhere[],\n indexMap: IndexMap,\n): QueryPlan {\n const eqByField = new Map<string, CleanedWhere>();\n for (const w of where) {\n if (isKeyable(w) && !eqByField.has(w.field)) eqByField.set(w.field, w);\n }\n\n // Cheapest: a direct id lookup. Everything else filters in memory.\n const idClause = eqByField.get(\"id\");\n if (idClause) {\n return {\n kind: \"byId\",\n id: String(idClause.value),\n residual: where.filter((w) => w !== idClause),\n };\n }\n\n const patterns = indexMap[model] ?? [];\n for (const pattern of patterns) {\n if (!pattern.pk.every((f) => eqByField.has(f))) continue;\n\n const key: StoreItem = {};\n const consumed = new Set<CleanedWhere>();\n for (const f of pattern.pk) {\n const clause = eqByField.get(f)!;\n key[f] = clause.value;\n consumed.add(clause);\n }\n // Attach as many leading sort-key fields as are present (prefix match).\n for (const f of pattern.sk ?? []) {\n const clause = eqByField.get(f);\n if (!clause) break;\n key[f] = clause.value;\n consumed.add(clause);\n }\n\n return {\n kind: \"index\",\n index: pattern.index,\n key,\n residual: where.filter((w) => !consumed.has(w)),\n };\n }\n\n return { kind: \"listByType\", residual: where };\n}\n","import type { BetterAuthDBSchema } from \"@better-auth/core/db\";\n\nimport type { AccessPattern, IndexMap } from \"../../index-map\";\n\n/** Logical index name for a single-field lookup (e.g. `email` -> `by_email`). */\nexport function lookupIndexName(field: string): string {\n return `by_${field}`;\n}\n\n/**\n * Derive a logical access-pattern map from a Better Auth schema.\n *\n * A single-field lookup index is created for every field that is either:\n * - `unique` (e.g. `user.email`, `session.token`) — direct lookups, or\n * - a `references` foreign key (e.g. `account.userId`) — by-parent lookups, or\n * - explicitly flagged `index: true`.\n *\n * This is what lets the built-in store serve any Better Auth model or plugin\n * (two-factor, passkey, api-key, …) out of the box: whatever fields the schema\n * marks as looked-up get an index, with no hand-maintained per-model table.\n */\nexport function deriveIndexMap(schema: BetterAuthDBSchema): IndexMap {\n const map: IndexMap = {};\n\n for (const [model, table] of Object.entries(schema)) {\n const patterns: AccessPattern[] = [];\n const seen = new Set<string>();\n\n const add = (field: string) => {\n if (field === \"id\" || seen.has(field)) return;\n seen.add(field);\n patterns.push({ index: lookupIndexName(field), pk: [field] });\n };\n\n // `unique` fields first (most selective), then foreign keys, then `index`.\n for (const [field, attr] of Object.entries(table.fields)) {\n if (attr.unique) add(field);\n }\n for (const [field, attr] of Object.entries(table.fields)) {\n if (attr.references) add(field);\n }\n for (const [field, attr] of Object.entries(table.fields)) {\n if (attr.index) add(field);\n }\n\n if (patterns.length > 0) map[model] = patterns;\n }\n\n return map;\n}\n","import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport {\n DeleteCommand,\n DynamoDBDocumentClient,\n GetCommand,\n PutCommand,\n QueryCommand,\n UpdateCommand,\n} from \"@aws-sdk/lib-dynamodb\";\n\nimport type { IndexMap } from \"../../index-map\";\nimport type { DynamoStore, QueryPage, StoreItem } from \"../../types\";\nimport type { SlotAssignment } from \"./key-codec\";\nimport {\n assignSlots,\n encodeKeys,\n encodeLookupQuery,\n primaryKey,\n stripReserved,\n TYPE_INDEX,\n TYPE_PK,\n} from \"./key-codec\";\nimport { generateSchemaFile } from \"./schema\";\n\nexport interface SingleTableStoreOptions {\n /** DynamoDB table name. Falls back to `DYNAMODB_TABLE_NAME`, then `\"better-auth\"`. */\n tableName?: string;\n /** AWS region (passed to the DynamoDB client). */\n region?: string;\n /** Endpoint override, e.g. DynamoDB Local or LocalStack (`http://localhost:4566`). */\n endpoint?: string;\n /** Resolved logical access-pattern map (derived or user-supplied). */\n indexMap: IndexMap;\n /** Pre-built document client (used by tests, e.g. against DynamoDB Local). */\n documentClient?: DynamoDBDocumentClient;\n}\n\n/**\n * A zero-dependency (beyond the AWS SDK) DynamoDB store for Better Auth.\n *\n * Uses a single table with a `byType` GSI plus generic lookup GSIs. It owns all\n * physical key encoding via the key codec, so the generic adapter core drives it\n * through logical index names only. Works with any Better Auth model or plugin\n * whose looked-up fields are described by the (typically schema-derived) index\n * map.\n */\nexport function createSingleTableStore(\n opts: SingleTableStoreOptions,\n): DynamoStore {\n const tableName =\n opts.tableName ?? process.env.DYNAMODB_TABLE_NAME ?? \"better-auth\";\n const doc =\n opts.documentClient ??\n DynamoDBDocumentClient.from(\n new DynamoDBClient({\n ...(opts.region ? { region: opts.region } : {}),\n ...(opts.endpoint ? { endpoint: opts.endpoint } : {}),\n }),\n { marshallOptions: { removeUndefinedValues: true } },\n );\n const indexMap = opts.indexMap;\n const assignment: SlotAssignment = assignSlots(indexMap);\n\n const query = async (\n input: Omit<\n ConstructorParameters<typeof QueryCommand>[0],\n \"TableName\" | \"ExclusiveStartKey\"\n >,\n cursor?: unknown,\n countOnly = false,\n ): Promise<{ items: StoreItem[]; count: number; cursor?: unknown }> => {\n const res = await doc.send(\n new QueryCommand({\n TableName: tableName,\n ExclusiveStartKey: cursor as Record<string, unknown> | undefined,\n ...(countOnly ? { Select: \"COUNT\" } : {}),\n ...input,\n }),\n );\n return {\n items: countOnly ? [] : (res.Items ?? []).map((i) => stripReserved(i)!),\n count: res.Count ?? 0,\n cursor: res.LastEvaluatedKey,\n };\n };\n\n return {\n async put(model, item) {\n await doc.send(\n new PutCommand({\n TableName: tableName,\n Item: { ...item, ...encodeKeys(model, item, indexMap, assignment) },\n }),\n );\n return item;\n },\n\n async getById(model, id) {\n const res = await doc.send(\n new GetCommand({ TableName: tableName, Key: primaryKey(model, id) }),\n );\n return stripReserved(res.Item);\n },\n\n async update(model, id, patch) {\n const res = await doc.send(\n new GetCommand({ TableName: tableName, Key: primaryKey(model, id) }),\n );\n const existing = stripReserved(res.Item);\n if (!existing) return null;\n const merged = { ...existing, ...patch };\n await doc.send(\n new PutCommand({\n TableName: tableName,\n Item: {\n ...merged,\n ...encodeKeys(model, merged, indexMap, assignment),\n },\n }),\n );\n return merged;\n },\n\n async deleteById(model, id) {\n await doc.send(\n new DeleteCommand({ TableName: tableName, Key: primaryKey(model, id) }),\n );\n },\n\n async consumeOne(model, id) {\n const res = await doc.send(\n new DeleteCommand({\n TableName: tableName,\n Key: primaryKey(model, id),\n ReturnValues: \"ALL_OLD\",\n }),\n );\n return stripReserved(res.Attributes);\n },\n\n async incrementOne(model, id, { increment, set }) {\n const names: Record<string, string> = {};\n const values: Record<string, unknown> = {};\n const addClauses: string[] = [];\n const setClauses: string[] = [];\n let i = 0;\n for (const [field, delta] of Object.entries(increment)) {\n const nameKey = `#f${i}`;\n const valueKey = `:v${i}`;\n names[nameKey] = field;\n values[valueKey] = delta;\n addClauses.push(`${nameKey} ${valueKey}`);\n i++;\n }\n for (const [field, value] of Object.entries(set ?? {})) {\n const nameKey = `#f${i}`;\n const valueKey = `:v${i}`;\n names[nameKey] = field;\n values[valueKey] = value;\n setClauses.push(`${nameKey} = ${valueKey}`);\n i++;\n }\n const expression = [\n setClauses.length ? `SET ${setClauses.join(\", \")}` : null,\n addClauses.length ? `ADD ${addClauses.join(\", \")}` : null,\n ]\n .filter(Boolean)\n .join(\" \");\n const res = await doc.send(\n new UpdateCommand({\n TableName: tableName,\n Key: primaryKey(model, id),\n UpdateExpression: expression,\n ExpressionAttributeNames: names,\n ExpressionAttributeValues: values,\n ReturnValues: \"ALL_NEW\",\n }),\n );\n return stripReserved(res.Attributes);\n },\n\n async queryIndex({ model, index, key, cursor }): Promise<QueryPage> {\n const lookup = encodeLookupQuery(model, index, key, indexMap, assignment);\n const names: Record<string, string> = { \"#pk\": lookup.pkAttr };\n const values: Record<string, unknown> = { \":pk\": lookup.pkValue };\n let condition = \"#pk = :pk\";\n if (lookup.skPrefix) {\n names[\"#sk\"] = lookup.skAttr;\n values[\":skp\"] = lookup.skPrefix;\n condition += \" AND begins_with(#sk, :skp)\";\n }\n const { items, cursor: next } = await query(\n {\n IndexName: lookup.indexName,\n KeyConditionExpression: condition,\n ExpressionAttributeNames: names,\n ExpressionAttributeValues: values,\n },\n cursor,\n );\n return { items, cursor: next };\n },\n\n async listByType({ model, cursor }): Promise<QueryPage> {\n const { items, cursor: next } = await query(\n {\n IndexName: TYPE_INDEX,\n KeyConditionExpression: \"#tpk = :tpk\",\n ExpressionAttributeNames: { \"#tpk\": TYPE_PK },\n ExpressionAttributeValues: { \":tpk\": model },\n },\n cursor,\n );\n return { items, cursor: next };\n },\n\n async count({ model, index, key }) {\n let base: Parameters<typeof query>[0];\n if (index && key) {\n const lookup = encodeLookupQuery(\n model,\n index,\n key,\n indexMap,\n assignment,\n );\n base = {\n IndexName: lookup.indexName,\n KeyConditionExpression: \"#pk = :pk\",\n ExpressionAttributeNames: { \"#pk\": lookup.pkAttr },\n ExpressionAttributeValues: { \":pk\": lookup.pkValue },\n };\n } else {\n base = {\n IndexName: TYPE_INDEX,\n KeyConditionExpression: \"#tpk = :tpk\",\n ExpressionAttributeNames: { \"#tpk\": TYPE_PK },\n ExpressionAttributeValues: { \":tpk\": model },\n };\n }\n let total = 0;\n let cursor: unknown = undefined;\n do {\n const page = await query(base, cursor, true);\n total += page.count;\n cursor = page.cursor;\n } while (cursor);\n return total;\n },\n\n createSchema: ({ file }) =>\n Promise.resolve(\n generateSchemaFile({\n tableName,\n lookupSlots: assignment.maxSlots,\n file,\n }),\n ),\n };\n}\n","import type { IndexMap } from \"../../index-map\";\nimport type { StoreItem } from \"../../types\";\n\n/**\n * Physical key encoding for the built-in single-table store.\n *\n * All reserved attributes are namespaced under `__ba_` so they never collide\n * with Better Auth field names, and are stripped before items are returned. The\n * table has a `PK`/`SK` primary key, one `byType` GSI (to list a whole model),\n * and N generic lookup GSIs — one physical slot per logical index a model uses.\n */\nexport const PK = \"__ba_pk\";\nexport const SK = \"__ba_sk\";\nexport const TYPE_PK = \"__ba_tpk\";\nexport const TYPE_SK = \"__ba_tsk\";\nexport const TYPE_INDEX = \"byType\";\nconst SK_CONST = \"#\";\nconst RESERVED_PREFIX = \"__ba_\";\n\nexport const gsiName = (slot: number): string => `lookup${slot}`;\nexport const gsiPk = (slot: number): string => `__ba_g${slot}pk`;\nexport const gsiSk = (slot: number): string => `__ba_g${slot}sk`;\n\nconst enc = (v: unknown): string => String(v);\nconst joinValues = (values: unknown[]): string => values.map(enc).join(\"#\");\n\n/** Physical slot assignment: model -> (logical index name -> GSI slot number). */\nexport interface SlotAssignment {\n slots: Record<string, Record<string, number>>;\n maxSlots: number;\n}\n\n/**\n * Assign each logical index to a physical GSI slot. Because indexes on\n * different models never coexist on one item, a model's Nth index always maps\n * to slot N — so the table needs only `max(indexes per model)` lookup GSIs.\n */\nexport function assignSlots(indexMap: IndexMap): SlotAssignment {\n const slots: Record<string, Record<string, number>> = {};\n let maxSlots = 0;\n for (const [model, patterns] of Object.entries(indexMap)) {\n const modelSlots: Record<string, number> = {};\n patterns.forEach((pattern, i) => {\n modelSlots[pattern.index] = i + 1;\n });\n slots[model] = modelSlots;\n maxSlots = Math.max(maxSlots, patterns.length);\n }\n return { slots, maxSlots };\n}\n\nexport const primaryKey = (model: string, id: string) => ({\n [PK]: `${model}#${id}`,\n [SK]: SK_CONST,\n});\n\nconst lookupPkValue = (model: string, index: string, values: unknown[]) =>\n `${model}#${index}#${joinValues(values)}`;\n\n/**\n * Compute the reserved key attributes to store alongside an item: the primary\n * key, the type-index key, and a lookup key for every index whose partition\n * fields are all present on the item.\n */\nexport function encodeKeys(\n model: string,\n item: StoreItem,\n indexMap: IndexMap,\n assignment: SlotAssignment,\n): StoreItem {\n const id = String(item.id);\n const keys: StoreItem = {\n ...primaryKey(model, id),\n [TYPE_PK]: model,\n [TYPE_SK]: `${enc(item.createdAt ?? \"\")}#${id}`,\n };\n\n for (const pattern of indexMap[model] ?? []) {\n const pkValues = pattern.pk.map((f) => item[f]);\n if (pkValues.some((v) => v == null)) continue; // index doesn't apply to this row\n const slot = assignment.slots[model]?.[pattern.index];\n if (!slot) continue;\n keys[gsiPk(slot)] = lookupPkValue(model, pattern.index, pkValues);\n const skValues = (pattern.sk ?? []).map((f) => item[f]);\n keys[gsiSk(slot)] = skValues.some((v) => v == null)\n ? `${id}`\n : `${joinValues(skValues)}#${id}`;\n }\n\n return keys;\n}\n\n/** A resolved lookup query against one GSI slot. */\nexport interface LookupQuery {\n indexName: string;\n pkAttr: string;\n pkValue: string;\n skAttr: string;\n /** When set, narrow with `begins_with(skAttr, skPrefix)`. */\n skPrefix?: string;\n}\n\n/**\n * Translate a logical index query into a concrete GSI query. Uses the\n * partition key always, and a `begins_with` sort-key prefix when the caller\n * supplied any leading sort-key field values.\n */\nexport function encodeLookupQuery(\n model: string,\n index: string,\n key: StoreItem,\n indexMap: IndexMap,\n assignment: SlotAssignment,\n): LookupQuery {\n const slot = assignment.slots[model]?.[index];\n if (!slot) {\n throw new Error(\n `No physical slot for index \"${index}\" on model \"${model}\". ` +\n `Ensure the index is present in the adapter's index map.`,\n );\n }\n const pattern = (indexMap[model] ?? []).find((p) => p.index === index)!;\n const pkValues = pattern.pk.map((f) => key[f]);\n const skPresent = (pattern.sk ?? [])\n .map((f) => key[f])\n .filter((v) => v != null);\n\n return {\n indexName: gsiName(slot),\n pkAttr: gsiPk(slot),\n pkValue: lookupPkValue(model, index, pkValues),\n skAttr: gsiSk(slot),\n skPrefix: skPresent.length > 0 ? `${joinValues(skPresent)}#` : undefined,\n };\n}\n\n/** Remove all reserved (`__ba_`) attributes, yielding clean Better Auth data. */\nexport function stripReserved(\n item: StoreItem | undefined | null,\n): StoreItem | null {\n if (!item) return null;\n const clean: StoreItem = {};\n for (const [k, v] of Object.entries(item)) {\n if (!k.startsWith(RESERVED_PREFIX)) clean[k] = v;\n }\n return clean;\n}\n","import type {\n CreateTableCommandInput,\n DynamoDBClient,\n} from \"@aws-sdk/client-dynamodb\";\nimport {\n CreateTableCommand,\n ResourceInUseException,\n waitUntilTableExists,\n} from \"@aws-sdk/client-dynamodb\";\n\nimport {\n gsiName,\n gsiPk,\n gsiSk,\n PK,\n SK,\n TYPE_INDEX,\n TYPE_PK,\n TYPE_SK,\n} from \"./key-codec\";\n\n/** Build the CreateTable input for the built-in store's single-table layout. */\nexport function buildTableDefinition(\n tableName: string,\n lookupSlots: number,\n): CreateTableCommandInput {\n const attr = (name: string) => ({\n AttributeName: name,\n AttributeType: \"S\" as const,\n });\n\n const attributeDefinitions = [\n attr(PK),\n attr(SK),\n attr(TYPE_PK),\n attr(TYPE_SK),\n ];\n const globalSecondaryIndexes = [\n {\n IndexName: TYPE_INDEX,\n KeySchema: [\n { AttributeName: TYPE_PK, KeyType: \"HASH\" as const },\n { AttributeName: TYPE_SK, KeyType: \"RANGE\" as const },\n ],\n Projection: { ProjectionType: \"ALL\" as const },\n },\n ];\n\n for (let slot = 1; slot <= lookupSlots; slot++) {\n attributeDefinitions.push(attr(gsiPk(slot)), attr(gsiSk(slot)));\n globalSecondaryIndexes.push({\n IndexName: gsiName(slot),\n KeySchema: [\n { AttributeName: gsiPk(slot), KeyType: \"HASH\" as const },\n { AttributeName: gsiSk(slot), KeyType: \"RANGE\" as const },\n ],\n Projection: { ProjectionType: \"ALL\" as const },\n });\n }\n\n return {\n TableName: tableName,\n BillingMode: \"PAY_PER_REQUEST\",\n KeySchema: [\n { AttributeName: PK, KeyType: \"HASH\" },\n { AttributeName: SK, KeyType: \"RANGE\" },\n ],\n AttributeDefinitions: attributeDefinitions,\n GlobalSecondaryIndexes: globalSecondaryIndexes,\n };\n}\n\n/**\n * Create the table if it does not already exist and wait until it is active.\n * Idempotent — safe to call at startup or in test `beforeAll` hooks.\n */\nexport async function ensureSchema(opts: {\n client: DynamoDBClient;\n tableName: string;\n lookupSlots: number;\n}): Promise<void> {\n const input = buildTableDefinition(opts.tableName, opts.lookupSlots);\n try {\n await opts.client.send(new CreateTableCommand(input));\n } catch (error) {\n if (!(error instanceof ResourceInUseException)) throw error; // already exists\n }\n await waitUntilTableExists(\n { client: opts.client, maxWaitTime: 60 },\n { TableName: opts.tableName },\n );\n}\n\n/**\n * Better Auth CLI `generate` hook: emit a portable CloudFormation template for\n * the built-in store's table. Table name and lookup-GSI count are configurable\n * (unlike a hardcoded layout), so the generated stack matches the adapter's\n * actual access patterns.\n */\nexport function generateSchemaFile(opts: {\n tableName: string;\n lookupSlots: number;\n file?: string;\n}): { code: string; path: string; overwrite: boolean } {\n const table = buildTableDefinition(opts.tableName, opts.lookupSlots);\n const template = {\n AWSTemplateFormatVersion: \"2010-09-09\",\n Resources: {\n BetterAuthTable: { Type: \"AWS::DynamoDB::Table\", Properties: table },\n },\n };\n const code = `/**\n * Auto-generated DynamoDB CloudFormation template for @datar-platform/better-auth-dynamodb.\n * Table: ${opts.tableName} — ${opts.lookupSlots} lookup GSI(s) + 1 byType GSI.\n * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html\n */\nexport const dynamoDBSchema = ${JSON.stringify(template, null, 2)} as const;\n\nexport default dynamoDBSchema;\n`;\n return {\n code,\n path: opts.file ?? \"dynamodb-cloudformation.ts\",\n overwrite: true,\n };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datar-platform/better-auth-dynamodb",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "private": false,
5
5
  "description": "A generic DynamoDB adapter for Better Auth. Bring your own store or use the built-in single-table store.",
6
6
  "type": "module",