updatedotdev/js/main 6k tokens More Tools
```
├── .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.
Copied!