10.0.4 • Published 4 years ago

kaimly-sdk v10.0.4

Weekly downloads
-
License
MIT
Repository
github
Last release
4 years ago

Installation

npm install kaimly-sdk

Usage

import { Kaimly } from 'kaimly-sdk';

const kaimly = new Kaimly('https://api.example.com/');

NOTE All methods return promises. Make sure to await methods, for example:

import { Kaimly } from 'kaimly-sdk';

const kaimly = new Kaimly('https://api.example.com/');

// Wait for login to be done...
await kaimly.auth.login({
	email: 'admin@example.com',
	password: 'password',
});

// ... before fetching items
const articles = await kaimly.items('articles').readMany();

console.log({
	items: articles.data,
	total: articles.meta.total_count,
});

Global

Initialize

import { Kaimly } from 'kaimly-sdk';

const kaimly = new Kaimly('https://api.example.com/');

url

The constructor accepts the URL as the first parameter.

options.auth

The authentication implementation. See Auth for more information.

Defaults to an instance Auth.

options.storage

The storage implementation. See Storage for more information.

Defaults to an instance of MemoryStorage when in node.js, and LocalStorage when in browsers.

NOTE:

If you plan to use multiple SDK instances at once, keep in mind that they will share the Storage across them, leading to unpredictable behaviors. This scenario might be a case while writing tests.

For example, the SDK instance that executed last the login() method writes the resulting access_token into the Storage and overwrites any prior fetched access_token from any other SDK instance. That might mix up your test scenario by granting false access rights to your previous logged-in users.

Adding prefixes to your Storage instances would solve this error:

import { Kaimly, MemoryStorage } from 'kaimly-sdk';
import { randomBytes } from 'crypto';

// ...

const prefix = randomBytes(8).toString('hex');
const storage = new MemoryStorage(prefix);
const url = `http://${host}:${port}`;
const kaimly = new Kaimly(url, { storage });

options.transport

The transport implementation. See Transport for more information.

Defaults to an instance of AxiosTransport.

Example

const kaimly = new Kaimly('http://api.example.com/');

Advanced Example

//
// WARNING: OK, not exactly a warning, but this isn't needed at all.
// It's just to illustrate how the SDK is instantiated by default when
// passing only the URL as the first parameter.
//

const url = 'http://api.example.com/';

const isBrowser = typeof window !== 'undefined';

// Storage adapter where authentication state (token & expiration) is stored.
const storage = isBrowser ? new LocalStorage() : new MemoryStorage();

// Transport used to communicate with the server.
const transport = new AxiosTransport(url, storage, async () => {
	await auth.refresh(); // This is how axios checks for refresh
});

// Auth is how authentication is handled, stored, and refreshed.
const auth = new Auth(transport, storage, {
	mode: isBrowser ? 'cookie' : 'json',
});

const kaimly = new Kaimly(url, {
	auth,
	storage,
	transport,
});

Get / Set API URL

// Get the API base URL
console.log(kaimly.transport.url); // => https://api.example.com/

// Set the API base URL
kaimly.transport.url = 'https://api2.example.com';

Access to transport/Axios

You can tap into the transport through kaimly.transport. If you are using the (default) AxiosTransport, you can access axios through kaimly.transport.axios.

Intercepting requests and responses

Axios transport offers a wrapper around Axios interceptors to make it easy for you to inject/eject interceptors.

const requestInterceptor = kaimly.transport.requests.intercept((config) => {
	config.headers['My-Custom-Header'] = 'Header value';
	return config;
});

// If you don't want the interceptor anymore, remove it
requestInterceptor.eject();
const responseInterceptor = kaimly.transport.responses.intercept((response) => {
	console.log('Response received', { response });
	return response;
});

// If you don't want the interceptor anymore, remove it
responseInterceptor.eject();

Items

You can get an instance of the item handler by providing the collection (and type, in the case of TypeScript) to the items function. The following examples will use the Article type.

JavaScript

// import { Kaimly, ID } from 'kaimly-sdk';
const { Kaimly } = require('kaimly-sdk');

const kaimly = new Kaimly('https://api.example.com');

const articles = kaimly.items('articles');

TypeScript

import { Kaimly, ID } from 'kaimly-sdk';

// Map your collection structure based on its fields.
type Article = {
	id: ID;
	title: string;
	body: string;
	published: boolean;
};

// Map your collections to its respective types. The SDK will
// infer its types based on usage later.
type MyBlog = {
	// [collection_name]: typescript_type
	articles: Article;

	// You can also extend Kaimly collection. The naming has
	// to match a Kaimly system collection and it will be merged
	// into the system spec.
	kaimly_users: {
		bio: string;
	};
};

// Let the SDK know about your collection types.
const kaimly = new Kaimly<MyBlog>('https://kaimly.myblog.com');

// typeof(article) is a partial "Article"
const article = await kaimly.items('articles').readOne(10);

// Error TS2322: "hello" is not assignable to type "boolean".
// post.published = 'hello';

Create Single Item

await articles.createOne({
	title: 'My New Article',
});

Create Multiple Items

await articles.createMany([
	{
		title: 'My First Article',
	},
	{
		title: 'My Second Article',
	},
]);

Read All

await articles.readMany();

Read By Query

await articles.readMany({
	search: 'Kaimly',
	filter: {
		date_published: {
			_gte: '$NOW',
		},
	},
});

Read By Primary Key(s)

await articles.readOne(15);

Supports optional query:

await articles.readOne(15, { fields: ['title'] });

Update Multiple Items

await articles.updateMany([15, 42], {
	title: 'Both articles now have the same title',
});

Supports optional query:

await articles.updateMany(
	[15, 42],
	{
		title: 'Both articles now have the same title',
	},
	{
		fields: ['title'],
	}
);

Delete

// One
await articles.deleteOne(15);

// Multiple
await articles.deleteMany([15, 42]);

Activity

Read All Activity

await kaimly.activity.readMany();

Read Activity By Query

await kaimly.activity.readMany({
	filter: {
		action: {
			_eq: 'create',
		},
	},
});

Read Activity By Primary Key(s)

await kaimly.activity.readOne(15);

Supports optional query:

await kaimly.activity.readOne(15, { fields: ['action'] });

Create a Comment

await kaimly.activity.comments.create({
	collection: 'articles',
	item: 15,
	comment: 'Hello, world!',
});

Update a comment

await kaimly.activity.comments.update(31, {
	comment: 'Howdy, world!',
});

Note: The passed key is the primary key of the comment

Delete a comment

await kaimly.activity.comments.delete(31);

Note: The passed key is the primary key of the comment

Auth

Configuration

Kaimly will accept custom implementations of the IAuth interface. The default implementation Auth can be imported from kaimly-sdk. The default implementation will require you to pass the transport and storage implementations. All options are optional.

import { Auth } from 'kaimly-sdk';

// ...

const sdk = new Kaimly('url', {
	auth: new Auth(transport, storage, options);
	// ...
});

transport

The transport responsible for communicating with Kaimly backend.

Defaults to an instance of AxiosTransport when not creating Auth youself.

storage

The storage responsible for storing authentication and sdk state.

When not creating Auth youself, defaults to MemoryStorage in node.js, and LocalStorage in browsers.

options

options.mode

Accepts cookie or json.

When in cookie mode, the API will set the refresh token in an httpOnly secure cookie that can't be accessed from client side JavaScript. This is the most secure way to connect to the API from a public front-end website.

When you can't rely on cookies, or need more control over handling the storage of the cookie (like in node.js), use json mode. This will return the refresh token in the "normal" payload. The storage of these tokens are handled by the storage implementation.

Defaults to cookie in browsers, json in node.js.

options.refresh

See Refresh auth token.

Get current token

const token = kaimly.auth.token;

Login

With credentials

await kaimly.auth.login({
	email: 'admin@example.com',
	password: 'd1r3ctu5',
});

With static tokens

await kaimly.auth.static('static_token');

Refresh auth token

You can set authentication to auto-refresh the token once it's close to expire.

await kaimly.auth.login(
	{
		email: 'admin@example.com',
		password: 'd1r3ctu5',
	},
	{
		refresh: {
			auto: true,
		},
	}
);

You can also set how much time before the expiration you want to auto-refresh the token. When not specified, 30 sec is the default time.

await kaimly.auth.login(
	{
		email: 'admin@example.com',
		password: 'd1r3ctu5',
	},
	{
		refresh: {
			auto: true,
			time: 30000, // refesh the token 30 secs before the expiration
		},
	}
);

Refresh Auth Token

You can manually refresh the authentication token. This won't try to refresh the token if it's still valid in the eyes of the SDK.

Also worth mentioning that any concurrent refreshes (trying to refresh while a there's an existing refresh running), will result in only a single refresh hitting the server, and promises resolving/rejecting with the result from the first call. This depends on the implementation of IAuth you're using.

await kaimly.auth.refresh();

You can force the refresh by passing true in the first parameter.

An optional parameter will accept auto-refresh information.

await kaimly.auth.refresh(true);

This function can either return the AuthResult in case a refresh was made, false in case SDK thinks it's not needed, or throw an error in case refresh fails.

Logout

await kaimly.auth.logout();

Request a Password Reset

await kaimly.auth.password.request('admin@example.com');

Reset a Password

await kaimly.auth.password.reset('abc.def.ghi', 'n3w-p455w0rd');

Note: The token passed in the first parameter is sent in an email to the user when using request()

Transport

The transport object abstracts how you communicate with Kaimly. Transports can be customized to use different HTTP libraries for example.

Interface

// Simplified version, `import { ITransport } from 'kaimly-sdk';`
interface ITransport {
	url;
	get(path);
	head(path);
	options(path);
	delete(path, data = undefined);
	post(path, data);
	put(path, data);
	patch(path, data);
}

AxiosTransport

The default transport used in both browser and node deployments. It supports auto refresh on request.

Options

AxiosTransport requires a base URL and a storage implementation to work.

const transport = new AxiosTransport('http://example.com', new MemoryStorage(), async () => {
	await sdk.auth.refresh();
});
await transport.get('/server/info');

Storage

The storage is used to load and save SDK data.

LocalStorage

The storage used in environments where Local Storage is supported.

Options

The LocalStorage implementation accepts a transparent prefix. Use this when you need multiple SDK instances with independent authentication for example.

MemoryStorage

The storage used when SDK data is ephemeral. For example: only during the lifecycle of the process.

Options

The MemoryStorage implementation accepts a transparent prefix so you can have multiple instances of the SDK without having clashing keys.

Collections

kaimly.collections;

Same methods as kaimly.items("kaimly_collections").

Fields

kaimly.fields;

Same methods as kaimly.items("kaimly_fields").

Files

kaimly.files;

Same methods as kaimly.items("kaimly_files").

Folders

kaimly.folders;

Same methods as kaimly.items("kaimly_folders").

Permissions

kaimly.permissions;

Same methods as kaimly.items("kaimly_permissions").

Presets

kaimly.presets;

Same methods as kaimly.items("kaimly_presets").

Relations

kaimly.relations;

Same methods as kaimly.items("kaimly_relations").

Revisions

kaimly.revisions;

Same methods as kaimly.items("kaimly_revisions").

Roles

kaimly.roles;

Same methods as kaimly.items("kaimly_roles").

Settings

kaimly.settings;

Same methods as kaimly.items("kaimly_settings").

Server

Ping the Server

await kaimly.server.ping();

Get Server/Project Info

await kaimly.server.info();

Users

kaimly.users;

Same methods as kaimly.items("kaimly_users"), and:

Invite a New User

await kaimly.users.invites.send('admin@example.com', 'fe38136e-52f7-4622-8498-112b8a32a1e2');

The second parameter is the role of the user

Accept a User Invite

await kaimly.users.invites.accept('<accept-token>', 'n3w-p455w0rd');

The provided token is sent to the user's email

Enable Two-Factor Authentication

await kaimly.users.tfa.enable('my-password');

Disable Two-Factor Authentication

await kaimly.users.tfa.disable('691402');

Get the Current User

await kaimly.users.me.read();

Supports optional query:

await kaimly.users.me.read({
	fields: ['last_access'],
});

Update the Current Users

await kaimly.users.me.update({ first_name: 'Admin' });

Supports optional query:

await kaimly.users.me.update({ first_name: 'Admin' }, { fields: ['last_access'] });

Utils

Get a Random String

await kaimly.utils.random.string();

Supports an optional length (defaults to 32):

await kaimly.utils.random.string(50);

Generate a Hash for a Given Value

await kaimly.utils.hash.generate('My String');

Verify if a Hash is Valid

await kaimly.utils.hash.verify('My String', '$argon2i$v=19$m=4096,t=3,p=1$A5uogJh');

Sort Items in a Collection

await kaimly.utils.sort('articles', 15, 42);

This will move item 15 to the position of item 42, and move everything in between one "slot" up.

Revert to a Previous Revision

await kaimly.utils.revert(451);

Note: The key passed is the primary key of the revision you'd like to apply.


TypeScript

If you are using TypeScript, the JS-SDK requires TypeScript 3.8 or newer. TypeScript will also improve the development experience by providing relevant information when manipulating your data. For example, kaimly.items knows about your collection types if you feed the SDK with enough information in the construction of the SDK instance. This allows for a more detailed IDE suggestions for return types, sorting, and filtering.

type BlogPost = {
	id: ID;
	title: string;
};

type BlogSettings = {
	display_promotions: boolean;
};

type MyCollections = {
	posts: BlogPost;
	settings: BlogSettings;
};

// This is how you feed custom type information to Kaimly.
const kaimly = new Kaimly<MyCollections>('http://url');

// ...

const post = await kaimly.items('posts').readOne(1);
// typeof(post) is a partial BlogPost object

const settings = await posts.singleton('settings').read();
// typeof(settings) is a partial BlogSettings object

You can also extend the Kaimly system type information by providing type information for system collections as well.

import { Kaimly } from 'kaimly-sdk';

// Custom fields added to Kaimly user collection.
type UserType = {
	level: number;
	experience: number;
};

type CustomTypes = {
	/*
	This type will be merged with Kaimly user type.
	It's important that the naming matches a kaimly
	collection name exactly. Typos won't get caught here
	since SDK will assume it's a custom user collection.
	*/
	kaimly_users: UserType;
};

const kaimly = new Kaimly<CustomTypes>('https://api.example.com');

await kaimly.auth.login({
	email: 'admin@example.com',
	password: 'password',
});

const me = await kaimly.users.me.read();
// typeof me = partial KaimlyUser & UserType;

// OK
me.level = 42;

// Error TS2322: Type "string" is not assignable to type "number".
me.experience = 'high';
10.0.4

4 years ago

10.0.3

4 years ago

10.0.2

4 years ago

10.0.1

4 years ago

9.0.0-rc.85

4 years ago