UNPKG

@eggermarc/better-auth-usage

Version:

**⚠️ Warning!** This package is a **work in progress**! Expect breaking changes and functionality changes.

2 lines (1 loc) 11 kB
import{APIError as j,createAuthEndpoint as D}from"better-auth/api";function U({maxLimit:t,minLimit:s,value:e}){return t&&e>t?"above-max-limit":s&&e<s?"below-min-limit":"in-limit"}function b(t,s){let e=new Date;if(s==="never")return{shouldReset:!1};let o=w(e,s);for(;o<=e;)o=w(o,s);return!t||t<o?{shouldReset:!0,nextReset:o}:{shouldReset:!1,nextReset:o}}function w(t,s){let e=new Date(t);switch(s){case"hourly":e.setHours(e.getHours()+1,0,0,0);break;case"6-hourly":{let o=t.getHours(),r=Math.floor(o/6)*6+6;r>=24?(e.setDate(e.getDate()+1),e.setHours(0,0,0,0)):e.setHours(r,0,0,0);break}case"daily":e.setDate(e.getDate()+1),e.setHours(0,0,0,0);break;case"weekly":{let r=(8-t.getDay())%7||7;e.setDate(e.getDate()+r),e.setHours(0,0,0,0);break}case"monthly":e.setMonth(e.getMonth()+1,1),e.setHours(0,0,0,0);break;case"quarterly":{let o=t.getMonth(),r=Math.floor(o/3)*3+3;r>=12?(e.setFullYear(e.getFullYear()+1),e.setMonth(0,1)):e.setMonth(r,1),e.setHours(0,0,0,0);break}case"yearly":e.setFullYear(e.getFullYear()+1,0,1),e.setHours(0,0,0,0);break;case"never":return t}return e}var d=t=>{let s=t.adapter;return{findLatestUsage:async({referenceId:e,featureKey:o,event:r})=>{let n=r?[{field:"referenceId",value:e},{field:"feature",value:o},{field:"event",value:r}]:[{field:"referenceId",value:e},{field:"feature",value:o}];return(await s.findMany({model:"usage",where:n,sortBy:{field:"createdAt",direction:"desc"}}))[0]},insertUsage:async({amount:e,referenceId:o,referenceType:r,event:n,feature:i})=>await s.transaction(async u=>{let m=await u.findMany({model:"usage",where:[{field:"referenceId",value:o},{field:"feature",value:i.key}],sortBy:{field:"createdAt",direction:"desc"},limit:1}),q=m[0],h=b(q?.lastResetAt??null,i.reset??"never");return h.shouldReset&&h.nextReset?await u.create({model:"usage",data:{referenceId:o,referenceType:r,event:n,amount:e,feature:i.key,lastResetAt:h.nextReset,afterAmount:e+(i.resetValue??0),createdAt:new Date}}):await u.create({model:"usage",data:{referenceId:o,referenceType:r,event:n,amount:e,lastResetAt:m[0].lastResetAt,feature:i.key,afterAmount:e+(m[0].afterAmount??0),createdAt:new Date}})}),syncUsage:async({referenceId:e,referenceType:o,feature:r})=>await s.transaction(async i=>{let c=await i.findMany({model:"usage",where:[{field:"referenceId",value:e}],sortBy:{field:"createdAt",direction:"desc"},limit:1}),u=b(c[0].lastResetAt,r.reset??"never");if(u.shouldReset&&u.nextReset)return await i.create({model:"usage",data:{referenceId:e,referenceType:o,event:"reset",amount:0,feature:r.key,afterAmount:r.resetValue??0,lastResetAt:u.nextReset,createdAt:new Date}})}),getCustomer:async({referenceId:e})=>await s.findOne({model:"customer",where:[{field:"referenceId",value:e}]}),upsertCustomer:async e=>await s.transaction(async r=>await r.findOne({model:"customer",where:[{field:"referenceId",value:e.referenceId}]})?await r.update({model:"customer",where:[{field:"referenceId",value:e.referenceId}],update:e}):await r.create({model:"customer",data:e}))}};import{APIError as E}from"better-auth/api";function p({featureKey:t,overrideKey:s,features:e,overrides:o}){let r=e[t];if(!r)throw new E("NOT_FOUND",{message:`Feature ${t} not found`});if(s&&o?.[s]){let i=o[s]?.features?.[t];i&&(r={...r,...i})}return r}async function v({adapter:t,feature:s,customer:e}){if(s.reset)return await t.syncUsage({referenceId:e.referenceId,referenceType:e.referenceType,feature:{key:s.key,resetValue:s.resetValue,reset:s.reset}})}import{z as y}from"zod";function A({features:t,overrides:s}){return D("/usage/sync",{method:"POST",body:y.object({referenceId:y.string(),featureKey:y.string(),overrideKey:y.string().optional()}),metadata:{openapi:{description:"Syncs customer usage based on reset rules (inserts a reset row if due).",requestBody:{required:!0,content:{"application/json":{schema:{type:"object",properties:{referenceId:{type:"string"},featureKey:{type:"string"}},required:["referenceId","featureKey"]}}}},responses:{200:{description:"Reset inserted or not needed"},404:{description:"Customer or feature not found"}}}}},async e=>{let o=d(e.context),r=await o.getCustomer({referenceId:e.body.referenceId});if(!r)throw new j("NOT_FOUND",{message:`Customer ${e.body.referenceId} not found`});let n=p({featureKey:e.body.featureKey,overrideKey:e.body.overrideKey,features:t,overrides:s});return await v({adapter:o,feature:n,customer:r})})}import{APIError as I,createAuthEndpoint as L,sessionMiddleware as S}from"better-auth/api";import{z as l}from"zod";import{APIError as x,createAuthMiddleware as M}from"better-auth/api";function g({features:t,overrides:s}){return M(async e=>{if(e.body?.referenceId&&e.body?.featureKey){let o=p({featureKey:e.body.featureKey,overrideKey:e.body.overrideKey,features:t,overrides:s});if(!(await o.authorizeReference?.({...e.body})??!0))throw new x("UNAUTHORIZED",{message:`Customer unauthorized by feature ${o.key}`})}})}function K({features:t,overrides:s}){return L("/usage/check",{method:"POST",middleware:[S,g],body:l.object({referenceId:l.string(),featureKey:l.string(),overrideKey:l.string().optional()}),metadata:{openapi:{description:"Checks current usage against feature limits.",requestBody:{required:!0,content:{"application/json":{schema:{type:"object",properties:{referenceId:{type:"string"},featureKey:{type:"string"},overrideKey:{type:"string"}},required:["referenceId","featureKey"]}}}},responses:{200:{description:"Status string"}}}}},async e=>{let o=d(e.context);if(!await o.getCustomer({referenceId:e.body.referenceId}))throw new I("NOT_FOUND",{message:`Customer ${e.body.referenceId} not found`});let n=p({featureKey:e.body.featureKey,overrideKey:e.body.overrideKey,features:t,overrides:s});if(!n)throw new I("NOT_FOUND",{message:"Feature not found"});let i=await o.findLatestUsage({referenceId:e.body.referenceId,featureKey:n.key});return U({minLimit:n.minLimit,maxLimit:n.maxLimit,value:i?.afterAmount??0})})}import{createAuthEndpoint as N}from"better-auth/api";import{z as k}from"zod";function C({features:t,overrides:s}){return N("/usage/features/:featureKey",{method:"GET",body:k.object({overrideKey:k.string().optional()}),metadata:{openapi:{description:"Returns the feature configuration (merged with overrides if provided).",parameters:[{in:"path",name:"featureKey",required:!0,schema:{type:"string"},description:"The key of the feature to retrieve"}],requestBody:{required:!0,content:{"application/json":{schema:{type:"object",properties:{overrideKey:{type:"string"}}}}}},responses:{200:{description:"Feature object",content:{"application/json":{schema:{type:"object"}}}},404:{description:"Feature not found"}}}}},async e=>{let r={...p({featureKey:e.params.featureKey,overrideKey:e.body.overrideKey,features:t,overrides:s})};return delete r.hooks,{feature:r}})}import{createAuthEndpoint as P}from"better-auth/api";function O({features:t}){return P("/usage/features",{method:"GET",metadata:{openapi:{description:"Lists registered features.",responses:{200:{description:"List of registered features.",content:{"application/json":{schema:{type:"array",items:{type:"object",properties:{featureKey:{type:"string"},details:{type:"array",items:{type:"string"}}},required:["featureKey"]}}}}}}}}},async()=>Object.values(t).map(s=>({featureKey:s.key,details:s.details})))}import{APIError as z,createAuthEndpoint as B,sessionMiddleware as H}from"better-auth/api";import{z as f}from"zod";function R({features:t,overrides:s}){return B("/usage/consume",{method:"POST",middleware:[H,g({features:t,overrides:s})],body:f.object({featureKey:f.string(),overrideKey:f.string().optional(),amount:f.number(),referenceId:f.string(),event:f.string().default("use")}),metadata:{openapi:{description:"Consume a feature (meter usage).",requestBody:{required:!0,content:{"application/json":{schema:{type:"object",properties:{featureKey:{type:"string",description:"Feature Key"},overrideKey:{type:"string",description:"Overriding Key for consumption limits"},amount:{type:"number",description:"Amount to be consumed"},referenceId:{type:"string",description:"Reference ID of the customer"},event:{type:"string",description:"(Optional) Event tag of the consumption"}},required:["featureKey","amount","referenceId"]}}}},responses:{200:{description:"Usage row inserted",content:{"application/json":{schema:{type:"object"}}}},404:{description:"Customer or feature not found"},401:{description:"Unauthorized"}}}}},async e=>{let o=d(e.context),r=await o.getCustomer({referenceId:e.body.referenceId});if(!r)throw new z("NOT_FOUND",{message:`Customer ${e.body.referenceId} not found`});let n=p({featureKey:e.body.featureKey,overrideKey:e.body.overrideKey,features:t,overrides:s}),c=(await o.findLatestUsage({referenceId:r.referenceId,featureKey:n.key}))?.afterAmount??0,u=c+e.body.amount;n.hooks?.before&&await n.hooks.before({customer:r,usage:{amount:e.body.amount,beforeAmount:c,afterAmount:u},feature:n});let m=await o.insertUsage({referenceType:r.referenceType,referenceId:r.referenceId,event:e.body.event,feature:n,amount:e.body.amount});return n.hooks?.after&&await n.hooks.after({customer:r,usage:{amount:e.body.amount,beforeAmount:c,afterAmount:u},feature:n}),m})}import{createAuthEndpoint as V,sessionMiddleware as _}from"better-auth/api";import{z as a}from"zod";var F=a.object({referenceId:a.string(),referenceType:a.string(),email:a.string().optional(),name:a.string().optional(),overrideKey:a.string().optional()}),Fe=a.object({referenceId:a.string(),featureKey:a.string(),maxLimit:a.number().optional(),minLimit:a.number().optional()}),Te=a.object({referenceId:a.string({}),referenceType:a.string(),event:a.string().optional(),createdAt:a.date(),lastResetAt:a.date(),amount:a.number(),afterAmount:a.number(),feature:a.string()});function T(){return V("/usage/upsert-customer",{method:"POST",body:F,middleware:[_],metadata:{openapi:{description:"Upserts a customer to the customer table",requestBody:{required:!0,content:{"application/json":{schema:{type:"object",properties:{referenceId:{type:"string"},referenceType:{type:"string"},name:{type:"string"},email:{type:"string"},overrideKey:{type:"string"}},required:["referenceId","referenceType"]}}},responses:{200:{description:"Successful Upsert"}}}}}},async t=>await d(t.context).upsertCustomer(t.body))}function Ve(t){return{id:"@eggermarc/usage",schema:{usage:{fields:{referenceId:{type:"string",required:!0,input:!0},referenceType:{type:"string",required:!0,input:!0},feature:{type:"string",required:!0,input:!0},amount:{type:"number",required:!0,input:!0},afterAmount:{type:"number",required:!0,input:!0},event:{type:"string",required:!0},lastResetAt:{type:"date",required:!0},createdAt:{type:"date",required:!0}}},customer:{fields:{referenceId:{type:"string",required:!0,input:!0,unique:!0},referenceType:{type:"string",required:!0,input:!0},email:{type:"string",required:!1,input:!0},name:{type:"string",required:!1,input:!0}}}},endpoints:{getFeature:C(t),consumeFeature:R(t),listFeatures:O(t),checkUsage:K(t),upsertCustomer:T(),syncUsage:A(t)}}}export{Ve as usage};