About
This is an alternative interface proposal for sdk-core based on the initial designs as outlined in sdk-core Issue 1.
Some of these issues were identified from the initial proposal.
- The
plattar
namespace should probably be removed as both api-core and sdk-core are now multi-product and multi-businesses codebases
- The role of
sdk-core
has changed to accomodate multiple services rather than a single service and will be used as a base for generative SDK based on api-core
module
- Generative SDK runs automatically without developer interference based on the setup of
api-core
. This removes the need to manually create SDK's for interfacing with api-core
services.
Proposal
⚠️ The following proposal is a guide only and the final implementation may change due to technical or structural reasons.
Setup
The initial setup of the SDK needs to accomodate the fact that a single source/website might need to interface with multiple independent backends. Everything should have reasonable defaults with expected outputs.
- Consider that a single website might require interfacing with multiple backends independently
- Consider that this tool will be used for generating an SDK for multiple services and as such, multiple SDK's might be used in a single project
export interface ServiceAuth {
// when type == "cookie", the SDK will use the cookies as an auth type
// when type == "token", the SDK will use the supplied token as an auth type
readonly type: 'cookie' | 'token';
// this needs to be set if type == 'token'. It will be ignored if type == 'cookie'
readonly token?: string;
// optionally disable TLS (for non-secure NodeJS) - disabled by default
readonly tls?: boolean;
}
export interface ServiceConfig {
// primary api url - eg: `https://api.plattar.com`
readonly url: string;
// optionally provide an authentication method - defaults to `cookie`
readonly auth?: ServiceAuth;
}
// abstract class forces implementation by the generated SDK
export abstract class Service {
// these will automatically setup a default internally accessible service
// if called more than once, will replace the last instance with the new one
public static config(config:ServiceConfig):Service;
// this will return the current default configured Service instance
// if no service is available/configured, this will throw an Error
public static get default(): Service;
}
Example
Simple setup can be done as following. This needs to be done before the SDK can be used. Generated SDK should always setup a default version of the Service.
Service.config({
url: 'https://api.plattar.com'
});
More advanced version of a setup can look like the following - for example in NodeJS environments
Service.config({
url: 'https://api.plattar.com',
auth: {
type: 'token',
tls: true,
token: 'my-plattar-auth-token'
}
});
The default configuration (if none provided) will look as follows.
⚠️ The Generative SDK tool for each independent service will be REQUIRED to provide a default configuration for the respective service
// This is the default config if none is provided/setup - url will change based on generated SDK
Service.config({
url: 'https://api.plattar.com',
auth: {
type: 'cookie',
tls: false,
token: undefined
}
});
Base Object
All api-core
object types extends the base core object to provide built-in functionality for interfacing with the API.
export abstract class CoreObject extends CoreQuery {
// returns a new Query instance for this Core Object type.
// optionally pass a Service instance to use, if missing will use Service.default instead
public static query(service?:Service):Query;
// force an implementation on the object type from inherited code
// this will throw an Error if not implemented properly
public static get type():string;
}
Example
⚠️ The SDK generator will need to extend the CoreObject type to inherit functionality and extend it.
export class Scene extends CoreObject {
public override static get type():string { return "scene"; }
}
export class Application extends CoreObject {
public override static get type():string { return "application"; }
}
Queries
Queries is the primary method for interfacing with the API using the built-in query engine.
⚠️ These Queries run on the Server to optimise database operations for faster results from the API so use them whenever possible to reduce latency and increase performance
// this will be extended and filled in by the generator to pass required url parameters into the query engine
// for example /v3/scene/:id would contain `id` as a required parameter
// this is an empty interface by default
export interface QueryParameters {}
// this would be extended and filled in by the generator to pass required attributes for specific requests
// this is an empty interface by default
export interface QueryAttributes {}
// each object type will extend the Query directly
export abstract class CoreQuery {
// allows either data-filtering or loose searching
public abstract where(variable:string, operation: "==" | "!=" | ">" | "<" | ">=" | "<=", value:string | number | boolean):this;
// allows sparse-fieldsets (only returns provided fields)
public abstract fields(...fields:string):this;
// further serialise relationships
public abstract include(...objects:CoreObject):this;
// returns soft-deleted or partially-deleted objects as part of the query
public abstract deleted(...objects:CoreObject):this;
// perform sorting operations
public abstract sort(variable:string, operation: "ascending" | "descending"):this;
// perform pagination (only return objects matching page)
public abstract page(pageNumber:number, numberOfObjects:number):this;
}
Examples
Run the following Code to query the API
⚠️ The names of functions that performs the query are filled in based on the name of the endpoint as defined in api-core
so they might change later on. For example, the get-by
endpoint is named refer
and is generated automatically. Same with the simple get
endpoint for a single object.
👍 A shortcut of Application.query()
functions is also available in the base object, so the following Application.query().get()
is the same as Application.get()
as CoreObject
also extends CoreQuery
type.
example 1 - getting a list of scenes from application
https://api.plattar.space/v3/application/cbdf/scene?filter[scene.scene_type][eq]=default&include=scene.application&sort=scene.updated_at
// get a list of all scenes related to application
const scenes:Scene[] = await Application.query().where("scene_type", "==", "default").include(Application).sort("updated_at", "ascending").refer({by:Scene, id:'cbdf'});
example 2 - getting a single scene with some queries and an includes with limited fields
https://api.plattar.space/v3/scene/cbdf?include=scene.application&fields[scene]=title,custom_json,application_id
// get a scene matching id
const scene:Scene | null = await Scene.query().include(Application).fields("title", "custom_json", "application_id").get({id: 'cbdf'});
example 3 - just getting a plain-old scene without anything fancy
https://api.plattar.space/v3/scene/cbdf
// get a scene
const scene:Scene | null = await Scene.query().get({id:'cbdf'});
example 4 - making a brand new scene
POST https://api.plattar.space/v3/scene
// create a new scene
const scene:Scene | null = await Scene.query().create({title: 'cbdf'});
👍 Another way of creating a new Scene is as follows, however need to be aware that this will override ALL attributes in the remote Scene
// create a new scene
const scene:Scene = new Scene();
scene.attributes.title = "cbdf";
const createdScene:Scene | null = await scene.query().create();
example 5 - update an existing scene
PUT https://api.plattar.space/v3/scene/cbdf
// initialise or GET an existing Scene instance
const scene:Scene | null = await Scene.query().get({id: 'cbdf'});
scene.attributes.title = "my new title";
const updatedScene:Scene | null = await scene.query().update();
👍 Another way of updating an existing Scene is as follows, however need to be aware that this will override ALL attributes in the remote Scene
// initialise or GET an existing Scene instance
const scene:Scene = new Scene("cbdf);
scene.attributes.title = "my new title";
const updatedScene:Scene | null = await scene.query().update();