Skip to content

Instantly share code, notes, and snippets.

@esakal
Last active January 16, 2021 13:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save esakal/af8813a62c7631cf4b7994b0bb23ad48 to your computer and use it in GitHub Desktop.
Save esakal/af8813a62c7631cf4b7994b0bb23ad48 to your computer and use it in GitHub Desktop.
Backendless Typescript classes for defined data tables

About

Note This code is under development and is missing features that will be added later.

This is generator code creates Typescript based classes for defined data tables.

Backlog

  • One-To-One relations retrieving
  • One-To-Many relations retrieving
  • Generate Users interface (no need for class as any changes should be done in cloud code)
  • fix indentation
  • Save changes of relations
  • Potential missing implementation currently not taken from javascript code generator. See open question in slack - based on responses from support in slack those are not needed
  • upon save, the server returns a new family object, should merge it with relations before returning it to the user
  • current code, any password is optional in the DTO regardless to the actual schema definition, should be for users only

How to add the generator into your backendless workspace

  1. open backendless console and navigate to files tab.
  2. add file js-typescript-dataclasses.json into codegen/features/generators
  3. add file ts-dataclasses.xsl into codegen/features/xsls/own-ts
  4. add initialization method that register classes

Releases

2021 Jan 16

  • set unloaded relations as undefined and not null (null represent empty relation while undefined represent unloaded relation)

2021 Jan 15

  • generate separate sdk for cloud code and browser
  • expose organization and owner id in cloud code sdk

2021 Jan 10

  • ignore readonly properties like objectId, updated, created as readonly
  • ignore properties ownerId.
  • set custom relation as readonly organization (used for multi-tenant)
  • allow creating an object with partial DTO (for partial server updates).
  • default properties to undefined instead of null.
  • use a custom xslt folder that is not being overriden by backendless (as it is reserved system folder)

2020 Dec 30

  • update save method, return the new object after save.
  • update save method, store object id
  • rename all interfaces suffix to DTO instead of Data
  • add new method to create DTO from object
  • during construction, allow setting self properties only
  • remove __className as it doesn't assist when mapping classes to tables
  • expose initialization method that maps classes to tables
  • add static className as it is used with unit of work
{
"category": "JavaScript",
"name": "Typescript classes for defined data tables",
"tooltip": "Generates reusable code which uses Backendless user registration and login API. When the social login options are enabled, the generated code includes functionality for login with social network identity.",
"icon": "/codegen/features/xsls/backendless-codegen-xsls/icons/js_class.png",
"xsl": "/codegen/features/xsls/own-ts/ts-dataclasses.xsl"
}
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- @formatter:off -->
<xsl:import href="js-util.xsl"/>
<xsl:include href="../backendless-codegen-xsls/util.xsl"/>
<xsl:variable name="applicationName" select="backendless-codegen/application/name"/>
<xsl:variable name="projectName"><xsl:value-of select="$applicationName"/>-data</xsl:variable>
<xsl:variable name="applicationId" select="backendless-codegen/application/id"/>
<xsl:variable name="apiKey" select="backendless-codegen/application/apiKeys/JS"/>
<xsl:variable name="serverURL" select="backendless-codegen/application/serverURL"/>
<xsl:template name="getTypescriptType">
<xsl:param name="str"/>
<xsl:choose>
<xsl:when test="$str = 'UNKNOWN' or $str = 'STRING' or $str = 'STRING_ID' or $str = 'TEXT' or $str = 'FILE_REF' or $str = 'EXTENDED_STRING'">string</xsl:when>
<xsl:when test="$str = 'INT' or $str = 'DOUBLE' or $str = 'AUTO_INCREMENT'">number</xsl:when>
<xsl:when test="$str = 'BOOLEAN'">boolean</xsl:when>
<xsl:when test="$str = 'DATETIME'">Date</xsl:when>
<xsl:when test="$str = 'COLLECTION' or $str = 'RELATION_LIST'">any[]</xsl:when>
<xsl:otherwise>unknown</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="generate-sdk">
<xsl:param name="isCloudCode" />
import Backendless from "backendless";
<xsl:for-each select="backendless-codegen/application/tables/table">export interface <xsl:call-template name="firstCharToUpperCase"><xsl:with-param name="str" select="name"/></xsl:call-template>DTO {
<xsl:for-each select="columns">
<xsl:if test="$isCloudCode=1 or name!='ownerId'">
<xsl:if test="not(expression) and name!='updated' and name != 'created'"><xsl:value-of select="name"/><xsl:if test="required='false' or name='blUserLocale' or name='password'">?</xsl:if>: <xsl:call-template name="getTypescriptType"><xsl:with-param name="str" select="dataType"/></xsl:call-template>,
</xsl:if>
</xsl:if>
</xsl:for-each>
<xsl:for-each select="relations">
<xsl:if test="$isCloudCode=1 or name!='organization'">
<xsl:choose>
<xsl:when test="relationshipType = 'ONE_TO_ONE'"><xsl:value-of select="name"/>?: <xsl:call-template name="firstCharToUpperCase"><xsl:with-param name="str" select="toTableName"/></xsl:call-template>DTO,
</xsl:when>
<xsl:when test="relationshipType = 'ONE_TO_MANY'"><xsl:value-of select="name"/>?: <xsl:call-template name="firstCharToUpperCase"><xsl:with-param name="str" select="toTableName"/></xsl:call-template>DTO[],
</xsl:when>
</xsl:choose>
</xsl:if>
</xsl:for-each>
}
export class <xsl:call-template name="firstCharToUpperCase"><xsl:with-param name="str" select="name"/></xsl:call-template> {
static readonly className = '<xsl:value-of select="name"/>';
<xsl:for-each select="columns">
<xsl:if test="$isCloudCode=1 or name!='ownerId'">
<xsl:if test="name='updated' or name = 'created' or name = 'objectId'">readonly </xsl:if><xsl:if test="not(expression)"><xsl:value-of select="name"/>: <xsl:call-template name="getTypescriptType"><xsl:with-param name="str" select="dataType"/></xsl:call-template> = undefined;
</xsl:if>
</xsl:if>
</xsl:for-each>
<xsl:for-each select="relations">
<xsl:if test="$isCloudCode=1 or name!='organization'">
<xsl:choose>
<xsl:when test="relationshipType = 'ONE_TO_ONE'"><xsl:value-of select="name"/>: <xsl:call-template name="firstCharToUpperCase"><xsl:with-param name="str" select="toTableName"/></xsl:call-template> = undefined;
</xsl:when>
<xsl:when test="relationshipType = 'ONE_TO_MANY'"><xsl:value-of select="name"/>: <xsl:call-template name="firstCharToUpperCase"><xsl:with-param name="str" select="toTableName"/></xsl:call-template>[] = [];
</xsl:when>
</xsl:choose>
</xsl:if>
</xsl:for-each>
constructor(data: <xsl:call-template name="firstCharToUpperCase"><xsl:with-param name="str" select="name"/></xsl:call-template>DTO | Partial&lt;<xsl:call-template name="firstCharToUpperCase"><xsl:with-param name="str" select="name"/></xsl:call-template>DTO&gt;, selfOnly = false) {
if (!data) {
return;
}
<xsl:for-each select="columns">
<xsl:if test="not(expression) and name != 'updated' and name != 'created' and ($isCloudCode=1 or name != 'ownerId')">this.<xsl:value-of select="name"/> = typeof data.<xsl:value-of select="name"/> !== 'undefined' ? data.<xsl:value-of select="name"/> : undefined;
</xsl:if>
</xsl:for-each>
if (selfOnly) {
return;
}
<xsl:for-each select="relations">
<xsl:if test="$isCloudCode=1 or name!='organization'">
<xsl:choose>
<xsl:when test="relationshipType = 'ONE_TO_ONE'">this.<xsl:value-of select="name"/> = data.<xsl:value-of select="name"/>? new <xsl:call-template name="firstCharToUpperCase"><xsl:with-param name="str" select="toTableName"/></xsl:call-template>(data.<xsl:value-of select="name"/>) : null;
</xsl:when>
<xsl:when test="relationshipType = 'ONE_TO_MANY'">this.<xsl:value-of select="name"/> = data.<xsl:value-of select="name"/>?.length ? data.<xsl:value-of select="name"/>.map(item => new <xsl:call-template name="firstCharToUpperCase"><xsl:with-param name="str" select="toTableName"/></xsl:call-template>(item)) : [];
</xsl:when>
</xsl:choose>
</xsl:if>
</xsl:for-each>
}
public toDTO(selfOnly: boolean = false): <xsl:call-template name="firstCharToUpperCase"><xsl:with-param name="str" select="name"/></xsl:call-template>DTO {
return {
<xsl:for-each select="columns">
<xsl:if test="not(expression) and name != 'updated' and name != 'created' and ($isCloudCode=1 or name != 'ownerId')"><xsl:value-of select="name"/>: this.<xsl:value-of select="name"/>,
</xsl:if>
</xsl:for-each>
<xsl:for-each select="relations">
<xsl:if test="$isCloudCode=1 or name!='organization'">
<xsl:choose>
<xsl:when test="relationshipType = 'ONE_TO_ONE'"><xsl:value-of select="name"/>: selfOnly ? null : (this.<xsl:value-of select="name"/>?.toDTO() ?? null),
</xsl:when>
<xsl:when test="relationshipType = 'ONE_TO_MANY'"><xsl:value-of select="name"/>: selfOnly ? [] : this.<xsl:value-of select="name"/>.map(item => item.toDTO()),
</xsl:when>
</xsl:choose>
</xsl:if>
</xsl:for-each>
}
}
<xsl:if test="not(name = 'Users')">
private get storage() {
return Backendless.Data.of(<xsl:call-template name="firstCharToUpperCase"><xsl:with-param name="str" select="name"/></xsl:call-template>);
}
async save()
{
const newItem = await this.storage.save(this.toDTO());
(this as any).objectId = newItem.objectId;
return newItem;
};
remove()
{
return this.storage.remove( this ).then(result => {
(this as any).objectId = null;
return result;
});
};
<xsl:for-each select="relations">
<xsl:if test="$isCloudCode=1 or name!='organization'">
async load<xsl:call-template name="firstCharToUpperCase"><xsl:with-param name="str" select="name"/></xsl:call-template>()
{
if(typeof this.<xsl:value-of select="name"/> === 'undefined' ) {
const query = Backendless.LoadRelationsQueryBuilder.create();
query.setRelationName('<xsl:value-of select="name"/>')
.setRelationModel(<xsl:call-template name="firstCharToUpperCase"><xsl:with-param name="str" select="toTableName"/></xsl:call-template>);
const result = await this.storage.loadRelations&lt;<xsl:call-template name="firstCharToUpperCase"><xsl:with-param name="str" select="toTableName"/></xsl:call-template>&gt;(this, query);
if (result) {
<xsl:choose>
<xsl:when test="relationshipType = 'ONE_TO_ONE'">
if (result?.length > 1) {
console.warn('[backendless] retrieved multiple items while expecting only one item as the realtion is of type one-to-one');
} else {
this.<xsl:value-of select="name"/> = result[0];
}</xsl:when>
<xsl:when test="relationshipType = 'ONE_TO_MANY'">
this.<xsl:value-of select="name"/> = result;</xsl:when>
</xsl:choose>
}
}
return this.<xsl:value-of select="name"/>;
};
</xsl:if>
</xsl:for-each>
</xsl:if>
}
</xsl:for-each>
let isBackendlessInitialized = false;
let isBackendlessTypesMapped = false;
export const mapTypes = () => {
if (isBackendlessTypesMapped) {
return;
}
isBackendlessTypesMapped = true;
// map tables to classes
<xsl:for-each select="backendless-codegen/application/tables/table">Backendless.Data.mapTableToClass('<xsl:value-of select="name"/>', <xsl:call-template name="firstCharToUpperCase"><xsl:with-param name="str" select="name"/></xsl:call-template>);
</xsl:for-each>
}
export const initializeBackendless = (options: {
serverUrl?: string,
applicationId: string,
apiKey: string
}) => {
if (isBackendlessInitialized) {
throw new Error(`Backendless was already initiated, cannot initiate again`);
}
isBackendlessInitialized = true;
if (options.serverUrl) {
Backendless.serverURL = options.serverUrl
}
mapTypes();
Backendless.initApp(options.applicationId, options.apiKey);
}
</xsl:template>
<xsl:template match="/">
<folder name="{$projectName}">
<file name="configuration.ts">
<xsl:call-template name="initApp"/>
</file>
<file name="ts-sdk.ts">
<xsl:call-template name="generate-sdk"><xsl:with-param name="isCloudCode" select="0"/></xsl:call-template>
</file>
<file name="ts-cloud-code-sdk.ts">
<xsl:call-template name="generate-sdk"><xsl:with-param name="isCloudCode" select="1"/></xsl:call-template>
</file>
</folder>
</xsl:template>
</xsl:stylesheet>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment