npm.io
2.23.16 • Published 3h ago

@deliverart/sdk-js-core

Licence
Version
2.23.16
Deps
1
Size
2.0 MB
Vulns
2
Weekly
0

@deliverart/sdk-js-core

Core package for the DeliverArt JavaScript SDK. Provides the API client, plugin system, and base request/response handling.

Installation

npm install @deliverart/sdk-js-core
# or
pnpm add @deliverart/sdk-js-core
# or
yarn add @deliverart/sdk-js-core

Exported Types

ApiClient
type ApiClient<Extensions extends ApiExtension = NonNullable<unknown>>

The main API client interface that provides methods to make API calls and manage plugins.

Methods:

  • call<TInputSchema, TOutputSchema, Q, H>(request: AbstractApiRequest): Promise<output<TOutputSchema>> - Execute an API request
  • addPlugin<T extends ApiExtension>(plugin: ApiClientPlugin<T>): ApiClient<T> - Add a plugin to extend client functionality
  • addRequestMiddleware(middleware: RequestMiddleware): void - Add middleware to intercept requests
  • addResponseMiddleware(middleware: ResponseMiddleware): void - Add middleware to intercept responses
AbstractApiRequest
abstract class AbstractApiRequest<
  TInputSchema extends ZodType<any, any, any>,
  TOutputSchema extends ZodType<any, any, any>,
  Query = unknown,
  Headers = unknown
>

Base class for all API requests. Extend this class to create custom API requests.

Abstract Properties:

  • method: 'GET' | 'POST' | 'PATCH' | 'DELETE' - HTTP method (required)
  • contentType: 'application/json' | 'multipart/form-data' | 'application/merge-patch+json' - Content-Type header (required)
  • accept: 'application/json' - Accept header (required)
  • inputSchema: TInputSchema - Zod schema for request validation (required)
  • outputSchema: TOutputSchema - Zod schema for response validation (required)
  • querySchema?: ZodType<Query> - Zod schema for query parameters (optional)
  • headersSchema?: ZodType<Headers> - Zod schema for headers (optional)

Abstract Methods:

  • getPath(): string - Returns the API endpoint path (required)

Built-in Methods:

  • validateInput(): output<TInputSchema> - Validates the request input
  • validateQuery(): Query | undefined - Validates query parameters
  • validateHeaders(): Headers | undefined - Validates headers
  • validateOutput(data: unknown): output<TOutputSchema> - Validates the response
  • parseResponse(data: unknown, rawResponse?: Response): output<TOutputSchema> - Parses and validates the response (can be overridden)
ApiExtension
type ApiExtension = Record<string, unknown>

Base type for plugin extensions that add new methods to the API client.

ApiClientPlugin
interface ApiClientPlugin<T extends ApiExtension = Record<string, unknown>> {
  setup: (client: ApiClient<Record<string, unknown>>) => T
}

Interface for creating plugins that extend the API client functionality.

Errors
InputValidationError
class InputValidationError extends Error {
  constructor(public readonly issues: ZodIssue[])
}

Thrown when request input validation fails.

Properties:

  • issues: ZodIssue[] - Array of validation errors from Zod
OutputValidationError
class OutputValidationError extends Error {
  constructor(public readonly issues: ZodIssue[])
}

Thrown when response validation fails.

Properties:

  • issues: ZodIssue[] - Array of validation errors from Zod

Usage

Creating the API Client
import { createApiClient } from '@deliverart/sdk-js-core';

const client = createApiClient({
  baseUrl: 'https://api.deliverart.com'
});

Parameters:

  • baseUrl: string (required) - The base URL for all API requests
Creating a Custom Request
import { AbstractApiRequest } from '@deliverart/sdk-js-core';
import { z } from 'zod';

// Define input schema
const createUserInputSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  age: z.coerce.number().min(18).optional()
});

// Define output schema
const userResponseSchema = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string(),
  age: z.coerce.number().nullable(),
  createdAt: z.string()
});

// Define query parameters schema (optional)
const getUsersQuerySchema = z.object({
  page: z.coerce.number().optional(),
  limit: z.coerce.number().optional(),
  search: z.string().optional()
});

// Create the request class
class CreateUser extends AbstractApiRequest<
  typeof createUserInputSchema,
  typeof userResponseSchema
> {
  readonly method = 'POST';
  readonly contentType = 'application/json';
  readonly accept = 'application/json';
  
  readonly inputSchema = createUserInputSchema;
  readonly outputSchema = userResponseSchema;
  readonly querySchema = undefined;
  readonly headersSchema = undefined;

  constructor(input: z.input<typeof createUserInputSchema>) {
    super(input);
  }

  getPath(): string {
    return '/users';
  }
}

// Request with path parameters
class GetUser extends AbstractApiRequest<
  typeof z.undefined,
  typeof userResponseSchema
> {
  readonly method = 'GET';
  readonly contentType = 'application/json';
  readonly accept = 'application/json';
  
  readonly inputSchema = z.undefined();
  readonly outputSchema = userResponseSchema;
  readonly querySchema = undefined;
  readonly headersSchema = undefined;

  private readonly userId: string;

  constructor(userId: string) {
    super(undefined);
    this.userId = userId;
  }

  getPath(): string {
    return `/users/${this.userId}`;
  }
}

// Request with query parameters
class GetUsers extends AbstractApiRequest<
  typeof z.undefined,
  typeof z.array(userResponseSchema),
  z.infer<typeof getUsersQuerySchema>
> {
  readonly method = 'GET';
  readonly contentType = 'application/json';
  readonly accept = 'application/json';
  
  readonly inputSchema = z.undefined();
  readonly outputSchema = z.array(userResponseSchema);
  readonly querySchema = getUsersQuerySchema;
  readonly headersSchema = undefined;

  constructor(options?: { query?: z.infer<typeof getUsersQuerySchema> }) {
    super(undefined, options);
  }

  getPath(): string {
    return '/users';
  }
}
Making API Calls
import { client } from './client';

// Create a user
const newUser = await client.call(new CreateUser({
  name: 'John Doe',
  email: 'john@example.com',
  age: 25
}));

// Get a user
const user = await client.call(new GetUser('user-123'));

// Get users with filters
const users = await client.call(new GetUsers({
  query: {
    page: 1,
    limit: 10,
    search: 'john'
  }
}));
Adding Middleware
Request Middleware
client.addRequestMiddleware(async (ctx) => {
  console.log('Making request to:', ctx.url.toString());
  
  // Modify headers
  return {
    ...ctx,
    init: {
      ...ctx.init,
      headers: {
        ...ctx.init.headers,
        'X-Custom-Header': 'value'
      }
    }
  };
});

Parameters:

  • ctx.url: URL - The request URL
  • ctx.init: RequestInit - The fetch init options

Return: Modified RequestContext or the original context

Response Middleware
client.addResponseMiddleware(async (parsed, requestCtx) => {
  console.log('Response status:', parsed.response.status);
  console.log('Response body:', parsed.body);
  
  return parsed;
});

Parameters:

  • parsed.response: Response - The fetch Response object
  • parsed.body: unknown - The parsed response body
  • requestCtx: RequestContext - The original request context

Return: Modified ParsedResponse or the original parsed response

Creating a Plugin
import { ApiClient, ApiClientPlugin, ApiExtension } from '@deliverart/sdk-js-core';

// Define the extension interface
interface LoggerExtension extends ApiExtension {
  logger: {
    enable: () => void;
    disable: () => void;
  };
}

// Create the plugin
class LoggerPlugin implements ApiClientPlugin<LoggerExtension> {
  private enabled = true;

  setup(client: ApiClient): LoggerExtension {
    // Add request middleware
    client.addRequestMiddleware(async (ctx) => {
      if (this.enabled) {
        console.log('[Request]', ctx.init.method, ctx.url.toString());
      }
      return ctx;
    });

    // Add response middleware
    client.addResponseMiddleware(async (parsed, ctx) => {
      if (this.enabled) {
        console.log('[Response]', parsed.response.status);
      }
      return parsed;
    });

    // Return extension methods
    return {
      logger: {
        enable: () => {
          this.enabled = true;
        },
        disable: () => {
          this.enabled = false;
        }
      }
    };
  }
}

// Use the plugin
const client = createApiClient({ baseUrl: 'https://api.example.com' })
  .addPlugin(new LoggerPlugin());

// Now you can use the logger methods
client.logger.disable();
client.logger.enable();
Error Handling
import { InputValidationError, OutputValidationError } from '@deliverart/sdk-js-core';

try {
  await client.call(new CreateUser({
    name: '', // Invalid: min length is 1
    email: 'invalid-email', // Invalid email format
  }));
} catch (error) {
  if (error instanceof InputValidationError) {
    console.error('Input validation failed:');
    error.issues.forEach(issue => {
      console.error(`- ${issue.path.join('.')}: ${issue.message}`);
    });
  } else if (error instanceof OutputValidationError) {
    console.error('Response validation failed:');
    error.issues.forEach(issue => {
      console.error(`- ${issue.path.join('.')}: ${issue.message}`);
    });
  } else {
    console.error('Other error:', error);
  }
}

Advanced Features

Custom Response Parsing

Override parseResponse to customize how responses are parsed:

import { Paginated, responseToPagination } from '@deliverart/sdk-js-global-types';

class GetUsers extends AbstractApiRequest</*...*/> {
  // ... other properties ...

  parseResponse(data: unknown, rawResponse: Response): Paginated<User> {
    const users = z.array(userResponseSchema).parse(data);
    
    return this.validateOutput({
      data: users,
      pagination: responseToPagination(rawResponse)
    });
  }
}
FormData Requests
const uploadImageInputSchema = z.instanceof(FormData);

class UploadImage extends AbstractApiRequest<
  typeof uploadImageInputSchema,
  typeof imageResponseSchema
> {
  readonly method = 'POST';
  readonly contentType = 'multipart/form-data';
  readonly accept = 'application/json';
  
  readonly inputSchema = uploadImageInputSchema;
  readonly outputSchema = imageResponseSchema;

  constructor(formData: FormData) {
    super(formData);
  }

  getPath(): string {
    return '/images';
  }
}

// Usage
const formData = new FormData();
formData.append('file', fileBlob);
formData.append('name', 'My Image');

const image = await client.call(new UploadImage(formData));

License

MIT