@datar-platform/better-auth-dynamodb 0.1.0-alpha.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/index.d.ts +267 -17
  3. package/dist/index.js +623 -13
  4. package/dist/index.js.map +1 -1
  5. package/package.json +33 -23
  6. package/dist/adapter.d.ts +0 -19
  7. package/dist/adapter.d.ts.map +0 -1
  8. package/dist/adapter.js +0 -153
  9. package/dist/adapter.js.map +0 -1
  10. package/dist/index-map.d.ts +0 -33
  11. package/dist/index-map.d.ts.map +0 -1
  12. package/dist/index-map.js +0 -1
  13. package/dist/index-map.js.map +0 -1
  14. package/dist/index.d.ts.map +0 -1
  15. package/dist/pagination.d.ts +0 -25
  16. package/dist/pagination.d.ts.map +0 -1
  17. package/dist/pagination.js +0 -86
  18. package/dist/pagination.js.map +0 -1
  19. package/dist/planner.d.ts +0 -36
  20. package/dist/planner.d.ts.map +0 -1
  21. package/dist/planner.js +0 -55
  22. package/dist/planner.js.map +0 -1
  23. package/dist/stores/default/derive-index-map.d.ts +0 -18
  24. package/dist/stores/default/derive-index-map.d.ts.map +0 -1
  25. package/dist/stores/default/derive-index-map.js +0 -46
  26. package/dist/stores/default/derive-index-map.js.map +0 -1
  27. package/dist/stores/default/key-codec.d.ts +0 -57
  28. package/dist/stores/default/key-codec.d.ts.map +0 -1
  29. package/dist/stores/default/key-codec.js +0 -106
  30. package/dist/stores/default/key-codec.js.map +0 -1
  31. package/dist/stores/default/schema.d.ts +0 -28
  32. package/dist/stores/default/schema.d.ts.map +0 -1
  33. package/dist/stores/default/schema.js +0 -91
  34. package/dist/stores/default/schema.js.map +0 -1
  35. package/dist/stores/default/single-table-store.d.ts +0 -26
  36. package/dist/stores/default/single-table-store.d.ts.map +0 -1
  37. package/dist/stores/default/single-table-store.js +0 -128
  38. package/dist/stores/default/single-table-store.js.map +0 -1
  39. package/dist/types.d.ts +0 -97
  40. package/dist/types.d.ts.map +0 -1
  41. package/dist/types.js +0 -1
  42. package/dist/types.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @datar-platform/better-auth-dynamodb
2
2
 
3
+ ## 0.1.0
4
+
5
+ First stable release. No code changes since `0.1.0-alpha.1` — the adapter, the
6
+ built-in single-table store, the query planner, and draining pagination are all
7
+ covered by unit and real-DynamoDB (LocalStack) end-to-end tests. Published with
8
+ npm provenance.
9
+
10
+ ## 0.1.0-alpha.1
11
+
12
+ ### Patch Changes
13
+
14
+ - Declared `@better-auth/core` as an optional peer dependency so the published
15
+ type declarations (which reference `@better-auth/core/db`) resolve cleanly
16
+ under strict package managers.
17
+ - Releases are now published with npm provenance.
18
+ - No runtime behavior changes.
19
+
3
20
  ## 0.1.0-alpha.0
4
21
 
5
22
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -1,18 +1,268 @@
1
+ import { BetterAuthOptions } from 'better-auth';
2
+ import { AdapterFactory } from 'better-auth/adapters';
3
+ import { DBAdapterDebugLogOption, CleanedWhere } from '@better-auth/core/db/adapter';
4
+ import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
5
+ import { BetterAuthDBSchema } from '@better-auth/core/db';
6
+ import { CreateTableCommandInput, DynamoDBClient } from '@aws-sdk/client-dynamodb';
7
+
1
8
  /**
2
- * @datar-platform/better-auth-dynamodb
3
- *
4
- * A generic DynamoDB adapter for Better Auth. Use the built-in single-table
5
- * store, or bring your own by implementing {@link DynamoStore}.
6
- */
7
- export { dynamoAdapter } from "./adapter.js";
8
- export type { DynamoAdapterConfig, DynamoStore, QueryPage, StoreItem, } from "./types.js";
9
- export type { AccessPattern, IndexMap, ModelIndexMap } from "./index-map.js";
10
- export type { QueryPlan } from "./planner.js";
11
- export { planQuery } from "./planner.js";
12
- export { matchesResidual } from "./pagination.js";
13
- export { createSingleTableStore } from "./stores/default/single-table-store.js";
14
- export type { SingleTableStoreOptions } from "./stores/default/single-table-store.js";
15
- export { deriveIndexMap } from "./stores/default/derive-index-map.js";
16
- export { buildTableDefinition, ensureSchema, generateSchemaFile, } from "./stores/default/schema.js";
17
- export { assignSlots } from "./stores/default/key-codec.js";
18
- //# sourceMappingURL=index.d.ts.map
9
+ * Declarative description of a DynamoDB access pattern (one logical index).
10
+ *
11
+ * The adapter never deals in physical `PK`/`SK` strings it only knows the
12
+ * *logical* index name and which fields form its partition/sort key. Each store
13
+ * implementation is responsible for translating a logical index + field values
14
+ * into a concrete DynamoDB query. This is what lets the exact same generic core
15
+ * drive both the built-in single-table store and a bring-your-own store (e.g.
16
+ * one backed by ElectroDB) without either leaking its key encoding upward.
17
+ */
18
+ interface AccessPattern {
19
+ /** Logical index name both the planner and the store agree on (e.g. `"byEmail"`). */
20
+ index: string;
21
+ /**
22
+ * Fields that make up the partition key. Every one of these MUST be present as
23
+ * an equality (`eq`) clause in a `where` for this pattern to be selected.
24
+ */
25
+ pk: string[];
26
+ /**
27
+ * Ordered fields that make up the sort key. Optional and prefix-satisfiable:
28
+ * the planner attaches as many leading `sk` fields as the `where` provides
29
+ * (as `eq` clauses), and leaves the rest to residual in-memory filtering.
30
+ */
31
+ sk?: string[];
32
+ }
33
+ /** Ordered list of access patterns for a single model, most specific first. */
34
+ type ModelIndexMap = AccessPattern[];
35
+ /**
36
+ * Map of model name -> its access patterns. The planner walks a model's
37
+ * patterns in order and picks the first whose `pk` fields are all present.
38
+ */
39
+ type IndexMap = Record<string, ModelIndexMap>;
40
+
41
+ /** A single record as stored/returned. Better Auth handles field-level typing. */
42
+ type StoreItem = Record<string, any>;
43
+ /** One page of a paginated query. `cursor` is opaque and store-defined. */
44
+ interface QueryPage {
45
+ items: StoreItem[];
46
+ cursor?: unknown;
47
+ }
48
+ /**
49
+ * The storage seam. A `DynamoStore` knows how to persist and query records for
50
+ * a given model; it owns all physical key encoding. The generic adapter core
51
+ * drives it purely through logical index names + field values, so any store —
52
+ * the built-in single-table store or a custom one — plugs in the same way.
53
+ *
54
+ * Implementations should return the full item (including generated fields) from
55
+ * `put`/`update` so the adapter can hand it back to Better Auth.
56
+ */
57
+ interface DynamoStore {
58
+ /** Persist a new record and return it (with any store-generated fields). */
59
+ put(model: string, item: StoreItem): Promise<StoreItem>;
60
+ /** Fetch a single record by its primary `id`, or `null` if absent. */
61
+ getById(model: string, id: string): Promise<StoreItem | null>;
62
+ /** Patch the named fields on the record with the given `id` and return the new item. */
63
+ update(model: string, id: string, patch: StoreItem): Promise<StoreItem | null>;
64
+ /** Delete the record with the given `id`. No-op if it does not exist. */
65
+ deleteById(model: string, id: string): Promise<void>;
66
+ /**
67
+ * Query a logical index. `key` holds the partition-key field values plus any
68
+ * leading sort-key field values the caller resolved. Returns one page; the
69
+ * core drains pages via `cursor` until exhausted.
70
+ */
71
+ queryIndex(req: {
72
+ model: string;
73
+ index: string;
74
+ key: StoreItem;
75
+ limit?: number;
76
+ cursor?: unknown;
77
+ }): Promise<QueryPage>;
78
+ /**
79
+ * List every record of a model (the substitute for a full table scan when no
80
+ * index matches a query). Returns one page; the core drains via `cursor`.
81
+ */
82
+ listByType(req: {
83
+ model: string;
84
+ limit?: number;
85
+ cursor?: unknown;
86
+ }): Promise<QueryPage>;
87
+ /**
88
+ * Optional fast count. Only used by the core when the query has no residual
89
+ * (in-memory) predicates. Return `null` to signal "no fast path, fall back to
90
+ * draining + counting".
91
+ */
92
+ count?(req: {
93
+ model: string;
94
+ index?: string;
95
+ key?: StoreItem;
96
+ }): Promise<number | null>;
97
+ /**
98
+ * Optional schema generator, wired to the Better Auth CLI `generate` command.
99
+ * The built-in store emits a portable CloudFormation template; a custom store
100
+ * may emit whatever provisioning artifact fits its physical layout.
101
+ */
102
+ createSchema?(opts: {
103
+ file?: string;
104
+ tables: unknown;
105
+ }): Promise<{
106
+ code: string;
107
+ path: string;
108
+ overwrite?: boolean;
109
+ }>;
110
+ }
111
+ /**
112
+ * Public configuration for {@link dynamoAdapter}.
113
+ *
114
+ * Omit `store` to use the built-in single-table store (needs `tableName`).
115
+ * Omit `indexMap` to auto-derive access patterns from the Better Auth schema
116
+ * (`unique` fields -> lookup indexes, `references` fields -> by-parent indexes).
117
+ */
118
+ interface DynamoAdapterConfig {
119
+ /** Storage backend. Defaults to the built-in single-table store. */
120
+ store?: DynamoStore;
121
+ /** Access-pattern map. Defaults to schema-derived patterns. */
122
+ indexMap?: IndexMap;
123
+ /** DynamoDB table name (used only by the built-in store). */
124
+ tableName?: string;
125
+ /** AWS region (used only by the built-in store). */
126
+ region?: string;
127
+ /**
128
+ * Override the DynamoDB endpoint (used only by the built-in store). Point this
129
+ * at DynamoDB Local or LocalStack, e.g. `http://localhost:4566`.
130
+ */
131
+ endpoint?: string;
132
+ /** Better Auth debug logging, forwarded to the adapter factory. */
133
+ debugLogs?: DBAdapterDebugLogOption;
134
+ }
135
+
136
+ /**
137
+ * A generic DynamoDB adapter for Better Auth.
138
+ *
139
+ * The factory handles field mapping, JSON/date/boolean coercion, ID generation
140
+ * and `where`-clause normalization; this adapter only implements the raw DB
141
+ * operations. Those operations are expressed against a pluggable
142
+ * {@link DynamoStore} and a declarative access-pattern map, so the same core
143
+ * drives both the built-in single-table store and any custom store.
144
+ *
145
+ * @example
146
+ * ```ts
147
+ * betterAuth({ database: dynamoAdapter({ tableName: "auth", region: "us-east-1" }) });
148
+ * ```
149
+ */
150
+ declare const dynamoAdapter: (config?: DynamoAdapterConfig) => AdapterFactory<BetterAuthOptions>;
151
+
152
+ /**
153
+ * A resolved query plan: how the adapter will fetch candidate rows for a
154
+ * `where` clause before applying any residual (in-memory) filtering.
155
+ *
156
+ * - `byId` — a direct primary-key get (the cheapest path).
157
+ * - `index` — a logical index query with a resolved partition/sort key.
158
+ * - `listByType`— no index matched; list all rows of the model and filter.
159
+ *
160
+ * `residual` holds the `where` clauses NOT satisfied by the chosen access path;
161
+ * they are applied in memory by {@link matchesResidual}.
162
+ */
163
+ type QueryPlan = {
164
+ kind: "byId";
165
+ id: string;
166
+ residual: CleanedWhere[];
167
+ } | {
168
+ kind: "index";
169
+ index: string;
170
+ key: StoreItem;
171
+ residual: CleanedWhere[];
172
+ } | {
173
+ kind: "listByType";
174
+ residual: CleanedWhere[];
175
+ };
176
+ /**
177
+ * Choose the most selective access path for `where` against a model's declared
178
+ * access patterns. Falls back to `listByType` when nothing matches.
179
+ *
180
+ * Index selection only ever consumes `eq`/`AND` clauses; everything else (and
181
+ * any `eq` clause not part of the chosen key) becomes residual.
182
+ */
183
+ declare function planQuery(model: string, where: CleanedWhere[], indexMap: IndexMap): QueryPlan;
184
+
185
+ /**
186
+ * Apply the residual `where` clauses in memory, honoring Better Auth's
187
+ * connector semantics: all `AND` clauses must match, and — if any `OR` clauses
188
+ * are present — at least one of them must also match. An empty residual passes.
189
+ */
190
+ declare function matchesResidual(item: StoreItem, residual: CleanedWhere[]): boolean;
191
+
192
+ interface SingleTableStoreOptions {
193
+ /** DynamoDB table name. Falls back to `DYNAMODB_TABLE_NAME`, then `"better-auth"`. */
194
+ tableName?: string;
195
+ /** AWS region (passed to the DynamoDB client). */
196
+ region?: string;
197
+ /** Endpoint override, e.g. DynamoDB Local or LocalStack (`http://localhost:4566`). */
198
+ endpoint?: string;
199
+ /** Resolved logical access-pattern map (derived or user-supplied). */
200
+ indexMap: IndexMap;
201
+ /** Pre-built document client (used by tests, e.g. against DynamoDB Local). */
202
+ documentClient?: DynamoDBDocumentClient;
203
+ }
204
+ /**
205
+ * A zero-dependency (beyond the AWS SDK) DynamoDB store for Better Auth.
206
+ *
207
+ * Uses a single table with a `byType` GSI plus generic lookup GSIs. It owns all
208
+ * physical key encoding via the key codec, so the generic adapter core drives it
209
+ * through logical index names only. Works with any Better Auth model or plugin
210
+ * whose looked-up fields are described by the (typically schema-derived) index
211
+ * map.
212
+ */
213
+ declare function createSingleTableStore(opts: SingleTableStoreOptions): DynamoStore;
214
+
215
+ /**
216
+ * Derive a logical access-pattern map from a Better Auth schema.
217
+ *
218
+ * A single-field lookup index is created for every field that is either:
219
+ * - `unique` (e.g. `user.email`, `session.token`) — direct lookups, or
220
+ * - a `references` foreign key (e.g. `account.userId`) — by-parent lookups, or
221
+ * - explicitly flagged `index: true`.
222
+ *
223
+ * This is what lets the built-in store serve any Better Auth model or plugin
224
+ * (two-factor, passkey, api-key, …) out of the box: whatever fields the schema
225
+ * marks as looked-up get an index, with no hand-maintained per-model table.
226
+ */
227
+ declare function deriveIndexMap(schema: BetterAuthDBSchema): IndexMap;
228
+
229
+ /** Build the CreateTable input for the built-in store's single-table layout. */
230
+ declare function buildTableDefinition(tableName: string, lookupSlots: number): CreateTableCommandInput;
231
+ /**
232
+ * Create the table if it does not already exist and wait until it is active.
233
+ * Idempotent — safe to call at startup or in test `beforeAll` hooks.
234
+ */
235
+ declare function ensureSchema(opts: {
236
+ client: DynamoDBClient;
237
+ tableName: string;
238
+ lookupSlots: number;
239
+ }): Promise<void>;
240
+ /**
241
+ * Better Auth CLI `generate` hook: emit a portable CloudFormation template for
242
+ * the built-in store's table. Table name and lookup-GSI count are configurable
243
+ * (unlike a hardcoded layout), so the generated stack matches the adapter's
244
+ * actual access patterns.
245
+ */
246
+ declare function generateSchemaFile(opts: {
247
+ tableName: string;
248
+ lookupSlots: number;
249
+ file?: string;
250
+ }): {
251
+ code: string;
252
+ path: string;
253
+ overwrite: boolean;
254
+ };
255
+
256
+ /** Physical slot assignment: model -> (logical index name -> GSI slot number). */
257
+ interface SlotAssignment {
258
+ slots: Record<string, Record<string, number>>;
259
+ maxSlots: number;
260
+ }
261
+ /**
262
+ * Assign each logical index to a physical GSI slot. Because indexes on
263
+ * different models never coexist on one item, a model's Nth index always maps
264
+ * to slot N — so the table needs only `max(indexes per model)` lookup GSIs.
265
+ */
266
+ declare function assignSlots(indexMap: IndexMap): SlotAssignment;
267
+
268
+ export { type AccessPattern, type DynamoAdapterConfig, type DynamoStore, type IndexMap, type ModelIndexMap, type QueryPage, type QueryPlan, type SingleTableStoreOptions, type StoreItem, assignSlots, buildTableDefinition, createSingleTableStore, deriveIndexMap, dynamoAdapter, ensureSchema, generateSchemaFile, matchesResidual, planQuery };