@datar-platform/better-auth-dynamodb 0.1.0-alpha.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 +18 -0
- package/LICENSE +21 -0
- package/README.md +137 -0
- package/dist/adapter.d.ts +19 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +153 -0
- package/dist/adapter.js.map +1 -0
- package/dist/index-map.d.ts +33 -0
- package/dist/index-map.d.ts.map +1 -0
- package/dist/index-map.js +1 -0
- package/dist/index-map.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/pagination.d.ts +25 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +86 -0
- package/dist/pagination.js.map +1 -0
- package/dist/planner.d.ts +36 -0
- package/dist/planner.d.ts.map +1 -0
- package/dist/planner.js +55 -0
- package/dist/planner.js.map +1 -0
- package/dist/stores/default/derive-index-map.d.ts +18 -0
- package/dist/stores/default/derive-index-map.d.ts.map +1 -0
- package/dist/stores/default/derive-index-map.js +46 -0
- package/dist/stores/default/derive-index-map.js.map +1 -0
- package/dist/stores/default/key-codec.d.ts +57 -0
- package/dist/stores/default/key-codec.d.ts.map +1 -0
- package/dist/stores/default/key-codec.js +106 -0
- package/dist/stores/default/key-codec.js.map +1 -0
- package/dist/stores/default/schema.d.ts +28 -0
- package/dist/stores/default/schema.d.ts.map +1 -0
- package/dist/stores/default/schema.js +91 -0
- package/dist/stores/default/schema.js.map +1 -0
- package/dist/stores/default/single-table-store.d.ts +26 -0
- package/dist/stores/default/single-table-store.d.ts.map +1 -0
- package/dist/stores/default/single-table-store.js +128 -0
- package/dist/stores/default/single-table-store.js.map +1 -0
- package/dist/types.d.ts +97 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/package.json +72 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# @datar-platform/better-auth-dynamodb
|
|
2
|
+
|
|
3
|
+
## 0.1.0-alpha.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Initial release. A generic DynamoDB adapter for Better Auth, built on a
|
|
8
|
+
pluggable `DynamoStore` seam.
|
|
9
|
+
- `dynamoAdapter()` implements the full Better Auth adapter contract via
|
|
10
|
+
`createAdapterFactory`, with a declarative where→index planner (`planQuery`)
|
|
11
|
+
driven by an `IndexMap`.
|
|
12
|
+
- Ships a zero-dependency built-in single-table store whose index map is
|
|
13
|
+
auto-derived from the Better Auth schema, so any model or plugin works out of
|
|
14
|
+
the box. `ensureSchema()` / `generateSchemaFile()` provision the table.
|
|
15
|
+
- Bring your own store by implementing `DynamoStore` — the seam is expressed in
|
|
16
|
+
logical index names and field values only, never physical PK/SK.
|
|
17
|
+
- Correct draining pagination, so `count` and `findMany` are not silently
|
|
18
|
+
capped at a single DynamoDB page.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joesome Web Services
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# @datar-platform/better-auth-dynamodb
|
|
2
|
+
|
|
3
|
+
A generic [DynamoDB](https://aws.amazon.com/dynamodb/) adapter for [Better Auth](https://better-auth.com).
|
|
4
|
+
|
|
5
|
+
- **Works out of the box** with any Better Auth model or plugin — the built-in
|
|
6
|
+
single-table store derives its indexes from your schema (`unique` fields and
|
|
7
|
+
foreign keys), so two-factor, passkey, API-key, organization, etc. just work.
|
|
8
|
+
- **Bring your own store.** The adapter talks to a small `DynamoStore` seam, so
|
|
9
|
+
you can back it with an existing single-table design (ElectroDB, custom key
|
|
10
|
+
encoding, a shared table) without changing the adapter.
|
|
11
|
+
- **Correct pagination.** `findMany`/`count` drain every page — no silent row cap.
|
|
12
|
+
- Zero dependencies beyond the AWS SDK.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @datar-platform/better-auth-dynamodb @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick start (built-in single-table store)
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { dynamoAdapter } from "@datar-platform/better-auth-dynamodb";
|
|
24
|
+
import { betterAuth } from "better-auth";
|
|
25
|
+
|
|
26
|
+
export const auth = betterAuth({
|
|
27
|
+
database: dynamoAdapter({
|
|
28
|
+
tableName: "better-auth", // or set DYNAMODB_TABLE_NAME
|
|
29
|
+
region: "us-east-1",
|
|
30
|
+
}),
|
|
31
|
+
emailAndPassword: { enabled: true },
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Provision the table
|
|
36
|
+
|
|
37
|
+
The store uses one table with a `byType` GSI (to list a model) plus one generic
|
|
38
|
+
lookup GSI per index a model needs. Create it once at startup or in setup:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
42
|
+
import {
|
|
43
|
+
assignSlots,
|
|
44
|
+
deriveIndexMap,
|
|
45
|
+
ensureSchema,
|
|
46
|
+
} from "@datar-platform/better-auth-dynamodb";
|
|
47
|
+
|
|
48
|
+
// `schema` is your Better Auth instance's resolved DB schema.
|
|
49
|
+
const indexMap = deriveIndexMap(schema);
|
|
50
|
+
await ensureSchema({
|
|
51
|
+
client: new DynamoDBClient({ region: "us-east-1" }),
|
|
52
|
+
tableName: "better-auth",
|
|
53
|
+
lookupSlots: assignSlots(indexMap).maxSlots,
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
You can also generate a CloudFormation template via the Better Auth CLI
|
|
58
|
+
(`npx @better-auth/cli generate`) — the adapter's `createSchema` emits one sized
|
|
59
|
+
to your access patterns.
|
|
60
|
+
|
|
61
|
+
## Bring your own store
|
|
62
|
+
|
|
63
|
+
Implement `DynamoStore` to run the same adapter against your own table layout.
|
|
64
|
+
The adapter drives it purely through **logical index names + field values** — it
|
|
65
|
+
never touches physical `PK`/`SK` strings, so your store owns all key encoding.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { dynamoAdapter } from "@datar-platform/better-auth-dynamodb";
|
|
69
|
+
|
|
70
|
+
import type {
|
|
71
|
+
DynamoStore,
|
|
72
|
+
IndexMap,
|
|
73
|
+
} from "@datar-platform/better-auth-dynamodb";
|
|
74
|
+
|
|
75
|
+
const store: DynamoStore = {
|
|
76
|
+
put(model, item) {
|
|
77
|
+
/* ... */
|
|
78
|
+
},
|
|
79
|
+
getById(model, id) {
|
|
80
|
+
/* ... */
|
|
81
|
+
},
|
|
82
|
+
update(model, id, patch) {
|
|
83
|
+
/* ... */
|
|
84
|
+
},
|
|
85
|
+
deleteById(model, id) {
|
|
86
|
+
/* ... */
|
|
87
|
+
},
|
|
88
|
+
queryIndex({ model, index, key }) {
|
|
89
|
+
/* map (model, index, key) -> a GSI query */
|
|
90
|
+
},
|
|
91
|
+
listByType({ model }) {
|
|
92
|
+
/* list all rows of a model */
|
|
93
|
+
},
|
|
94
|
+
// optional: count(), createSchema()
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Describe which fields each model is looked up by:
|
|
98
|
+
const indexMap: IndexMap = {
|
|
99
|
+
user: [{ index: "byEmail", pk: ["email"] }],
|
|
100
|
+
account: [
|
|
101
|
+
{ index: "byUser", pk: ["userId"], sk: ["providerId"] },
|
|
102
|
+
{ index: "byAccountId", pk: ["accountId"], sk: ["providerId"] },
|
|
103
|
+
],
|
|
104
|
+
// ...
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
betterAuth({ database: dynamoAdapter({ store, indexMap }) });
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Given a `where`, the adapter picks the first access pattern whose partition-key
|
|
111
|
+
fields are all present (attaching any leading sort-key fields), then filters any
|
|
112
|
+
remaining predicates in memory. Provide an `indexMap` to match your table, or
|
|
113
|
+
omit it to auto-derive from the schema.
|
|
114
|
+
|
|
115
|
+
## Configuration
|
|
116
|
+
|
|
117
|
+
| Option | Default | Description |
|
|
118
|
+
| ----------- | ------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
119
|
+
| `store` | built-in single-table store | Storage backend (`DynamoStore`). |
|
|
120
|
+
| `indexMap` | derived from schema | Access-pattern map. |
|
|
121
|
+
| `tableName` | `DYNAMODB_TABLE_NAME` → `better-auth` | Table name (built-in store). |
|
|
122
|
+
| `region` | SDK default | AWS region (built-in store). |
|
|
123
|
+
| `endpoint` | SDK default | Endpoint override for DynamoDB Local / LocalStack, e.g. `http://localhost:4566` (built-in store). |
|
|
124
|
+
| `debugLogs` | `false` | Better Auth debug logging. |
|
|
125
|
+
|
|
126
|
+
## Notes & limitations
|
|
127
|
+
|
|
128
|
+
- IDs are strings (Better Auth generates them); `supportsNumericIds` is `false`.
|
|
129
|
+
- Dates are stored as ISO strings and re-hydrated on read (`supportsDates: false`).
|
|
130
|
+
- No native transactions — multi-row ops run sequentially in small batches.
|
|
131
|
+
- The built-in store's derived index map keys on schema field names; custom
|
|
132
|
+
`fieldName` mappings are not yet resolved in derivation (pass an explicit
|
|
133
|
+
`indexMap` if you rename fields).
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BetterAuthOptions } from "better-auth";
|
|
2
|
+
import type { AdapterFactory } from "better-auth/adapters";
|
|
3
|
+
import type { DynamoAdapterConfig } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* A generic DynamoDB adapter for Better Auth.
|
|
6
|
+
*
|
|
7
|
+
* The factory handles field mapping, JSON/date/boolean coercion, ID generation
|
|
8
|
+
* and `where`-clause normalization; this adapter only implements the raw DB
|
|
9
|
+
* operations. Those operations are expressed against a pluggable
|
|
10
|
+
* {@link DynamoStore} and a declarative access-pattern map, so the same core
|
|
11
|
+
* drives both the built-in single-table store and any custom store.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* betterAuth({ database: dynamoAdapter({ tableName: "auth", region: "us-east-1" }) });
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare const dynamoAdapter: (config?: DynamoAdapterConfig) => AdapterFactory<BetterAuthOptions>;
|
|
19
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI3D,OAAO,KAAK,EAAE,mBAAmB,EAA0B,MAAM,YAAY,CAAC;AAW9E;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa,GACxB,SAAQ,mBAAwB,KAC/B,cAAc,CAAC,iBAAiB,CA0NlC,CAAC"}
|
package/dist/adapter.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { createAdapterFactory } from "better-auth/adapters";
|
|
2
|
+
import { applySort, applyWindow, drainPages, matchesResidual, } from "./pagination.js";
|
|
3
|
+
import { planQuery } from "./planner.js";
|
|
4
|
+
import { deriveIndexMap } from "./stores/default/derive-index-map.js";
|
|
5
|
+
import { createSingleTableStore } from "./stores/default/single-table-store.js";
|
|
6
|
+
/**
|
|
7
|
+
* A generic DynamoDB adapter for Better Auth.
|
|
8
|
+
*
|
|
9
|
+
* The factory handles field mapping, JSON/date/boolean coercion, ID generation
|
|
10
|
+
* and `where`-clause normalization; this adapter only implements the raw DB
|
|
11
|
+
* operations. Those operations are expressed against a pluggable
|
|
12
|
+
* {@link DynamoStore} and a declarative access-pattern map, so the same core
|
|
13
|
+
* drives both the built-in single-table store and any custom store.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* betterAuth({ database: dynamoAdapter({ tableName: "auth", region: "us-east-1" }) });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export const dynamoAdapter = (config = {}) => {
|
|
21
|
+
return createAdapterFactory({
|
|
22
|
+
config: {
|
|
23
|
+
adapterId: "dynamodb",
|
|
24
|
+
adapterName: "DynamoDB",
|
|
25
|
+
// DynamoDB keys are strings; let Better Auth generate string ids.
|
|
26
|
+
supportsNumericIds: false,
|
|
27
|
+
// We serialize JSON ourselves via the SDK document client.
|
|
28
|
+
supportsJSON: true,
|
|
29
|
+
// No native Date type — stored as ISO strings (see transforms below).
|
|
30
|
+
supportsDates: false,
|
|
31
|
+
supportsBooleans: true,
|
|
32
|
+
usePlural: false,
|
|
33
|
+
debugLogs: config.debugLogs,
|
|
34
|
+
// Dates <-> ISO strings, since supportsDates is false.
|
|
35
|
+
customTransformInput: ({ data, fieldAttributes }) => fieldAttributes.type === "date" && data instanceof Date
|
|
36
|
+
? data.toISOString()
|
|
37
|
+
: data,
|
|
38
|
+
customTransformOutput: ({ data, fieldAttributes }) => fieldAttributes.type === "date" && typeof data === "string"
|
|
39
|
+
? new Date(data)
|
|
40
|
+
: data,
|
|
41
|
+
},
|
|
42
|
+
adapter: ({ schema, debugLog, getDefaultModelName }) => {
|
|
43
|
+
const indexMap = config.indexMap ?? deriveIndexMap(schema);
|
|
44
|
+
const store = config.store ??
|
|
45
|
+
createSingleTableStore({
|
|
46
|
+
tableName: config.tableName,
|
|
47
|
+
region: config.region,
|
|
48
|
+
endpoint: config.endpoint,
|
|
49
|
+
indexMap,
|
|
50
|
+
});
|
|
51
|
+
/** Resolve every row matching `where`, using the best access path. */
|
|
52
|
+
const queryAll = async (model, where) => {
|
|
53
|
+
const clauses = where ?? [];
|
|
54
|
+
const plan = planQuery(model, clauses, indexMap);
|
|
55
|
+
debugLog("queryAll", { model, plan: plan.kind });
|
|
56
|
+
let candidates;
|
|
57
|
+
if (plan.kind === "byId") {
|
|
58
|
+
const one = await store.getById(model, plan.id);
|
|
59
|
+
candidates = one ? [one] : [];
|
|
60
|
+
}
|
|
61
|
+
else if (plan.kind === "index") {
|
|
62
|
+
candidates = await drainPages((cursor) => store.queryIndex({
|
|
63
|
+
model,
|
|
64
|
+
index: plan.index,
|
|
65
|
+
key: plan.key,
|
|
66
|
+
cursor,
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
candidates = await drainPages((cursor) => store.listByType({ model, cursor }));
|
|
71
|
+
}
|
|
72
|
+
return candidates.filter((item) => matchesResidual(item, plan.residual));
|
|
73
|
+
};
|
|
74
|
+
/** Find the primary id for a `where`, cheaply if it names `id` directly. */
|
|
75
|
+
const resolveId = async (model, where) => {
|
|
76
|
+
const plan = planQuery(model, where, indexMap);
|
|
77
|
+
if (plan.kind === "byId")
|
|
78
|
+
return plan.id;
|
|
79
|
+
const [first] = await queryAll(model, where);
|
|
80
|
+
return first ? String(first.id) : null;
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
async create({ model, data, }) {
|
|
84
|
+
const m = getDefaultModelName(model);
|
|
85
|
+
const created = await store.put(m, data);
|
|
86
|
+
return created;
|
|
87
|
+
},
|
|
88
|
+
async update({ model, where, update, }) {
|
|
89
|
+
const m = getDefaultModelName(model);
|
|
90
|
+
const id = await resolveId(m, where);
|
|
91
|
+
if (!id)
|
|
92
|
+
return null;
|
|
93
|
+
const updated = await store.update(m, id, update);
|
|
94
|
+
return updated;
|
|
95
|
+
},
|
|
96
|
+
async updateMany({ model, where, update, }) {
|
|
97
|
+
const m = getDefaultModelName(model);
|
|
98
|
+
const items = await queryAll(m, where);
|
|
99
|
+
await runBatched(items, (item) => store.update(m, String(item.id), update));
|
|
100
|
+
return items.length;
|
|
101
|
+
},
|
|
102
|
+
async delete({ model, where, }) {
|
|
103
|
+
const m = getDefaultModelName(model);
|
|
104
|
+
const id = await resolveId(m, where);
|
|
105
|
+
if (id)
|
|
106
|
+
await store.deleteById(m, id);
|
|
107
|
+
},
|
|
108
|
+
async deleteMany({ model, where, }) {
|
|
109
|
+
const m = getDefaultModelName(model);
|
|
110
|
+
const items = await queryAll(m, where);
|
|
111
|
+
await runBatched(items, (item) => store.deleteById(m, String(item.id)));
|
|
112
|
+
return items.length;
|
|
113
|
+
},
|
|
114
|
+
async findOne({ model, where, }) {
|
|
115
|
+
const m = getDefaultModelName(model);
|
|
116
|
+
const [first] = await queryAll(m, where);
|
|
117
|
+
return (first ?? null);
|
|
118
|
+
},
|
|
119
|
+
async findMany({ model, where, limit, sortBy, offset, }) {
|
|
120
|
+
const m = getDefaultModelName(model);
|
|
121
|
+
const items = applyWindow(applySort(await queryAll(m, where), sortBy), offset, limit);
|
|
122
|
+
return items;
|
|
123
|
+
},
|
|
124
|
+
async count({ model, where, }) {
|
|
125
|
+
const m = getDefaultModelName(model);
|
|
126
|
+
const clauses = where ?? [];
|
|
127
|
+
const plan = planQuery(m, clauses, indexMap);
|
|
128
|
+
// Fast path: no residual predicates and the store can count natively.
|
|
129
|
+
if (plan.residual.length === 0 && store.count) {
|
|
130
|
+
const fast = await store.count(plan.kind === "index"
|
|
131
|
+
? { model: m, index: plan.index, key: plan.key }
|
|
132
|
+
: { model: m });
|
|
133
|
+
if (fast != null)
|
|
134
|
+
return fast;
|
|
135
|
+
}
|
|
136
|
+
return (await queryAll(m, clauses)).length;
|
|
137
|
+
},
|
|
138
|
+
...(store.createSchema
|
|
139
|
+
? {
|
|
140
|
+
createSchema: (props) => store.createSchema(props),
|
|
141
|
+
}
|
|
142
|
+
: {}),
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
/** Run an async op over items in small concurrent chunks to avoid throttling. */
|
|
148
|
+
async function runBatched(items, op, chunkSize = 10) {
|
|
149
|
+
for (let i = 0; i < items.length; i += chunkSize) {
|
|
150
|
+
await Promise.all(items.slice(i, i + chunkSize).map(op));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative description of a DynamoDB access pattern (one logical index).
|
|
3
|
+
*
|
|
4
|
+
* The adapter never deals in physical `PK`/`SK` strings — it only knows the
|
|
5
|
+
* *logical* index name and which fields form its partition/sort key. Each store
|
|
6
|
+
* implementation is responsible for translating a logical index + field values
|
|
7
|
+
* into a concrete DynamoDB query. This is what lets the exact same generic core
|
|
8
|
+
* drive both the built-in single-table store and a bring-your-own store (e.g.
|
|
9
|
+
* one backed by ElectroDB) without either leaking its key encoding upward.
|
|
10
|
+
*/
|
|
11
|
+
export interface AccessPattern {
|
|
12
|
+
/** Logical index name both the planner and the store agree on (e.g. `"byEmail"`). */
|
|
13
|
+
index: string;
|
|
14
|
+
/**
|
|
15
|
+
* Fields that make up the partition key. Every one of these MUST be present as
|
|
16
|
+
* an equality (`eq`) clause in a `where` for this pattern to be selected.
|
|
17
|
+
*/
|
|
18
|
+
pk: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Ordered fields that make up the sort key. Optional and prefix-satisfiable:
|
|
21
|
+
* the planner attaches as many leading `sk` fields as the `where` provides
|
|
22
|
+
* (as `eq` clauses), and leaves the rest to residual in-memory filtering.
|
|
23
|
+
*/
|
|
24
|
+
sk?: string[];
|
|
25
|
+
}
|
|
26
|
+
/** Ordered list of access patterns for a single model, most specific first. */
|
|
27
|
+
export type ModelIndexMap = AccessPattern[];
|
|
28
|
+
/**
|
|
29
|
+
* Map of model name -> its access patterns. The planner walks a model's
|
|
30
|
+
* patterns in order and picks the first whose `pk` fields are all present.
|
|
31
|
+
*/
|
|
32
|
+
export type IndexMap = Record<string, ModelIndexMap>;
|
|
33
|
+
//# sourceMappingURL=index-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-map.d.ts","sourceRoot":"","sources":["../src/index-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,EAAE,EAAE,MAAM,EAAE,CAAC;IACb;;;;OAIG;IACH,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;CACf;AAED,+EAA+E;AAC/E,MAAM,MAAM,aAAa,GAAG,aAAa,EAAE,CAAC;AAE5C;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=index-map.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-map.js","sourceRoot":"","sources":["../src/index-map.ts"],"names":[],"mappings":""}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,YAAY,EACV,mBAAmB,EACnB,WAAW,EACX,SAAS,EACT,SAAS,GACV,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC7E,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGlD,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAChF,YAAY,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACtF,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AACtE,OAAO,EACL,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,GACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
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 { planQuery } from "./planner.js";
|
|
9
|
+
export { matchesResidual } from "./pagination.js";
|
|
10
|
+
// Built-in single-table store (opt-in for advanced wiring / tests).
|
|
11
|
+
export { createSingleTableStore } from "./stores/default/single-table-store.js";
|
|
12
|
+
export { deriveIndexMap } from "./stores/default/derive-index-map.js";
|
|
13
|
+
export { buildTableDefinition, ensureSchema, generateSchemaFile, } from "./stores/default/schema.js";
|
|
14
|
+
export { assignSlots } from "./stores/default/key-codec.js";
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAU7C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,oEAAoE;AACpE,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAEhF,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AACtE,OAAO,EACL,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,GACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CleanedWhere } from "@better-auth/core/db/adapter";
|
|
2
|
+
import type { QueryPage, StoreItem } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Drain every page of a paginated store query into a single array.
|
|
5
|
+
*
|
|
6
|
+
* DynamoDB paginates at 1 MB / index-page boundaries regardless of match count,
|
|
7
|
+
* so a single `.query()` can silently return a partial result. Draining here —
|
|
8
|
+
* rather than trusting one page — is what makes `findMany`/`count` correct
|
|
9
|
+
* (and removes the old adapter's implicit 10 000-row cap).
|
|
10
|
+
*/
|
|
11
|
+
export declare function drainPages(fetch: (cursor: unknown) => Promise<QueryPage>): Promise<StoreItem[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Apply the residual `where` clauses in memory, honoring Better Auth's
|
|
14
|
+
* connector semantics: all `AND` clauses must match, and — if any `OR` clauses
|
|
15
|
+
* are present — at least one of them must also match. An empty residual passes.
|
|
16
|
+
*/
|
|
17
|
+
export declare function matchesResidual(item: StoreItem, residual: CleanedWhere[]): boolean;
|
|
18
|
+
/** Stable in-memory sort matching Better Auth's `sortBy` contract. */
|
|
19
|
+
export declare function applySort(items: StoreItem[], sortBy?: {
|
|
20
|
+
field: string;
|
|
21
|
+
direction: "asc" | "desc";
|
|
22
|
+
}): StoreItem[];
|
|
23
|
+
/** Apply `offset`/`limit` (a `limit` of 0 or undefined means "no cap"). */
|
|
24
|
+
export declare function applyWindow(items: StoreItem[], offset?: number, limit?: number): StoreItem[];
|
|
25
|
+
//# sourceMappingURL=pagination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAEjE,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvD;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,SAAS,CAAC,GAC7C,OAAO,CAAC,SAAS,EAAE,CAAC,CAStB;AAiCD;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,YAAY,EAAE,GACvB,OAAO,CAQT;AAED,sEAAsE;AACtE,wBAAgB,SAAS,CACvB,KAAK,EAAE,SAAS,EAAE,EAClB,MAAM,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,KAAK,GAAG,MAAM,CAAA;CAAE,GACpD,SAAS,EAAE,CASb;AAED,2EAA2E;AAC3E,wBAAgB,WAAW,CACzB,KAAK,EAAE,SAAS,EAAE,EAClB,MAAM,CAAC,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,MAAM,GACb,SAAS,EAAE,CAKb"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drain every page of a paginated store query into a single array.
|
|
3
|
+
*
|
|
4
|
+
* DynamoDB paginates at 1 MB / index-page boundaries regardless of match count,
|
|
5
|
+
* so a single `.query()` can silently return a partial result. Draining here —
|
|
6
|
+
* rather than trusting one page — is what makes `findMany`/`count` correct
|
|
7
|
+
* (and removes the old adapter's implicit 10 000-row cap).
|
|
8
|
+
*/
|
|
9
|
+
export async function drainPages(fetch) {
|
|
10
|
+
const out = [];
|
|
11
|
+
let cursor = undefined;
|
|
12
|
+
do {
|
|
13
|
+
const page = await fetch(cursor);
|
|
14
|
+
out.push(...page.items);
|
|
15
|
+
cursor = page.cursor;
|
|
16
|
+
} while (cursor);
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
function matchOne(item, w) {
|
|
20
|
+
const actual = item[w.field];
|
|
21
|
+
const expected = w.value;
|
|
22
|
+
switch (w.operator) {
|
|
23
|
+
case "eq":
|
|
24
|
+
return actual === expected;
|
|
25
|
+
case "ne":
|
|
26
|
+
return actual !== expected;
|
|
27
|
+
case "lt":
|
|
28
|
+
return actual < expected;
|
|
29
|
+
case "lte":
|
|
30
|
+
return actual <= expected;
|
|
31
|
+
case "gt":
|
|
32
|
+
return actual > expected;
|
|
33
|
+
case "gte":
|
|
34
|
+
return actual >= expected;
|
|
35
|
+
case "in":
|
|
36
|
+
return Array.isArray(expected) && expected.includes(actual);
|
|
37
|
+
case "not_in":
|
|
38
|
+
return Array.isArray(expected) && !expected.includes(actual);
|
|
39
|
+
case "contains":
|
|
40
|
+
return String(actual).includes(String(expected));
|
|
41
|
+
case "starts_with":
|
|
42
|
+
return String(actual).startsWith(String(expected));
|
|
43
|
+
case "ends_with":
|
|
44
|
+
return String(actual).endsWith(String(expected));
|
|
45
|
+
default:
|
|
46
|
+
return actual === expected;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Apply the residual `where` clauses in memory, honoring Better Auth's
|
|
51
|
+
* connector semantics: all `AND` clauses must match, and — if any `OR` clauses
|
|
52
|
+
* are present — at least one of them must also match. An empty residual passes.
|
|
53
|
+
*/
|
|
54
|
+
export function matchesResidual(item, residual) {
|
|
55
|
+
const andOk = residual
|
|
56
|
+
.filter((w) => w.connector !== "OR")
|
|
57
|
+
.every((w) => matchOne(item, w));
|
|
58
|
+
if (!andOk)
|
|
59
|
+
return false;
|
|
60
|
+
const or = residual.filter((w) => w.connector === "OR");
|
|
61
|
+
return or.length === 0 || or.some((w) => matchOne(item, w));
|
|
62
|
+
}
|
|
63
|
+
/** Stable in-memory sort matching Better Auth's `sortBy` contract. */
|
|
64
|
+
export function applySort(items, sortBy) {
|
|
65
|
+
if (!sortBy)
|
|
66
|
+
return items;
|
|
67
|
+
const { field, direction } = sortBy;
|
|
68
|
+
const dir = direction === "asc" ? 1 : -1;
|
|
69
|
+
return [...items].sort((a, b) => {
|
|
70
|
+
if (a[field] < b[field])
|
|
71
|
+
return -dir;
|
|
72
|
+
if (a[field] > b[field])
|
|
73
|
+
return dir;
|
|
74
|
+
return 0;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/** Apply `offset`/`limit` (a `limit` of 0 or undefined means "no cap"). */
|
|
78
|
+
export function applyWindow(items, offset, limit) {
|
|
79
|
+
let out = items;
|
|
80
|
+
if (offset && offset > 0)
|
|
81
|
+
out = out.slice(offset);
|
|
82
|
+
if (limit && limit > 0)
|
|
83
|
+
out = out.slice(0, limit);
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=pagination.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination.js","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CleanedWhere } from "@better-auth/core/db/adapter";
|
|
2
|
+
import type { IndexMap } from "./index-map.js";
|
|
3
|
+
import type { StoreItem } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* A resolved query plan: how the adapter will fetch candidate rows for a
|
|
6
|
+
* `where` clause before applying any residual (in-memory) filtering.
|
|
7
|
+
*
|
|
8
|
+
* - `byId` — a direct primary-key get (the cheapest path).
|
|
9
|
+
* - `index` — a logical index query with a resolved partition/sort key.
|
|
10
|
+
* - `listByType`— no index matched; list all rows of the model and filter.
|
|
11
|
+
*
|
|
12
|
+
* `residual` holds the `where` clauses NOT satisfied by the chosen access path;
|
|
13
|
+
* they are applied in memory by {@link matchesResidual}.
|
|
14
|
+
*/
|
|
15
|
+
export type QueryPlan = {
|
|
16
|
+
kind: "byId";
|
|
17
|
+
id: string;
|
|
18
|
+
residual: CleanedWhere[];
|
|
19
|
+
} | {
|
|
20
|
+
kind: "index";
|
|
21
|
+
index: string;
|
|
22
|
+
key: StoreItem;
|
|
23
|
+
residual: CleanedWhere[];
|
|
24
|
+
} | {
|
|
25
|
+
kind: "listByType";
|
|
26
|
+
residual: CleanedWhere[];
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Choose the most selective access path for `where` against a model's declared
|
|
30
|
+
* access patterns. Falls back to `listByType` when nothing matches.
|
|
31
|
+
*
|
|
32
|
+
* Index selection only ever consumes `eq`/`AND` clauses; everything else (and
|
|
33
|
+
* any `eq` clause not part of the chosen key) becomes residual.
|
|
34
|
+
*/
|
|
35
|
+
export declare function planQuery(model: string, where: CleanedWhere[], indexMap: IndexMap): QueryPlan;
|
|
36
|
+
//# sourceMappingURL=planner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"planner.d.ts","sourceRoot":"","sources":["../src/planner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAEjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;;;;;;;;;GAUG;AACH,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;CAAE,GAC1E;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;CAAE,CAAC;AAOrD;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,YAAY,EAAE,EACrB,QAAQ,EAAE,QAAQ,GACjB,SAAS,CA4CX"}
|