Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: initialization #6

Merged
merged 24 commits into from
Dec 6, 2024
Merged

feat: initialization #6

merged 24 commits into from
Dec 6, 2024

Conversation

Convly
Copy link
Member

@Convly Convly commented Nov 30, 2024











What does it do?

tl;dr Initialize the Strapi SDK project

Implements the tech design originating from the internal Strapi SDK Request For Comment.

When packed and distributed, the package exposes a single factory used to create a new Strapi SDK instance.

Usage

The factory takes a single parameter: the SDK configuration.

export interface StrapiSDKConfig {
  baseURL: string;
  auth?: AuthConfig;
}

Where

  • The baseURL is used to prepend all future request URLs.
  • The optional auth configuration is used to authenticate future HTTP requests using a specified auth strategy.

TypeScript/ESM

import { createStrapiSDK } from '@strapi/sdk-js';

const sdk = createStrapiSDK({
  baseURL: 'http://localhost:1337',
  auth: {
    strategy: 'api-token',
    options: { token: '<TOKEN>' }
  }
});

JavaScript/CommonJS

const { createStrapiSDK } = require('@strapi/sdk-js');

const sdk = createStrapiSDK({
    baseURL: 'http://localhost:1337',
    auth: {
        strategy: 'users-permissions',
        options: { identifier: 'email@example.com', password: '<PASSWORD>' }
    }
});

Features

The initialization step includes the following features (whether as a contributor or end user)

Contributor

  • File structure and repo setup using TypeScript
  • Unit test setup and 100% coverage using jest
  • Build process using rollup
  • Linting and formatting using eslint/prettier
  • Safe pack strategy
  • Commit-lint
  • Pre-commit checks

End-User

  • SDK factory import/require
  • Ability to set up the SDK using a baseURL
  • Ability to manage the authentication using two default auth strategies
    • API Tokens
    • Users & Permissions
  • Ability to make custom HTTP requests using the sdk.fetch() method

Why is it needed?

To make the life of devs using Strapi easier.

How to test it?

The project has a 100% code coverage and is configured to accept a minimum of 95% coverage on all tokens.

Yup, 95%.

Life's good

To run the tests, use

pnpm test

To run the tests with coverage, use

pnpm test:cov

Scripts

  • build: Executes Rollup with rollup.config.mjs. The option --failAfterWarnings ensures the process exits with an error code if there are any warnings during the build process.
  • build:clean: First deletes the dist directory, then similar to build, run Rollup with the same configuration and settings.
  • clean: Uses Rimraf to recursively delete the dist directory, cleaning up build artifacts.
  • lint: Runs ESLint across the entire codebase to identify style and programming issues.
  • lint:fix: Executes ESLint with the --fix option to automatically correct certain linting issues.
  • prepare: Rely on husky to run pre-commit hooks (lint, tests, build, etc...)
  • prettier:check: Runs Prettier to check if code formatting matches the project’s style rules, without making changes.
  • prettier:write: Executes Prettier to reformat code according to the specified style rules.
  • test: Runs Jest in verbose mode to execute the test suites.
  • test:cov: Runs Jest in verbose mode with a coverage report, offering insights into how much of the codebase is covered by tests.
  • ts:check: Uses the TS Compiler to check for type errors in the project as specified by tsconfig.build.json, without outputting compiled files (--noEmit).
  • watch: Runs Rollup in watch mode, which re-builds the project on file changes, again using --failAfterWarnings to stop the process on warnings.
  • prepack: Executes a shell script located at ./scripts/pre-pack.sh. Used to prepare the package for publishing, performing tasks such as linting, testing or compiling files.






























Overall Simplified Architecture

Although it exposes a simple factory function to the public API, the SDK package is designed in an object-oriented way.

The entry point and main class is the StrapiSDK.

It uses an HttpClient dependency to perform requests based on the user configuration.

The HttpClient manages the authentication of its requests using an AuthManager.

The AuthManager is responsible for managing its internal state (auth provider, authentication state, etc...).

  • It uses an AuthProviderFactory to create instances of necessary auth providers
  • It registers two auth providers by default but is internally open for extension:
    • API Token: ApiTokenAuthProvider
    • Users & Permissions: UsersPermissionsAuthProvider

Architecture Diagram

Note: only the public API of each class is shown here to avoid verbosity and keep it simple

classDiagram
    class StrapiSDK {
        +baseURL: string
        +fetch(url, init): Promise<Response>
    }
        
    class HttpClient {
        +baseURL: string
        +attachHeaders(request): void
        +fetch(url, init): Promise<Response>
        +_fetch(url, init): Promise<Response>
        +setAuthStrategy(name, options): HttpClient;
        +setBaseURL(url): HttpClient;
    }
        
    class AuthProviderFactory {
        +create(authStrategy, options): AuthProvider
        +register(strategy, creator): AuthProviderFactory
    }
        
    class AuthManager {
        +authenticate(client: HttpClient): void
        +authenticateRequest(request): void
        +handleUnauthorizedError(): void
        +isAuthenticated: boolean
        +setStrategy(name, options): void
        +strategy: string | undefined
    }
        
    class ApiTokenAuthProvider {
        +authenticate(): void
        +headers: Record<string, string>
        +name: string
    }
        
    class UsersPermissionsAuthProvider {
        +authenticate(client: HttpClient): void
        +headers: Record<string, string>
        +name: string
    }
        
    class AbstractAuthProvider {
        #authenticate(client: HttpClient): void
        #headers: Record<string, string>
        #name: string
    }

    style StrapiSDK fill:#B2DFDB,stroke:#00796B,stroke-width:2px
    style HttpClient fill:#E3F2FD,stroke:#64B5F6,stroke-width:2px
    style AuthManager fill:#E8F5E9,stroke:#81C784,stroke-width:2px
    style AuthProviderFactory fill:#decbe4,stroke:#333,stroke-width:2px
    style AbstractAuthProvider fill:#FFF3E0,stroke:#FFB74D,stroke-width:2px
    style ApiTokenAuthProvider fill:#FFF3E0,stroke:#FFB74D,stroke-width:2px
    style UsersPermissionsAuthProvider fill:#FFF3E0,stroke:#FFB74D,stroke-width:2px

    StrapiSDK --> HttpClient : Uses
    AuthManager --> AuthProviderFactory: Uses
    AuthManager --> ApiTokenAuthProvider : Delegates
    AuthManager --> UsersPermissionsAuthProvider : Delegates
    HttpClient --> AuthManager : Utilizes
    ApiTokenAuthProvider --|> AbstractAuthProvider : Extends
    UsersPermissionsAuthProvider --|> AbstractAuthProvider : Extends
Loading






































































Damn, you really read all that? Congrats I guess?































???

@Convly Convly added pr: feature New or updates to features source: sdk-js labels Nov 30, 2024
@Convly Convly changed the title Initialization feat: initialization Dec 2, 2024
tsconfig.eslint.json Show resolved Hide resolved
.prettierignore Outdated Show resolved Hide resolved
tests/unit/sdk/initialization.test.ts Outdated Show resolved Hide resolved
src/validators/url.ts Outdated Show resolved Hide resolved
@Convly Convly requested a review from innerdvations December 2, 2024 12:47
Copy link
Contributor

@innerdvations innerdvations left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! I will try testing it out for real soon, but code itself looks good

Copy link
Contributor

@innerdvations innerdvations left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried it out and it worked.

I think there are some differences between this and the defined tasks for things like error handling, but those can be resolved later. Merging this will let us get started building the actual sdk functionality 🥳

src/sdk.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@jhoward1994 jhoward1994 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need "type": "module" in the package JSON?

I just tried to use it from a simple test app as:

import { createStrapiSDK } from "@strapi/sdk-js";

const strapi = createStrapiSDK({
  baseURL: "http://localhost:1337",
});

async function testStrapi() {
  try {
    // Example: Fetch all entries of a content type
    const response = await strapi.fetch("/api/articles");
    const articles = await response.json();
    console.log("Articles:", articles);
  } catch (error) {
    console.error("Error interacting with Strapi:", error);
  }
}

testStrapi();

And got the error:

file:///Users/jamie/scr/strapi/code/strapi-sdk-test/test.js:1
import { createStrapiSDK } from "@strapi/sdk-js";
         ^^^^^^^^^^^^^^^
SyntaxError: Named export 'createStrapiSDK' not found. The requested module '@strapi/sdk-js' is a CommonJS module, which may not support all module.exports as named exports.
...

@jhoward1994 jhoward1994 mentioned this pull request Dec 4, 2024
@Convly Convly merged commit cac33fe into main Dec 6, 2024
8 checks passed
@Convly Convly deleted the feat/sdk-initialization branch December 6, 2024 09:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pr: feature New or updates to features
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants