betterauth-dynamodb-adapter 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -22,6 +22,10 @@ A DynamoDB adapter for [Better Auth](https://www.better-auth.com/). Uses a singl
22
22
  npm install betterauth-dynamodb-adapter
23
23
  # or
24
24
  bun add betterauth-dynamodb-adapter
25
+ # or
26
+ pnpm add betterauth-dynamodb-adapter
27
+ # or
28
+ yarn add betterauth-dynamodb-adapter
25
29
  ```
26
30
 
27
31
  ### Peer dependencies
@@ -47,6 +51,8 @@ Then create a Global Secondary Index:
47
51
  |----------------|---------------|--------|
48
52
  | `_table-index` | `_table` | String |
49
53
 
54
+ Optionally, enable TTL on `expiresAt` to auto-expire sessions and verification tokens.
55
+
50
56
 
51
57
  ### AWS CLI
52
58
 
@@ -67,6 +73,10 @@ aws dynamodb create-table \
67
73
  "Projection": {"ProjectionType": "ALL"}
68
74
  }]' \
69
75
  --billing-mode PAY_PER_REQUEST
76
+
77
+ aws dynamodb update-time-to-live \
78
+ --table-name my-auth-table \
79
+ --time-to-live-specification "Enabled=true, AttributeName=expiresAt"
70
80
  ```
71
81
 
72
82
  ### CDK
@@ -86,6 +96,45 @@ table.addGlobalSecondaryIndex({
86
96
  partitionKey: { name: "_table", type: AttributeType.STRING },
87
97
  projectionType: ProjectionType.ALL,
88
98
  });
99
+
100
+ table.addTimeToLive({ attribute: "expiresAt" });
101
+ ```
102
+
103
+ ### Terraform
104
+
105
+ ```hcl
106
+ resource "aws_dynamodb_table" "auth" {
107
+ name = "my-auth-table"
108
+ billing_mode = "PAY_PER_REQUEST"
109
+ hash_key = "_pk"
110
+ range_key = "_sk"
111
+
112
+ attribute {
113
+ name = "_pk"
114
+ type = "S"
115
+ }
116
+
117
+ attribute {
118
+ name = "_sk"
119
+ type = "S"
120
+ }
121
+
122
+ attribute {
123
+ name = "_table"
124
+ type = "S"
125
+ }
126
+
127
+ global_secondary_index {
128
+ name = "_table-index"
129
+ hash_key = "_table"
130
+ projection_type = "ALL"
131
+ }
132
+
133
+ ttl {
134
+ attribute_name = "expiresAt"
135
+ enabled = true
136
+ }
137
+ }
89
138
  ```
90
139
 
91
140
  ## Usage
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- var e=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);let t=require(`better-auth/adapters`),n=require(`@aws-sdk/client-dynamodb`);var r=e((e=>{var t=class e{value;constructor(e){typeof e==`object`&&`N`in e?this.value=String(e.N):this.value=String(e);let t=typeof e.valueOf()==`number`?e.valueOf():0;if(t>2**53-1||t<-(2**53-1)||Math.abs(t)===1/0||Number.isNaN(t))throw Error(`NumberValue should not be initialized with an imprecise number=${t}. Use a string instead.`)}static from(t){return new e(t)}toAttributeValue(){return{N:this.toString()}}toBigInt(){let e=this.toString();return BigInt(e)}toString(){return String(this.value)}valueOf(){return this.toString()}};let n=(e,n)=>{if(e===void 0)throw Error(`Pass options.removeUndefinedValues=true to remove undefined values from map/array/set.`);if(e===null&&typeof e==`object`)return s();if(Array.isArray(e))return r(e,n);if(e?.constructor?.name===`Set`)return i(e,n);if(e?.constructor?.name===`Map`)return a(e,n);if(e?.constructor?.name===`Object`||!e.constructor&&typeof e==`object`)return o(e,n);if(p(e))return e.length===0&&n?.convertEmptyValues?s():c(e);if(typeof e==`boolean`||e?.constructor?.name===`Boolean`)return{BOOL:e.valueOf()};if(typeof e==`number`||e?.constructor?.name===`Number`)return f(e,n);if(e instanceof t)return e.toAttributeValue();if(typeof e==`bigint`)return u(e);if(typeof e==`string`||e?.constructor?.name===`String`)return e.length===0&&n?.convertEmptyValues?s():l(e);if(n?.convertClassInstanceToMap&&typeof e==`object`)return o(e,n);throw Error(`Unsupported type passed: ${e}. Pass options.convertClassInstanceToMap=true to marshall typeof object as map attribute.`)},r=(e,t)=>({L:e.filter(e=>typeof e!=`function`&&(!t?.removeUndefinedValues||t?.removeUndefinedValues&&e!==void 0)).map(e=>n(e,t))}),i=(e,n)=>{let r=n?.removeUndefinedValues?new Set([...e].filter(e=>e!==void 0)):e;if(!n?.removeUndefinedValues&&r.has(void 0))throw Error(`Pass options.removeUndefinedValues=true to remove undefined values from map/array/set.`);if(r.size===0){if(n?.convertEmptyValues)return s();throw Error(`Pass a non-empty set, or options.convertEmptyValues=true.`)}let i=r.values().next().value;if(i instanceof t)return{NS:Array.from(r).map(e=>e.toString())};if(typeof i==`number`)return{NS:Array.from(r).map(e=>f(e,n)).map(e=>e.N)};if(typeof i==`bigint`)return{NS:Array.from(r).map(u).map(e=>e.N)};if(typeof i==`string`)return{SS:Array.from(r).map(l).map(e=>e.S)};if(p(i))return{BS:Array.from(r).map(c).map(e=>e.B)};throw Error(`Only Number Set (NS), Binary Set (BS) or String Set (SS) are allowed.`)},a=(e,t)=>({M:(e=>{let r={};for(let[i,a]of e)typeof a!=`function`&&(a!==void 0||!t?.removeUndefinedValues)&&(r[i]=n(a,t));return r})(e)}),o=(e,t)=>({M:(e=>{let r={};for(let i in e){let a=e[i];typeof a!=`function`&&(a!==void 0||!t?.removeUndefinedValues)&&(r[i]=n(a,t))}return r})(e)}),s=()=>({NULL:!0}),c=e=>({B:e}),l=e=>({S:e.toString()}),u=e=>({N:e.toString()}),d=e=>{throw Error(`${e} Use NumberValue from @aws-sdk/lib-dynamodb.`)},f=(e,t)=>{if([NaN,1/0,-1/0].map(e=>e.toString()).includes(e.toString()))throw Error(`Special numeric value ${e.toString()} is not allowed`);return t?.allowImpreciseNumbers||(Number(e)>2**53-1?d(`Number ${e.toString()} is greater than Number.MAX_SAFE_INTEGER.`):Number(e)<-(2**53-1)&&d(`Number ${e.toString()} is lesser than Number.MIN_SAFE_INTEGER.`)),{N:e.toString()}},p=e=>e?.constructor?[`ArrayBuffer`,`Blob`,`Buffer`,`DataView`,`File`,`Int8Array`,`Uint8Array`,`Uint8ClampedArray`,`Int16Array`,`Uint16Array`,`Int32Array`,`Uint32Array`,`Float32Array`,`Float64Array`,`BigInt64Array`,`BigUint64Array`].includes(e.constructor.name):!1,m=(e,t)=>{for(let[n,r]of Object.entries(e))if(r!==void 0)switch(n){case`NULL`:return null;case`BOOL`:return!!r;case`N`:return h(r,t);case`B`:return _(r);case`S`:return g(r);case`L`:return v(r,t);case`M`:return y(r,t);case`NS`:return new Set(r.map(e=>h(e,t)));case`BS`:return new Set(r.map(_));case`SS`:return new Set(r.map(g));default:throw Error(`Unsupported type passed: ${n}`)}throw Error(`No value defined: ${JSON.stringify(e)}`)},h=(e,n)=>{if(typeof n?.wrapNumbers==`function`)return n?.wrapNumbers(e);if(n?.wrapNumbers)return t.from(e);let r=Number(e);if((r>2**53-1||r<-(2**53-1))&&![1/0,-1/0].includes(r))if(typeof BigInt==`function`)try{return BigInt(e)}catch{throw Error(`${e} can't be converted to BigInt. Set options.wrapNumbers to get string value.`)}else throw Error(`${e} is outside SAFE_INTEGER bounds. Set options.wrapNumbers to get string value.`);return r},g=e=>e,_=e=>e,v=(e,t)=>e.map(e=>m(e,t)),y=(e,t)=>Object.entries(e).reduce((e,[n,r])=>(e[n]=m(r,t),e),{});function b(e,t){let r=n(e,t),[i,a]=Object.entries(r)[0];switch(i){case`M`:case`L`:return t?.convertTopLevelContainer?r:a;default:return r}}e.marshall=b,e.unmarshall=(e,t)=>t?.convertWithoutMapWrapper?m(e,t):m({M:e},t)}))();const i=e=>{let{tableName:i,region:a}=e,o=new n.DynamoDBClient({region:a});function s(e){let{_pk:t,_sk:n,_table:r,...i}=e;return i}function c(e){let t=[],n={},r={};return e.forEach((e,i)=>{let a=`#w${i}`,o=`:w${i}`;r[a]=e.field;let s;switch(e.operator){case`ne`:n[o]=e.value,s=`${a} <> ${o}`;break;case`gt`:n[o]=e.value,s=`${a} > ${o}`;break;case`gte`:n[o]=e.value,s=`${a} >= ${o}`;break;case`lt`:n[o]=e.value,s=`${a} < ${o}`;break;case`lte`:n[o]=e.value,s=`${a} <= ${o}`;break;case`in`:case`not_in`:{let t=e.value,r=t.map((e,t)=>`:w${i}_${t}`);t.forEach((e,t)=>{n[`:w${i}_${t}`]=e});let o=`${a} IN (${r.join(`,`)})`;s=e.operator===`not_in`?`NOT ${o}`:o;break}case`contains`:n[o]=e.value,s=`contains(${a}, ${o})`;break;case`starts_with`:n[o]=e.value,s=`begins_with(${a}, ${o})`;break;case`ends_with`:n[o]=e.value,s=`contains(${a}, ${o})`;break;default:n[o]=e.value,s=`${a} = ${o}`;break}i>0&&t.push(e.connector===`OR`?`OR`:`AND`),t.push(s)}),{expression:t.join(` `),vals:n,names:r}}async function l(e,t){let a={"#_table":`_table`},l={":_table":e},u,d=a,f=l;if(t&&t.length>0){let e=c(t);u=e.expression,d={...a,...e.names},f={...l,...e.vals}}return((await o.send(new n.QueryCommand({TableName:i,IndexName:`_table-index`,KeyConditionExpression:`#_table = :_table`,FilterExpression:u,ExpressionAttributeNames:d,ExpressionAttributeValues:(0,r.marshall)(f)}))).Items||[]).map(e=>s((0,r.unmarshall)(e)))}return(0,t.createAdapterFactory)({config:{adapterId:`dynamodb`,adapterName:`DynamoDB Adapter`,supportsJSON:!1,supportsDates:!1,supportsBooleans:!1,supportsNumericIds:!1},adapter:()=>({create:async({model:e,data:t})=>{let a=t.id||crypto.randomUUID(),s={...t,id:a,_pk:`${e}#${a}`,_sk:`${e}#${a}`,_table:e};return await o.send(new n.PutItemCommand({TableName:i,Item:(0,r.marshall)(s,{removeUndefinedValues:!0})})),{...t,id:a}},findOne:async({model:e,where:t})=>{if(t.length===1&&t[0].field===`id`&&t[0].operator===`eq`){let a=t[0].value,c=await o.send(new n.GetItemCommand({TableName:i,Key:(0,r.marshall)({_pk:`${e}#${a}`,_sk:`${e}#${a}`})}));return c.Item?s((0,r.unmarshall)(c.Item)):null}return(await l(e,t))[0]||null},findMany:async({model:e,where:t,limit:n,sortBy:r,offset:i})=>{let a=await l(e,t);if(r){let e=r.direction===`desc`?-1:1;a.sort((t,n)=>t[r.field]<n[r.field]?-1*e:t[r.field]>n[r.field]?1*e:0)}return i&&(a=a.slice(i)),n&&(a=a.slice(0,n)),a},update:async({model:e,where:t,update:a})=>{let s=await l(e,t);if(s.length===0)return null;let c=s[0],u=c.id,d=Object.entries(a),f=[],p={},m={};return d.forEach(([e,t],n)=>{e===`id`||e.startsWith(`_`)||(f.push(`#u${n} = :u${n}`),m[`#u${n}`]=e,p[`:u${n}`]=t)}),f.length===0?c:(await o.send(new n.UpdateItemCommand({TableName:i,Key:(0,r.marshall)({_pk:`${e}#${u}`,_sk:`${e}#${u}`}),UpdateExpression:`SET ${f.join(`, `)}`,ExpressionAttributeNames:m,ExpressionAttributeValues:(0,r.marshall)(p,{removeUndefinedValues:!0})})),{...c,...a})},updateMany:async({model:e,where:t,update:a})=>{let s=await l(e,t),c=0;for(let t of s){let s=Object.entries(a),l=[],u={},d={};s.forEach(([e,t],n)=>{e===`id`||e.startsWith(`_`)||(l.push(`#u${n} = :u${n}`),d[`#u${n}`]=e,u[`:u${n}`]=t)}),l.length!==0&&(await o.send(new n.UpdateItemCommand({TableName:i,Key:(0,r.marshall)({_pk:`${e}#${t.id}`,_sk:`${e}#${t.id}`}),UpdateExpression:`SET ${l.join(`, `)}`,ExpressionAttributeNames:d,ExpressionAttributeValues:(0,r.marshall)(u,{removeUndefinedValues:!0})})),c++)}return c},delete:async({model:e,where:t})=>{let a=await l(e,t);a.length!==0&&await o.send(new n.DeleteItemCommand({TableName:i,Key:(0,r.marshall)({_pk:`${e}#${a[0].id}`,_sk:`${e}#${a[0].id}`})}))},deleteMany:async({model:e,where:t})=>{let a=await l(e,t);for(let t of a)await o.send(new n.DeleteItemCommand({TableName:i,Key:(0,r.marshall)({_pk:`${e}#${t.id}`,_sk:`${e}#${t.id}`})}));return a.length},count:async({model:e,where:t})=>(await l(e,t)).length})})};module.exports=i;
1
+ let e=require(`better-auth/adapters`),t=require(`@aws-sdk/client-dynamodb`),n=require(`@aws-sdk/util-dynamodb`);var r=class extends Error{constructor(e,t){super(e),this.operation=t,this.name=`DynamoDBAdapterError`}};const i=i=>{if(!i.tableName||i.tableName.trim().length===0)throw new r(`tableName must not be empty`,`initialization`);let{tableName:a,region:o}=i,s=new t.DynamoDBClient({region:o});function c(e){let{_pk:t,_sk:n,_table:r,...i}=e;return i}function l(e){if(!e||e.length===0)throw new r(`where conditions cannot be empty`,`buildFilter`);let t=[],n={},i={};return e.forEach((e,a)=>{let o=`#w${a}`,s=`:w${a}`;i[o]=e.field;let c;switch(e.operator){case`ne`:n[s]=e.value,c=`${o} <> ${s}`;break;case`gt`:n[s]=e.value,c=`${o} > ${s}`;break;case`gte`:n[s]=e.value,c=`${o} >= ${s}`;break;case`lt`:n[s]=e.value,c=`${o} < ${s}`;break;case`lte`:n[s]=e.value,c=`${o} <= ${s}`;break;case`in`:case`not_in`:{let t=e.value;if(!Array.isArray(t)||t.length===0)throw new r(`${e.operator} operator requires non-empty array`,`buildFilter`);let i=t.map((e,t)=>`:w${a}_${t}`);t.forEach((e,t)=>{n[`:w${a}_${t}`]=e});let s=`${o} IN (${i.join(`,`)})`;c=e.operator===`not_in`?`NOT ${s}`:s;break}case`contains`:n[s]=e.value,c=`contains(${o}, ${s})`;break;case`starts_with`:n[s]=e.value,c=`begins_with(${o}, ${s})`;break;case`ends_with`:n[s]=e.value,c=`contains(${o}, ${s})`;break;default:n[s]=e.value,c=`${o} = ${s}`;break}a>0&&t.push(e.connector===`OR`?`OR`:`AND`),t.push(c)}),{expression:t.join(` `),vals:n,names:i}}async function u(e,i){if(!e||e.trim().length===0)throw new r(`model must not be empty`,`query`);let o={"#_table":`_table`},u={":_table":e},d,f=o,p=u;if(i&&i.length>0){let e=l(i);d=e.expression,f={...o,...e.names},p={...u,...e.vals}}try{return((await s.send(new t.QueryCommand({TableName:a,IndexName:`_table-index`,KeyConditionExpression:`#_table = :_table`,FilterExpression:d,ExpressionAttributeNames:f,ExpressionAttributeValues:(0,n.marshall)(p)}))).Items||[]).map(e=>c((0,n.unmarshall)(e)))}catch(t){throw new r(`Failed to query model "${e}": ${t instanceof Error?t.message:String(t)}`,`query`)}}return(0,e.createAdapterFactory)({config:{adapterId:`dynamodb`,adapterName:`DynamoDB Adapter`,supportsJSON:!1,supportsDates:!1,supportsBooleans:!1,supportsNumericIds:!1},adapter:()=>({create:async({model:e,data:i})=>{if(!e||e.trim().length===0)throw new r(`model must not be empty`,`create`);if(!i||typeof i!=`object`)throw new r(`data must be a valid object`,`create`);try{let r=i.id||crypto.randomUUID(),o={...i,id:r,_pk:`${e}#${r}`,_sk:`${e}#${r}`,_table:e};return await s.send(new t.PutItemCommand({TableName:a,Item:(0,n.marshall)(o,{removeUndefinedValues:!0})})),{...i,id:r}}catch(e){throw new r(`Failed to create item: ${e instanceof Error?e.message:String(e)}`,`create`)}},findOne:async({model:e,where:i})=>{if(!e||e.trim().length===0)throw new r(`model must not be empty`,`findOne`);if(!i||i.length===0)throw new r(`where conditions are required`,`findOne`);try{if(i.length===1&&i[0].field===`id`&&i[0].operator===`eq`){let r=i[0].value,o=await s.send(new t.GetItemCommand({TableName:a,Key:(0,n.marshall)({_pk:`${e}#${r}`,_sk:`${e}#${r}`})}));return o.Item?c((0,n.unmarshall)(o.Item)):null}return(await u(e,i))[0]||null}catch(e){throw new r(`Failed to find item: ${e instanceof Error?e.message:String(e)}`,`findOne`)}},findMany:async({model:e,where:t,limit:n,sortBy:i,offset:a})=>{if(!e||e.trim().length===0)throw new r(`model must not be empty`,`findMany`);if(n<0)throw new r(`limit must be non-negative`,`findMany`);if(a!==void 0&&a<0)throw new r(`offset must be non-negative`,`findMany`);try{let r=await u(e,t);if(i){let e=i.direction===`desc`?-1:1;r.sort((t,n)=>{let r=t[i.field],a=n[i.field];return r==null||a==null?0:r<a?-1*e:r>a?1*e:0})}return a&&(r=r.slice(a)),n&&(r=r.slice(0,n)),r}catch(e){throw new r(`Failed to find items: ${e instanceof Error?e.message:String(e)}`,`findMany`)}},update:async({model:e,where:i,update:o})=>{if(!e||e.trim().length===0)throw new r(`model must not be empty`,`update`);if(!i||i.length===0)throw new r(`where conditions are required`,`update`);if(!o||typeof o!=`object`)throw new r(`update data must be a valid object`,`update`);try{let r=await u(e,i);if(r.length===0)return null;let c=r[0],l=c.id,d=Object.entries(o),f=[],p={},m={};return d.forEach(([e,t],n)=>{e===`id`||e.startsWith(`_`)||(f.push(`#u${n} = :u${n}`),m[`#u${n}`]=e,p[`:u${n}`]=t)}),f.length===0?c:(await s.send(new t.UpdateItemCommand({TableName:a,Key:(0,n.marshall)({_pk:`${e}#${l}`,_sk:`${e}#${l}`}),UpdateExpression:`SET ${f.join(`, `)}`,ExpressionAttributeNames:m,ExpressionAttributeValues:(0,n.marshall)(p,{removeUndefinedValues:!0})})),{...c,...o})}catch(e){throw new r(`Failed to update item: ${e instanceof Error?e.message:String(e)}`,`update`)}},updateMany:async({model:e,where:i,update:o})=>{if(!e||e.trim().length===0)throw new r(`model must not be empty`,`updateMany`);if(!i||i.length===0)throw new r(`where conditions are required`,`updateMany`);try{let r=await u(e,i),c=0;for(let i of r){let r=Object.entries(o),l=[],u={},d={};r.forEach(([e,t],n)=>{e===`id`||e.startsWith(`_`)||(l.push(`#u${n} = :u${n}`),d[`#u${n}`]=e,u[`:u${n}`]=t)}),l.length!==0&&(await s.send(new t.UpdateItemCommand({TableName:a,Key:(0,n.marshall)({_pk:`${e}#${i.id}`,_sk:`${e}#${i.id}`}),UpdateExpression:`SET ${l.join(`, `)}`,ExpressionAttributeNames:d,ExpressionAttributeValues:(0,n.marshall)(u,{removeUndefinedValues:!0})})),c++)}return c}catch(e){throw new r(`Failed to update items: ${e instanceof Error?e.message:String(e)}`,`updateMany`)}},delete:async({model:e,where:i})=>{if(!e||e.trim().length===0)throw new r(`model must not be empty`,`delete`);if(!i||i.length===0)throw new r(`where conditions are required`,`delete`);try{let r=await u(e,i);if(r.length===0)return;await s.send(new t.DeleteItemCommand({TableName:a,Key:(0,n.marshall)({_pk:`${e}#${r[0].id}`,_sk:`${e}#${r[0].id}`})}))}catch(e){throw new r(`Failed to delete item: ${e instanceof Error?e.message:String(e)}`,`delete`)}},deleteMany:async({model:e,where:i})=>{if(!e||e.trim().length===0)throw new r(`model must not be empty`,`deleteMany`);if(!i||i.length===0)throw new r(`where conditions are required`,`deleteMany`);try{let r=await u(e,i);for(let i of r)await s.send(new t.DeleteItemCommand({TableName:a,Key:(0,n.marshall)({_pk:`${e}#${i.id}`,_sk:`${e}#${i.id}`})}));return r.length}catch(e){throw new r(`Failed to delete items: ${e instanceof Error?e.message:String(e)}`,`deleteMany`)}},count:async({model:e,where:t})=>{if(!e||e.trim().length===0)throw new r(`model must not be empty`,`count`);try{return(await u(e,t)).length}catch(e){throw new r(`Failed to count items: ${e instanceof Error?e.message:String(e)}`,`count`)}}})})};module.exports=i;
@@ -0,0 +1,23 @@
1
+ type AWSRegion = "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2" | "eu-west-1" | "eu-west-2" | "eu-west-3" | "eu-central-1" | "eu-north-1" | "ap-northeast-1" | "ap-northeast-2" | "ap-northeast-3" | "ap-southeast-1" | "ap-southeast-2" | "ap-south-1" | "ca-central-1" | "sa-east-1" | "me-south-1" | "af-south-1" | "ap-east-1";
2
+ interface DynamoDBAdapterConfig {
3
+ tableName: string;
4
+ region: AWSRegion;
5
+ }
6
+ /**
7
+ * DynamoDB adapter for better-auth
8
+ *
9
+ * @param config - The configuration for the adapter
10
+ * @param config.tableName - The DynamoDB table name (must not be empty)
11
+ * @param config.region - The AWS region (must be a valid AWS region)
12
+ * @returns The adapter factory with CRUD operations
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const adapter = dynamoDBAdapter({
17
+ * tableName: "my-auth-table",
18
+ * region: "us-east-1",
19
+ * });
20
+ * ```
21
+ */
22
+ declare const DynamoDBAdapter: (config: DynamoDBAdapterConfig) => import("better-auth/adapters").AdapterFactory<import("@better-auth/core").BetterAuthOptions>;
23
+ export default DynamoDBAdapter;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{createAdapterFactory as e}from"better-auth/adapters";import{DeleteItemCommand as t,DynamoDBClient as n,GetItemCommand as r,PutItemCommand as i,QueryCommand as a,UpdateItemCommand as o}from"@aws-sdk/client-dynamodb";var s=((e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports))((e=>{var t=class e{value;constructor(e){typeof e==`object`&&`N`in e?this.value=String(e.N):this.value=String(e);let t=typeof e.valueOf()==`number`?e.valueOf():0;if(t>2**53-1||t<-(2**53-1)||Math.abs(t)===1/0||Number.isNaN(t))throw Error(`NumberValue should not be initialized with an imprecise number=${t}. Use a string instead.`)}static from(t){return new e(t)}toAttributeValue(){return{N:this.toString()}}toBigInt(){let e=this.toString();return BigInt(e)}toString(){return String(this.value)}valueOf(){return this.toString()}};let n=(e,n)=>{if(e===void 0)throw Error(`Pass options.removeUndefinedValues=true to remove undefined values from map/array/set.`);if(e===null&&typeof e==`object`)return s();if(Array.isArray(e))return r(e,n);if(e?.constructor?.name===`Set`)return i(e,n);if(e?.constructor?.name===`Map`)return a(e,n);if(e?.constructor?.name===`Object`||!e.constructor&&typeof e==`object`)return o(e,n);if(p(e))return e.length===0&&n?.convertEmptyValues?s():c(e);if(typeof e==`boolean`||e?.constructor?.name===`Boolean`)return{BOOL:e.valueOf()};if(typeof e==`number`||e?.constructor?.name===`Number`)return f(e,n);if(e instanceof t)return e.toAttributeValue();if(typeof e==`bigint`)return u(e);if(typeof e==`string`||e?.constructor?.name===`String`)return e.length===0&&n?.convertEmptyValues?s():l(e);if(n?.convertClassInstanceToMap&&typeof e==`object`)return o(e,n);throw Error(`Unsupported type passed: ${e}. Pass options.convertClassInstanceToMap=true to marshall typeof object as map attribute.`)},r=(e,t)=>({L:e.filter(e=>typeof e!=`function`&&(!t?.removeUndefinedValues||t?.removeUndefinedValues&&e!==void 0)).map(e=>n(e,t))}),i=(e,n)=>{let r=n?.removeUndefinedValues?new Set([...e].filter(e=>e!==void 0)):e;if(!n?.removeUndefinedValues&&r.has(void 0))throw Error(`Pass options.removeUndefinedValues=true to remove undefined values from map/array/set.`);if(r.size===0){if(n?.convertEmptyValues)return s();throw Error(`Pass a non-empty set, or options.convertEmptyValues=true.`)}let i=r.values().next().value;if(i instanceof t)return{NS:Array.from(r).map(e=>e.toString())};if(typeof i==`number`)return{NS:Array.from(r).map(e=>f(e,n)).map(e=>e.N)};if(typeof i==`bigint`)return{NS:Array.from(r).map(u).map(e=>e.N)};if(typeof i==`string`)return{SS:Array.from(r).map(l).map(e=>e.S)};if(p(i))return{BS:Array.from(r).map(c).map(e=>e.B)};throw Error(`Only Number Set (NS), Binary Set (BS) or String Set (SS) are allowed.`)},a=(e,t)=>({M:(e=>{let r={};for(let[i,a]of e)typeof a!=`function`&&(a!==void 0||!t?.removeUndefinedValues)&&(r[i]=n(a,t));return r})(e)}),o=(e,t)=>({M:(e=>{let r={};for(let i in e){let a=e[i];typeof a!=`function`&&(a!==void 0||!t?.removeUndefinedValues)&&(r[i]=n(a,t))}return r})(e)}),s=()=>({NULL:!0}),c=e=>({B:e}),l=e=>({S:e.toString()}),u=e=>({N:e.toString()}),d=e=>{throw Error(`${e} Use NumberValue from @aws-sdk/lib-dynamodb.`)},f=(e,t)=>{if([NaN,1/0,-1/0].map(e=>e.toString()).includes(e.toString()))throw Error(`Special numeric value ${e.toString()} is not allowed`);return t?.allowImpreciseNumbers||(Number(e)>2**53-1?d(`Number ${e.toString()} is greater than Number.MAX_SAFE_INTEGER.`):Number(e)<-(2**53-1)&&d(`Number ${e.toString()} is lesser than Number.MIN_SAFE_INTEGER.`)),{N:e.toString()}},p=e=>e?.constructor?[`ArrayBuffer`,`Blob`,`Buffer`,`DataView`,`File`,`Int8Array`,`Uint8Array`,`Uint8ClampedArray`,`Int16Array`,`Uint16Array`,`Int32Array`,`Uint32Array`,`Float32Array`,`Float64Array`,`BigInt64Array`,`BigUint64Array`].includes(e.constructor.name):!1,m=(e,t)=>{for(let[n,r]of Object.entries(e))if(r!==void 0)switch(n){case`NULL`:return null;case`BOOL`:return!!r;case`N`:return h(r,t);case`B`:return _(r);case`S`:return g(r);case`L`:return v(r,t);case`M`:return y(r,t);case`NS`:return new Set(r.map(e=>h(e,t)));case`BS`:return new Set(r.map(_));case`SS`:return new Set(r.map(g));default:throw Error(`Unsupported type passed: ${n}`)}throw Error(`No value defined: ${JSON.stringify(e)}`)},h=(e,n)=>{if(typeof n?.wrapNumbers==`function`)return n?.wrapNumbers(e);if(n?.wrapNumbers)return t.from(e);let r=Number(e);if((r>2**53-1||r<-(2**53-1))&&![1/0,-1/0].includes(r))if(typeof BigInt==`function`)try{return BigInt(e)}catch{throw Error(`${e} can't be converted to BigInt. Set options.wrapNumbers to get string value.`)}else throw Error(`${e} is outside SAFE_INTEGER bounds. Set options.wrapNumbers to get string value.`);return r},g=e=>e,_=e=>e,v=(e,t)=>e.map(e=>m(e,t)),y=(e,t)=>Object.entries(e).reduce((e,[n,r])=>(e[n]=m(r,t),e),{});function b(e,t){let r=n(e,t),[i,a]=Object.entries(r)[0];switch(i){case`M`:case`L`:return t?.convertTopLevelContainer?r:a;default:return r}}e.marshall=b,e.unmarshall=(e,t)=>t?.convertWithoutMapWrapper?m(e,t):m({M:e},t)}))();const c=c=>{let{tableName:l,region:u}=c,d=new n({region:u});function f(e){let{_pk:t,_sk:n,_table:r,...i}=e;return i}function p(e){let t=[],n={},r={};return e.forEach((e,i)=>{let a=`#w${i}`,o=`:w${i}`;r[a]=e.field;let s;switch(e.operator){case`ne`:n[o]=e.value,s=`${a} <> ${o}`;break;case`gt`:n[o]=e.value,s=`${a} > ${o}`;break;case`gte`:n[o]=e.value,s=`${a} >= ${o}`;break;case`lt`:n[o]=e.value,s=`${a} < ${o}`;break;case`lte`:n[o]=e.value,s=`${a} <= ${o}`;break;case`in`:case`not_in`:{let t=e.value,r=t.map((e,t)=>`:w${i}_${t}`);t.forEach((e,t)=>{n[`:w${i}_${t}`]=e});let o=`${a} IN (${r.join(`,`)})`;s=e.operator===`not_in`?`NOT ${o}`:o;break}case`contains`:n[o]=e.value,s=`contains(${a}, ${o})`;break;case`starts_with`:n[o]=e.value,s=`begins_with(${a}, ${o})`;break;case`ends_with`:n[o]=e.value,s=`contains(${a}, ${o})`;break;default:n[o]=e.value,s=`${a} = ${o}`;break}i>0&&t.push(e.connector===`OR`?`OR`:`AND`),t.push(s)}),{expression:t.join(` `),vals:n,names:r}}async function m(e,t){let n={"#_table":`_table`},r={":_table":e},i,o=n,c=r;if(t&&t.length>0){let e=p(t);i=e.expression,o={...n,...e.names},c={...r,...e.vals}}return((await d.send(new a({TableName:l,IndexName:`_table-index`,KeyConditionExpression:`#_table = :_table`,FilterExpression:i,ExpressionAttributeNames:o,ExpressionAttributeValues:(0,s.marshall)(c)}))).Items||[]).map(e=>f((0,s.unmarshall)(e)))}return e({config:{adapterId:`dynamodb`,adapterName:`DynamoDB Adapter`,supportsJSON:!1,supportsDates:!1,supportsBooleans:!1,supportsNumericIds:!1},adapter:()=>({create:async({model:e,data:t})=>{let n=t.id||crypto.randomUUID(),r={...t,id:n,_pk:`${e}#${n}`,_sk:`${e}#${n}`,_table:e};return await d.send(new i({TableName:l,Item:(0,s.marshall)(r,{removeUndefinedValues:!0})})),{...t,id:n}},findOne:async({model:e,where:t})=>{if(t.length===1&&t[0].field===`id`&&t[0].operator===`eq`){let n=t[0].value,i=await d.send(new r({TableName:l,Key:(0,s.marshall)({_pk:`${e}#${n}`,_sk:`${e}#${n}`})}));return i.Item?f((0,s.unmarshall)(i.Item)):null}return(await m(e,t))[0]||null},findMany:async({model:e,where:t,limit:n,sortBy:r,offset:i})=>{let a=await m(e,t);if(r){let e=r.direction===`desc`?-1:1;a.sort((t,n)=>t[r.field]<n[r.field]?-1*e:t[r.field]>n[r.field]?1*e:0)}return i&&(a=a.slice(i)),n&&(a=a.slice(0,n)),a},update:async({model:e,where:t,update:n})=>{let r=await m(e,t);if(r.length===0)return null;let i=r[0],a=i.id,c=Object.entries(n),u=[],f={},p={};return c.forEach(([e,t],n)=>{e===`id`||e.startsWith(`_`)||(u.push(`#u${n} = :u${n}`),p[`#u${n}`]=e,f[`:u${n}`]=t)}),u.length===0?i:(await d.send(new o({TableName:l,Key:(0,s.marshall)({_pk:`${e}#${a}`,_sk:`${e}#${a}`}),UpdateExpression:`SET ${u.join(`, `)}`,ExpressionAttributeNames:p,ExpressionAttributeValues:(0,s.marshall)(f,{removeUndefinedValues:!0})})),{...i,...n})},updateMany:async({model:e,where:t,update:n})=>{let r=await m(e,t),i=0;for(let t of r){let r=Object.entries(n),a=[],c={},u={};r.forEach(([e,t],n)=>{e===`id`||e.startsWith(`_`)||(a.push(`#u${n} = :u${n}`),u[`#u${n}`]=e,c[`:u${n}`]=t)}),a.length!==0&&(await d.send(new o({TableName:l,Key:(0,s.marshall)({_pk:`${e}#${t.id}`,_sk:`${e}#${t.id}`}),UpdateExpression:`SET ${a.join(`, `)}`,ExpressionAttributeNames:u,ExpressionAttributeValues:(0,s.marshall)(c,{removeUndefinedValues:!0})})),i++)}return i},delete:async({model:e,where:n})=>{let r=await m(e,n);r.length!==0&&await d.send(new t({TableName:l,Key:(0,s.marshall)({_pk:`${e}#${r[0].id}`,_sk:`${e}#${r[0].id}`})}))},deleteMany:async({model:e,where:n})=>{let r=await m(e,n);for(let n of r)await d.send(new t({TableName:l,Key:(0,s.marshall)({_pk:`${e}#${n.id}`,_sk:`${e}#${n.id}`})}));return r.length},count:async({model:e,where:t})=>(await m(e,t)).length})})};export{c as default};
1
+ import{createAdapterFactory as e}from"better-auth/adapters";import{DeleteItemCommand as t,DynamoDBClient as n,GetItemCommand as r,PutItemCommand as i,QueryCommand as a,UpdateItemCommand as o}from"@aws-sdk/client-dynamodb";import{marshall as s,unmarshall as c}from"@aws-sdk/util-dynamodb";var l=class extends Error{constructor(e,t){super(e),this.operation=t,this.name=`DynamoDBAdapterError`}};const u=u=>{if(!u.tableName||u.tableName.trim().length===0)throw new l(`tableName must not be empty`,`initialization`);let{tableName:d,region:f}=u,p=new n({region:f});function m(e){let{_pk:t,_sk:n,_table:r,...i}=e;return i}function h(e){if(!e||e.length===0)throw new l(`where conditions cannot be empty`,`buildFilter`);let t=[],n={},r={};return e.forEach((e,i)=>{let a=`#w${i}`,o=`:w${i}`;r[a]=e.field;let s;switch(e.operator){case`ne`:n[o]=e.value,s=`${a} <> ${o}`;break;case`gt`:n[o]=e.value,s=`${a} > ${o}`;break;case`gte`:n[o]=e.value,s=`${a} >= ${o}`;break;case`lt`:n[o]=e.value,s=`${a} < ${o}`;break;case`lte`:n[o]=e.value,s=`${a} <= ${o}`;break;case`in`:case`not_in`:{let t=e.value;if(!Array.isArray(t)||t.length===0)throw new l(`${e.operator} operator requires non-empty array`,`buildFilter`);let r=t.map((e,t)=>`:w${i}_${t}`);t.forEach((e,t)=>{n[`:w${i}_${t}`]=e});let o=`${a} IN (${r.join(`,`)})`;s=e.operator===`not_in`?`NOT ${o}`:o;break}case`contains`:n[o]=e.value,s=`contains(${a}, ${o})`;break;case`starts_with`:n[o]=e.value,s=`begins_with(${a}, ${o})`;break;case`ends_with`:n[o]=e.value,s=`contains(${a}, ${o})`;break;default:n[o]=e.value,s=`${a} = ${o}`;break}i>0&&t.push(e.connector===`OR`?`OR`:`AND`),t.push(s)}),{expression:t.join(` `),vals:n,names:r}}async function g(e,t){if(!e||e.trim().length===0)throw new l(`model must not be empty`,`query`);let n={"#_table":`_table`},r={":_table":e},i,o=n,u=r;if(t&&t.length>0){let e=h(t);i=e.expression,o={...n,...e.names},u={...r,...e.vals}}try{return((await p.send(new a({TableName:d,IndexName:`_table-index`,KeyConditionExpression:`#_table = :_table`,FilterExpression:i,ExpressionAttributeNames:o,ExpressionAttributeValues:s(u)}))).Items||[]).map(e=>m(c(e)))}catch(t){throw new l(`Failed to query model "${e}": ${t instanceof Error?t.message:String(t)}`,`query`)}}return e({config:{adapterId:`dynamodb`,adapterName:`DynamoDB Adapter`,supportsJSON:!1,supportsDates:!1,supportsBooleans:!1,supportsNumericIds:!1},adapter:()=>({create:async({model:e,data:t})=>{if(!e||e.trim().length===0)throw new l(`model must not be empty`,`create`);if(!t||typeof t!=`object`)throw new l(`data must be a valid object`,`create`);try{let n=t.id||crypto.randomUUID(),r={...t,id:n,_pk:`${e}#${n}`,_sk:`${e}#${n}`,_table:e};return await p.send(new i({TableName:d,Item:s(r,{removeUndefinedValues:!0})})),{...t,id:n}}catch(e){throw new l(`Failed to create item: ${e instanceof Error?e.message:String(e)}`,`create`)}},findOne:async({model:e,where:t})=>{if(!e||e.trim().length===0)throw new l(`model must not be empty`,`findOne`);if(!t||t.length===0)throw new l(`where conditions are required`,`findOne`);try{if(t.length===1&&t[0].field===`id`&&t[0].operator===`eq`){let n=t[0].value,i=await p.send(new r({TableName:d,Key:s({_pk:`${e}#${n}`,_sk:`${e}#${n}`})}));return i.Item?m(c(i.Item)):null}return(await g(e,t))[0]||null}catch(e){throw new l(`Failed to find item: ${e instanceof Error?e.message:String(e)}`,`findOne`)}},findMany:async({model:e,where:t,limit:n,sortBy:r,offset:i})=>{if(!e||e.trim().length===0)throw new l(`model must not be empty`,`findMany`);if(n<0)throw new l(`limit must be non-negative`,`findMany`);if(i!==void 0&&i<0)throw new l(`offset must be non-negative`,`findMany`);try{let a=await g(e,t);if(r){let e=r.direction===`desc`?-1:1;a.sort((t,n)=>{let i=t[r.field],a=n[r.field];return i==null||a==null?0:i<a?-1*e:i>a?1*e:0})}return i&&(a=a.slice(i)),n&&(a=a.slice(0,n)),a}catch(e){throw new l(`Failed to find items: ${e instanceof Error?e.message:String(e)}`,`findMany`)}},update:async({model:e,where:t,update:n})=>{if(!e||e.trim().length===0)throw new l(`model must not be empty`,`update`);if(!t||t.length===0)throw new l(`where conditions are required`,`update`);if(!n||typeof n!=`object`)throw new l(`update data must be a valid object`,`update`);try{let r=await g(e,t);if(r.length===0)return null;let i=r[0],a=i.id,c=Object.entries(n),l=[],u={},f={};return c.forEach(([e,t],n)=>{e===`id`||e.startsWith(`_`)||(l.push(`#u${n} = :u${n}`),f[`#u${n}`]=e,u[`:u${n}`]=t)}),l.length===0?i:(await p.send(new o({TableName:d,Key:s({_pk:`${e}#${a}`,_sk:`${e}#${a}`}),UpdateExpression:`SET ${l.join(`, `)}`,ExpressionAttributeNames:f,ExpressionAttributeValues:s(u,{removeUndefinedValues:!0})})),{...i,...n})}catch(e){throw new l(`Failed to update item: ${e instanceof Error?e.message:String(e)}`,`update`)}},updateMany:async({model:e,where:t,update:n})=>{if(!e||e.trim().length===0)throw new l(`model must not be empty`,`updateMany`);if(!t||t.length===0)throw new l(`where conditions are required`,`updateMany`);try{let r=await g(e,t),i=0;for(let t of r){let r=Object.entries(n),a=[],c={},l={};r.forEach(([e,t],n)=>{e===`id`||e.startsWith(`_`)||(a.push(`#u${n} = :u${n}`),l[`#u${n}`]=e,c[`:u${n}`]=t)}),a.length!==0&&(await p.send(new o({TableName:d,Key:s({_pk:`${e}#${t.id}`,_sk:`${e}#${t.id}`}),UpdateExpression:`SET ${a.join(`, `)}`,ExpressionAttributeNames:l,ExpressionAttributeValues:s(c,{removeUndefinedValues:!0})})),i++)}return i}catch(e){throw new l(`Failed to update items: ${e instanceof Error?e.message:String(e)}`,`updateMany`)}},delete:async({model:e,where:n})=>{if(!e||e.trim().length===0)throw new l(`model must not be empty`,`delete`);if(!n||n.length===0)throw new l(`where conditions are required`,`delete`);try{let r=await g(e,n);if(r.length===0)return;await p.send(new t({TableName:d,Key:s({_pk:`${e}#${r[0].id}`,_sk:`${e}#${r[0].id}`})}))}catch(e){throw new l(`Failed to delete item: ${e instanceof Error?e.message:String(e)}`,`delete`)}},deleteMany:async({model:e,where:n})=>{if(!e||e.trim().length===0)throw new l(`model must not be empty`,`deleteMany`);if(!n||n.length===0)throw new l(`where conditions are required`,`deleteMany`);try{let r=await g(e,n);for(let n of r)await p.send(new t({TableName:d,Key:s({_pk:`${e}#${n.id}`,_sk:`${e}#${n.id}`})}));return r.length}catch(e){throw new l(`Failed to delete items: ${e instanceof Error?e.message:String(e)}`,`deleteMany`)}},count:async({model:e,where:t})=>{if(!e||e.trim().length===0)throw new l(`model must not be empty`,`count`);try{return(await g(e,t)).length}catch(e){throw new l(`Failed to count items: ${e instanceof Error?e.message:String(e)}`,`count`)}}})})};export{u as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "betterauth-dynamodb-adapter",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "DynamoDB adapter for Better Auth — single-table design with full CRUD, filtering, and sorting support",
5
5
  "author": "rokku-x",
6
6
  "type": "module",
@@ -15,15 +15,15 @@
15
15
  "homepage": "https://github.com/rokku-x/betterauth-dynamodb-adapter#readme",
16
16
  "main": "dist/index.cjs",
17
17
  "module": "dist/index.mjs",
18
- "types": "dist/index.d.mts",
18
+ "types": "dist/index.d.ts",
19
19
  "exports": {
20
20
  ".": {
21
21
  "import": {
22
- "types": "./dist/index.d.mts",
22
+ "types": "./dist/index.d.ts",
23
23
  "default": "./dist/index.mjs"
24
24
  },
25
25
  "require": {
26
- "types": "./dist/index.d.cts",
26
+ "types": "./dist/index.d.ts",
27
27
  "default": "./dist/index.cjs"
28
28
  }
29
29
  }
@@ -46,23 +46,6 @@
46
46
  "database",
47
47
  "typescript"
48
48
  ],
49
- "scripts": {
50
- "build": "tsdown",
51
- "dev": "tsdown --watch",
52
- "test": "vitest run",
53
- "test:watch": "vitest",
54
- "typecheck": "tsc --noEmit",
55
- "prepublishOnly": "npm run build",
56
- "changeset": "changeset",
57
- "version:changeset": "changeset version",
58
- "publish:changeset": "changeset publish"
59
- },
60
- "devDependencies": {
61
- "@changesets/cli": "^2.30.0",
62
- "tsdown": "^0.21.7",
63
- "typescript": "^5.0.0",
64
- "vitest": "^1.0.0"
65
- },
66
49
  "peerDependencies": {
67
50
  "better-auth": ">=1.0.0",
68
51
  "@aws-sdk/client-dynamodb": ">=3.0.0",
@@ -76,4 +59,4 @@
76
59
  "optional": false
77
60
  }
78
61
  }
79
- }
62
+ }