``` ├── .DS_Store (omitted) ├── .env (omitted) ├── .eslintignore ├── .eslintrc.js (200 tokens) ├── .gitattributes (omitted) ├── .github/ ├── workflows/ ├── npm-publish.yml (200 tokens) ├── .gitignore (400 tokens) ├── .npmignore ├── .prettierrc ├── .vscode/ ├── settings.json ├── LICENSE (omitted) ├── README.md (1200 tokens) ├── package.json (300 tokens) ├── pnpm-lock.yaml (omitted) ├── src/ ├── .DS_Store ├── Update.ts (300 tokens) ├── UpdateBilling.ts (800 tokens) ├── UpdateEntitlements.ts (400 tokens) ├── createClient.ts (100 tokens) ├── index.ts ├── types/ ├── billing.ts (300 tokens) ├── cookie.ts (100 tokens) ├── entitlement.ts (100 tokens) ├── index.ts ├── internal.ts ├── options.ts (100 tokens) ├── request.ts (100 tokens) ├── utils/ ├── cookie-defaults.ts (200 tokens) ├── environment.ts ├── index.ts ├── object.ts (100 tokens) ├── request.ts (800 tokens) ├── storage.ts (200 tokens) ├── tsconfig.declarations.json (100 tokens) ├── tsconfig.json (100 tokens) ├── tsup.config.ts (100 tokens) ``` ## /.eslintignore ```eslintignore path="/.eslintignore" src/types/global.d.ts ``` ## /.eslintrc.js ```js path="/.eslintrc.js" module.exports = { root: true, parser: '@typescript-eslint/parser', plugins: ['@typescript-eslint', 'node', 'prettier'], extends: [ 'eslint:recommended', 'plugin:node/recommended', 'plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], rules: { 'prettier/prettier': 'warn', 'node/no-missing-import': 'off', 'node/no-empty-function': 'off', 'node/no-unsupported-features/es-syntax': 'off', 'node/no-missing-require': 'off', 'node/shebang': 'off', '@typescript-eslint/no-use-before-define': 'off', quotes: ['warn', 'single', { avoidEscape: true }], 'node/no-unpublished-import': 'off', '@typescript-eslint/no-unsafe-assignment': 'off', '@typescript-eslint/no-var-requires': 'off', '@typescript-eslint/ban-ts-comment': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ``` ## /.github/workflows/npm-publish.yml ```yml path="/.github/workflows/npm-publish.yml" name: NPM Package on: push: branches: - main pull_request: branches: - main jobs: validate-and-publish: runs-on: ubuntu-latest if: ${{ !contains(github.event.head_commit.message, '[skip-publish]') }} steps: - name: Checkout code uses: actions/checkout@v3 - name: 📦 Setup pnpm uses: pnpm/action-setup@v4.1.0 with: version: 9.0.0 - name: ⎔ Setup Node.js uses: actions/setup-node@v4 with: node-version: 18 registry-url: 'https://registry.npmjs.org' cache: "pnpm" - name: Install dependencies run: pnpm install --frozen-lockfile - name: Build package run: pnpm run build - name: Perform dry run publish run: pnpm publish --dry-run --no-git-checks - name: Publish to NPM if: ${{ github.ref == 'refs/heads/main' }} run: pnpm publish --no-git-checks env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ``` ## /.gitignore ```gitignore path="/.gitignore" # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # Snowpack dependency directory (https://snowpack.dev/) web_modules/ # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ .rts2_cache_es/ .rts2_cache_umd/ # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env.test .env.local # parcel-bundler cache (https://parceljs.org/) .cache .parcel-cache # Next.js build output .next out # Nuxt.js build / generate output .nuxt dist # Gatsby files .cache/ # Comment in the public line in if your project uses Gatsby and not Next.js # https://nextjs.org/blog/next-9-1#public-directory-support # public # vuepress build output .vuepress/dist # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ # TernJS port file .tern-port # Stores VSCode versions used for testing VSCode extensions .vscode-test # yarn v2 .yarn/cache .yarn/unplugged .yarn/build-state.yml .yarn/install-state.gz .pnp.* # Compiled code dist/ ``` ## /.npmignore ```npmignore path="/.npmignore" *.log npm-debug.log* coverage .nyc_output node_modules package-lock.json yarn.lock pnpm-lock.yaml src test examples example-next-js umd_temp CHANGELOG.md .travis.yml .editorconfig .eslintignore .eslintrc .babelrc .gitignore ``` ## /.prettierrc ```prettierrc path="/.prettierrc" { "singleQuote": true, "trailingComma": "es5", "arrowParens": "avoid", "printWidth": 80 } ``` ## /.vscode/settings.json ```json path="/.vscode/settings.json" { "editor.formatOnSave": true, "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" } } ``` ## /README.md # Update JS Library Update is a library for seamless billing and entitlement management. It integrates with your existing tools, like Stripe and Supabase, so you can integrate without migrating away from your existing stack. ## 🚀 Quickstart The easiest way to get started with Update is to use the `create-update-app` command: ```bash npm create update@latest ``` This tool will help you choose a framework and set up a fully working Update application in seconds. Just provide a name and your API keys. For source code examples, check out our [examples repository](https://github.com/updatedotdev/examples). ## ✨ Features - **Billing**: Seamless payments management - **Entitlements**: Simple access control for premium features - **Framework Support**: Built-in integration for Next.js and other SSR environments ## 🔧 Installation ```bash npm install @updatedev/js ``` ## 🔐 Auth Integrations - 🐘 [Supabase](https://supabase.com?utm_source=update&utm_medium=referral&utm_campaign=update-js-readme) - 🔥 [Firebase](https://firebase.google.com?utm_source=update&utm_medium=referral&utm_campaign=update-js-readme) - 🔐 [Clerk](https://clerk.com?utm_source=update&utm_medium=referral&utm_campaign=update-js-readme) - ⚙️ Custom ## 🏁 Getting Started First, you need to create an account on [Update](https://update.dev) and obtain your publishable key. This key is essential for initializing the Update client in your application. Additionally, configure your preferred authentication provider to manage user sessions and access control. ## ⚙️ Initializing ### Basic Setup ```typescript import { createClient } from '@updatedev/js'; export async function createUpdateClient() { return createClient(process.env.NEXT_PUBLIC_UPDATE_PUBLISHABLE_KEY!, { getSessionToken: async () => { // This must be replaced with your own logic to get your session token // For example, with Supabase: // // import { createSupabaseClient } from '@/utils/supabase/client' // ... // const supabase = createSupabaseClient() // const { data } = await supabase.auth.getSession() // if (data.session == null) return // return data.session.access_token // For this example, we'll just return a static token return 'your-session-token'; }, environment: process.env.NODE_ENV === 'production' ? 'live' : 'test', }); } ``` ### Initialization options - `getSessionToken`: A function that returns a session token for the user. This is optional, but required for most functions that require authentication. - `environment`: The environment to use for the client. Valid values are `live` and `test`. ### Environment Variables (.env.local) ``` NEXT_PUBLIC_UPDATE_PUBLISHABLE_KEY= ``` ## 🏗️ Framework Integration ### Next.js Integration Update works well with Next.js and other SSR environments. Create a `utils/update` directory with these files: #### Client (utils/update/client.ts) ```typescript import { createClient } from '@updatedev/js'; export async function createUpdateClient() { return createClient(process.env.NEXT_PUBLIC_UPDATE_PUBLISHABLE_KEY!, { getSessionToken: async () => { // This must be replaced with your own logic to get your session token // For example, with Supabase: // // import { createSupabaseClient } from '@/utils/supabase/client' // ... // const supabase = createSupabaseClient() // const { data } = await supabase.auth.getSession() // if (data.session == null) return // return data.session.access_token // For this example, we'll just return a static token return 'your-session-token'; }, environment: process.env.NODE_ENV === 'production' ? 'live' : 'test', }); } ``` #### Server (utils/update/server.ts) ```typescript import { createClient } from '@updatedev/js'; export async function createUpdateClient() { return createClient(process.env.NEXT_PUBLIC_UPDATE_PUBLISHABLE_KEY!, { getSessionToken: async () => { // This must be replaced with your own logic to get your session token // For example, with Supabase: // // import { createSupabaseClient } from '@/utils/supabase/server' // const supabase = await createSupabaseClient() // const { data } = await supabase.auth.getSession() // if (data.session == null) return // return data.session.access_token // For this example, we'll just return a static token return 'your-session-token'; }, environment: process.env.NODE_ENV === 'production' ? 'live' : 'test', }); } ``` ## 💳 Billing Features ### Getting Products ```typescript const { data, error } = await client.billing.getProducts(); ``` ### Creating a Checkout Session ```typescript const { data, error } = await client.billing.createCheckoutSession(priceId, { redirect_url: 'http://localhost:3000/subscription', }); ``` ### Managing Subscriptions Get user subscriptions: ```typescript const { data } = await client.billing.getSubscriptions(); ``` Cancel a subscription: ```typescript await client.billing.updateSubscription(id, { cancel_at_period_end: true, }); ``` Reactivate a subscription: ```typescript await client.billing.updateSubscription(id, { cancel_at_period_end: false, }); ``` ## 🛡️ Entitlements ### List Entitlements ```typescript const { data, error } = await client.entitlements.list(); ``` ### Check Entitlement ```typescript const { data, error } = await client.entitlements.check('premium'); ``` ## 📚 Documentation For complete documentation, visit [our documentation](https://update.dev/docs). ## 💬 Support Need help? Join our [Discord community](https://discord.gg/Guege5tXFK) for support and discussions. ## 🤝 License MIT ## /package.json ```json path="/package.json" { "name": "@updatedev/js", "version": "0.4.2", "description": "Update JavaScript SDK", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", "types": "dist/types/index.d.ts", "homepage": "https://update.dev", "bugs": { "url": "https://github.com/updatedotdev/js/issues" }, "keywords": [ "update", "update.dev", "auth", "billing", "saas", "sdk" ], "files": [ "dist/**/*" ], "scripts": { "clean": "rimraf ./dist", "build": "npm run clean && tsup", "build:declarations": "tsc -p tsconfig.declarations.json", "prepublishOnly": "npm run build" }, "repository": { "type": "git", "url": "git+https://github.com/updatedotdev/js.git" }, "license": "MIT", "author": { "name": "Update Team", "email": "support@update.dev", "url": "https://github.com/updatedotdev" }, "engines": { "node": ">=12.0" }, "devDependencies": { "@types/node": "^12.20.11", "@typescript-eslint/eslint-plugin": "^4.22.0", "@typescript-eslint/parser": "^4.22.0", "eslint": "^7.25.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^3.4.0", "prettier": "^3.5.3", "rimraf": "^6.0.1", "tslib": "^2.8.1", "tsup": "^8.4.0" }, "dependencies": { "cookie": "^1.0.2" } } ``` ## /src/.DS_Store Binary file available at https://raw.githubusercontent.com/updatedotdev/js/refs/heads/main/src/.DS_Store ## /src/Update.ts ```ts path="/src/Update.ts" import { API_KEY_HEADER, ENVIRONMENT_HEADER } from './types/internal'; import { UpdateClientOptions } from './types/options'; import { UpdateBillingClient } from './UpdateBilling'; import { UpdateEntitlements } from './UpdateEntitlements'; import { RequestClient } from './utils/request'; export class UpdateClient { billing: UpdateBillingClient; entitlements: UpdateEntitlements; constructor( private readonly apiKey: string, options: UpdateClientOptions ) { const billingEnvironment = this.getEnvironment(options); const requestClient = new RequestClient({ baseUrl: options?.apiUrl ?? 'https://api.update.dev/v1', headers: { [ENVIRONMENT_HEADER]: billingEnvironment, [API_KEY_HEADER]: this.apiKey, }, getAuthorizationToken: options.getSessionToken, }); this.billing = new UpdateBillingClient({ requestClient, hasSessionToken: options.getSessionToken !== undefined, }); this.entitlements = new UpdateEntitlements({ requestClient, hasSessionToken: options.getSessionToken !== undefined, }); } private getEnvironment(options: UpdateClientOptions): 'live' | 'test' { if (options.environment) { if (options.environment === 'live') return 'live'; if (options.environment === 'test') return 'test'; throw new Error('Invalid environment'); } return 'test'; } } ``` ## /src/UpdateBilling.ts ```ts path="/src/UpdateBilling.ts" import { RequestClient } from './utils/request'; import { CreateCheckoutSession, CreateCheckoutSessionOptions, ProductResponse, ProductWithPrices, Subscription, SubscriptionsResponse, UpdateSubscriptionRequest, UpdateSubscriptionResponse, } from './types/billing'; export class UpdateBillingClient { private requestClient: RequestClient; private hasSessionToken: boolean; constructor({ requestClient, hasSessionToken, }: { requestClient: RequestClient; hasSessionToken: boolean; }) { this.requestClient = requestClient; this.hasSessionToken = hasSessionToken; } async getProducts(): Promise<ProductResponse> { const { data, error } = await this.requestClient.request< ProductWithPrices[] >({ endpoint: '/billing/products', method: 'GET', }); if (error) { return { data: { products: null, }, error: { message: error.message, }, }; } return { data: { products: data, }, error: null, }; } async getSubscriptions(): Promise<SubscriptionsResponse> { if (!this.hasSessionToken) { console.warn( '@updatedev/js: billing.getSubscriptions() called without a session token. You need to add `getSessionToken` to createClient().' ); return { data: { subscriptions: null, }, error: { message: 'No session token', }, }; } const { data, error } = await this.requestClient.request<Subscription[]>({ endpoint: '/billing/subscriptions', method: 'GET', extra: { includeUser: true, }, }); if (error) { return { data: { subscriptions: null, }, error: { message: error.message, }, }; } return { data: { subscriptions: data, }, error: null, }; } async updateSubscription( id: string, { cancel_at_period_end }: UpdateSubscriptionRequest ): Promise<UpdateSubscriptionResponse> { if (!this.hasSessionToken) { console.warn( '@updatedev/js: billing.updateSubscription() called without a session token. You need to add `getSessionToken` to createClient().' ); return { data: { subscription: null, }, error: { message: 'No session token', }, }; } const { data, error } = await this.requestClient.request<Subscription>({ endpoint: '/billing/subscriptions/update', method: 'POST', body: { id, cancel_at_period_end, }, extra: { includeUser: true, }, }); if (error) { return { data: { subscription: null, }, error: { message: error.message, }, }; } return { data: { subscription: data, }, error: null, }; } async createCheckoutSession( id: string, options: CreateCheckoutSessionOptions ): Promise<CreateCheckoutSession> { if (!this.hasSessionToken) { console.warn( '@updatedev/js: billing.createCheckoutSession() called without a session token. You need to add `getSessionToken` to createClient().' ); return { data: { url: null, }, error: { message: 'No session token', }, }; } const { data, error } = await this.requestClient.request<string>({ endpoint: '/billing/checkout/create', method: 'POST', body: { id, options, }, extra: { includeUser: true, }, }); if (error) { return { data: { url: null, }, error: { message: error.message, }, }; } return { data: { url: data, }, error: null, }; } } ``` ## /src/UpdateEntitlements.ts ```ts path="/src/UpdateEntitlements.ts" import { CheckEntitlementResponse, ListEntitlementsResponse, } from './types/entitlement'; import { RequestClient } from './utils/request'; export class UpdateEntitlements { private requestClient: RequestClient; private hasSessionToken: boolean; constructor({ requestClient, hasSessionToken, }: { requestClient: RequestClient; hasSessionToken: boolean; }) { this.requestClient = requestClient; this.hasSessionToken = hasSessionToken; } async list(): Promise<ListEntitlementsResponse> { if (!this.hasSessionToken) { console.warn( '@updatedev/js: entitlements.list() called without a session token. You need to add `getSessionToken` to createClient().' ); return { data: { entitlements: null, }, error: { message: 'No session token', }, }; } const { data, error } = await this.requestClient.request<string[]>({ endpoint: '/entitlements', method: 'GET', extra: { includeUser: true, }, }); if (error) { return { data: { entitlements: null, }, error: { message: error.message, }, }; } return { data: { entitlements: data, }, error: null, }; } async check(entitlement: string): Promise<CheckEntitlementResponse> { if (!this.hasSessionToken) { console.warn( '@updatedev/js: entitlements.check() called without a session token. You need to add `getSessionToken` to createClient().' ); return { data: null, error: { message: 'No session token', }, }; } const { data, error } = await this.requestClient.request<{ has_access: boolean; }>({ endpoint: '/entitlements/check', method: 'POST', body: { entitlement, }, extra: { includeUser: true, }, }); if (error) { return { data: null, error: { message: error.message, }, }; } return { data: { hasAccess: data.has_access, }, error: null, }; } } ``` ## /src/createClient.ts ```ts path="/src/createClient.ts" import { UpdateClientOptions } from './types/options'; import { UpdateClient } from './Update'; export function createClient( updateApiKey: string, options: UpdateClientOptions ): UpdateClient { return new UpdateClient(updateApiKey, options); } ``` ## /src/index.ts ```ts path="/src/index.ts" export * from './types'; export * from './createClient'; export * from './Update'; ``` ## /src/types/billing.ts ```ts path="/src/types/billing.ts" export type Price = { id: string; type: 'recurring' | 'one-time'; currency: string; interval: string; unit_amount: number; interval_count: number; }; export type Product = { id: string; name: string; description: string; status: string; }; export type ProductWithPrices = Product & { prices: Price[]; }; export type Subscription = { id: string; status: 'active' | 'past_due' | 'inactive'; price: Price; product: Product; cancel_at_period_end: boolean; canceled_at: string | null; current_period_end: string; current_period_start: string; }; export type CreateCheckoutSession = | { data: { url: string; }; error: null; } | { data: { url: null; }; error: { message: string; }; }; export type SubscriptionsResponse = | { data: { subscriptions: Subscription[]; }; error: null; } | { data: { subscriptions: null; }; error: { message: string; }; }; export type ProductResponse = | { data: { products: ProductWithPrices[]; }; error: null; } | { data: { products: null; }; error: { message: string; }; }; export type UpdateSubscriptionRequest = { cancel_at_period_end: boolean; }; export type UpdateSubscriptionResponse = { data: { subscription: Subscription | null; }; error: { message: string; } | null; }; export type CreateCheckoutSessionOptions = { redirect_url: string; quantity?: number; }; ``` ## /src/types/cookie.ts ```ts path="/src/types/cookie.ts" import { SerializeOptions } from 'cookie'; export type GetAllCookies = () => | Promise<{ name: string; value: string }[] | null> | { name: string; value: string }[] | null; export type SetAllCookies = ( cookies: { name: string; value: string; options: Partial<SerializeOptions>; }[] ) => Promise<void> | void; ``` ## /src/types/entitlement.ts ```ts path="/src/types/entitlement.ts" export type ListEntitlementsResponse = | { data: { entitlements: string[]; }; error: null; } | { data: { entitlements: null; }; error: { message: string; }; }; export type CheckEntitlementResponse = | { data: { hasAccess: boolean; }; error: null; } | { data: null; error: { message: string; }; }; ``` ## /src/types/index.ts ```ts path="/src/types/index.ts" export * from './billing'; ``` ## /src/types/internal.ts ```ts path="/src/types/internal.ts" export const ENVIRONMENT_HEADER = 'X-Update-Environment'; export const API_KEY_HEADER = 'X-Api-Key'; ``` ## /src/types/options.ts ```ts path="/src/types/options.ts" import { StorageOptions } from '../utils/storage'; export type GetToken = () => | Promise<string | undefined | null> | string | undefined | null; export interface UpdateClientOptions { getSessionToken?: GetToken; environment?: 'live' | 'test'; apiUrl?: string; storage?: StorageOptions; } ``` ## /src/types/request.ts ```ts path="/src/types/request.ts" export interface RequestOptions { endpoint: string; method: 'GET' | 'POST'; body?: unknown | FormData; headers?: Record<string, string>; queryParams?: Record<string, string | number | boolean | null | undefined>; extra?: { includeUser?: boolean; }; } export interface CoreRequestOptions { endpoint: string; method: 'GET' | 'POST'; body?: unknown | FormData; headers?: Record<string, string>; queryParams?: Record<string, string | number | boolean | null | undefined>; } export type ApiResponse<T = any> = | { data: T; error: null; status: 'SUCCESS'; } | { data: null; error: { message: string; }; status: 'ERROR'; }; ``` ## /src/utils/cookie-defaults.ts ```ts path="/src/utils/cookie-defaults.ts" import { isBrowser } from './environment'; import { serialize, parse, SerializeOptions } from 'cookie'; type CookieOptions = Partial<SerializeOptions>; export function setAll( cookies: { name: string; value: string; options: CookieOptions; }[] ): void { if (!isBrowser) return; cookies.forEach(({ name, value, options }) => { document.cookie = serialize(name, value, options || {}); }); } export function getAll(): | { name: string; value: string; }[] | null { if (!isBrowser) return null; const cookies = parse(document.cookie); return Object.entries(cookies) .filter(([, v]) => v !== undefined) .map(([name, value]) => ({ name, value: value as string, // value cannot be undefined due to filter above })); } ``` ## /src/utils/environment.ts ```ts path="/src/utils/environment.ts" function isBrowser(): boolean { return typeof window !== 'undefined' && typeof document !== 'undefined'; } export { isBrowser }; ``` ## /src/utils/index.ts ```ts path="/src/utils/index.ts" export * from './cookie-defaults'; export * from './environment'; ``` ## /src/utils/object.ts ```ts path="/src/utils/object.ts" export function getDefinedObject<T extends Record<string, unknown>>(object: T) { let definedObject: Record<string, unknown> = {}; if (object) { Object.entries(object).forEach(([key, value]) => { if (value !== undefined && typeof value !== 'function') { definedObject[key] = value; } }); } return definedObject; } ``` ## /src/utils/request.ts ```ts path="/src/utils/request.ts" import { GetToken } from '../types/options'; import { ApiResponse, CoreRequestOptions, RequestOptions, } from '../types/request'; export class RequestClient { private baseUrl: string; private getAuthorizationToken?: GetToken; private headers?: Record<string, string>; private baseBody?: Record<string, string | undefined>; constructor({ baseUrl, headers, getAuthorizationToken, baseBody, }: { baseUrl: string; headers?: Record<string, string | undefined>; getAuthorizationToken?: GetToken; baseBody?: Record<string, string | undefined>; }) { this.baseUrl = baseUrl || 'https://api.update.dev/v1'; this.headers = this._getDefinedObject(headers); this.baseBody = this._getDefinedObject(baseBody); this.getAuthorizationToken = getAuthorizationToken; this.baseBody = baseBody; } private _getDefinedObject(headers?: Record<string, string | undefined>) { let definedHeaders: Record<string, string> = {}; if (headers) { Object.entries(headers).forEach(([key, value]) => { if (value !== undefined) { definedHeaders[key] = value; } }); } return definedHeaders; } _buildQueryString( params: | Record<string, string | number | boolean | null | undefined> | undefined ): string { if (params == null) return ''; const queryString = new URLSearchParams(); Object.keys(params).forEach((key: string) => { if (params[key] == null) return; queryString.append(key, String(params[key])); }); return queryString.toString(); } async _execute(url: string, options: RequestInit): Promise<ApiResponse> { try { const response = await fetch(url, options); return response.json() as Promise<ApiResponse>; } catch (error) { return { data: null, error: { message: 'There was an error' }, status: 'ERROR', }; } } async _requestCore<T = any>( options: CoreRequestOptions ): Promise<ApiResponse<T>> { const { method, endpoint } = options; let body = options.body; let queryParams = options.queryParams; let requestHeaders = options.headers; let url = `${this.baseUrl}${endpoint}`; // Add base body to GET requests if (method === 'GET' && this.baseBody) { queryParams = { ...(queryParams || {}), ...this.baseBody, }; } // Add base body to POST requests if (method === 'POST' && this.baseBody) { body = { ...(body || {}), ...this.baseBody, }; } if (method === 'GET' && queryParams) { const queryString = this._buildQueryString(queryParams); url = `${url}?${queryString}`; } const headers: HeadersInit = new Headers({ ...requestHeaders, ...(this.headers || {}), }); const requestOptions: RequestInit = { method, headers, }; if (method === 'POST' && body) { headers.set('Content-Type', 'application/json'); requestOptions.body = JSON.stringify(body); } const { data, status, error } = await this._execute(url, requestOptions); if (status === 'ERROR') { return { data: null, error, status }; } return { data, error: null, status }; } async request<T = unknown>(options: RequestOptions): Promise<ApiResponse<T>> { let requestOptions = { ...options }; if ( requestOptions.extra?.includeUser != null && requestOptions.extra.includeUser && this.getAuthorizationToken ) { const authorizationToken = await this.getAuthorizationToken(); if (authorizationToken) { requestOptions.headers = { ...requestOptions.headers, Authorization: `Bearer ${authorizationToken}`, }; } } return this._requestCore(requestOptions); } } ``` ## /src/utils/storage.ts ```ts path="/src/utils/storage.ts" import { SerializeOptions } from 'cookie'; import { GetAllCookies, SetAllCookies } from '../types/cookie'; export type StorageOptions = { getAll?: GetAllCookies; setAll?: SetAllCookies; }; export class StorageClient { constructor( private getAll: GetAllCookies, private setAll: SetAllCookies | undefined ) { this.getAll = getAll; this.setAll = setAll; } async set( name: string, value: string, options: Partial<SerializeOptions> ): Promise<void> { if (this.setAll == null) return; await this.setAll([{ name, value, options }]); } async get(name: string): Promise<string | undefined> { const cookies = await this.getAll(); if (!cookies) { return undefined; } return cookies.find(cookie => cookie.name === name)?.value; } } ``` ## /tsconfig.declarations.json ```json path="/tsconfig.declarations.json" { "extends": "./tsconfig.json", "compilerOptions": { "declaration": true, "declarationDir": "./dist/types", "declarationMap": true, "emitDeclarationOnly": true, "noEmit": false, "skipLibCheck": true, "sourceMap": false }, "exclude": ["**/__tests__/**/*"] } ``` ## /tsconfig.json ```json path="/tsconfig.json" { "compilerOptions": { "allowJs": true, "esModuleInterop": true, "importHelpers": true, "isolatedModules": true, "lib": ["es6", "dom"], "module": "NodeNext", "moduleResolution": "NodeNext", "noImplicitReturns": true, "noUnusedLocals": true, "noUnusedParameters": true, "outDir": "dist", "resolveJsonModule": true, "skipLibCheck": true, "sourceMap": false, "strict": true, "target": "ES2019", "rootDir": "src" }, "include": ["src"] } ``` ## /tsup.config.ts ```ts path="/tsup.config.ts" import { defineConfig } from 'tsup'; export default defineConfig(options => { const common = { entry: ['./src/**/*.{ts,tsx,js,jsx}', '!./src/**/*.test.{ts,tsx}'], bundle: false, clean: true, minify: false, sourcemap: true, legacyOutput: true, onSuccess: 'npm run build:declarations', }; return [ { ...common, format: ['cjs'], outDir: 'dist/cjs', }, { ...common, format: ['esm'], onSuccess: options.watch ? undefined : common.onSuccess, }, ]; }); ``` The better and more specific the context, the better the LLM can follow instructions. If the context seems verbose, the user can refine the filter using uithub. Thank you for using https://uithub.com - Perfect LLM context for any GitHub repo.