@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.
- package/CHANGELOG.md +17 -0
- package/dist/index.d.ts +267 -17
- package/dist/index.js +623 -13
- package/dist/index.js.map +1 -1
- package/package.json +33 -23
- package/dist/adapter.d.ts +0 -19
- package/dist/adapter.d.ts.map +0 -1
- package/dist/adapter.js +0 -153
- package/dist/adapter.js.map +0 -1
- package/dist/index-map.d.ts +0 -33
- package/dist/index-map.d.ts.map +0 -1
- package/dist/index-map.js +0 -1
- package/dist/index-map.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/pagination.d.ts +0 -25
- package/dist/pagination.d.ts.map +0 -1
- package/dist/pagination.js +0 -86
- package/dist/pagination.js.map +0 -1
- package/dist/planner.d.ts +0 -36
- package/dist/planner.d.ts.map +0 -1
- package/dist/planner.js +0 -55
- package/dist/planner.js.map +0 -1
- package/dist/stores/default/derive-index-map.d.ts +0 -18
- package/dist/stores/default/derive-index-map.d.ts.map +0 -1
- package/dist/stores/default/derive-index-map.js +0 -46
- package/dist/stores/default/derive-index-map.js.map +0 -1
- package/dist/stores/default/key-codec.d.ts +0 -57
- package/dist/stores/default/key-codec.d.ts.map +0 -1
- package/dist/stores/default/key-codec.js +0 -106
- package/dist/stores/default/key-codec.js.map +0 -1
- package/dist/stores/default/schema.d.ts +0 -28
- package/dist/stores/default/schema.d.ts.map +0 -1
- package/dist/stores/default/schema.js +0 -91
- package/dist/stores/default/schema.js.map +0 -1
- package/dist/stores/default/single-table-store.d.ts +0 -26
- package/dist/stores/default/single-table-store.d.ts.map +0 -1
- package/dist/stores/default/single-table-store.js +0 -128
- package/dist/stores/default/single-table-store.js.map +0 -1
- package/dist/types.d.ts +0 -97
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -1
- 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
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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 };
|