diff --git a/README.md b/README.md index 72349cf..866fa0a 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ There are several ways you can define a query to the SQL Database. But, eventual - **filter** - defines a filter expression e.g. `country = 'uk' and city = 'London'` or you can use parameters and have filter as `country = @country AND city = @city` and provide parameters as an object`{country: 'UK', city: 'London'}`. And you can use SQL functions as well: e.g.: `cast(TransactionTime as Date) = '2021-11-21'` - **orderBy** - define a columns to sort e.g.: `OrderDate DESC, OrderId ASC - **top**` - specify the number of records to return. -- **join** - combine rows from two or more tables, based on a related column between them. You can define array `[JoinType, TableToJoin, JoinCondition]` or: `['InnerJoin', 'Customers c', 'c.CustomerId = t.CustomerId']` +- **join** - combine rows from two or more tables, based on a related column between them. You can define array `[JoinType, TableToJoin, JoinCondition, JoinCondition2]` or: `['InnerJoin', 'Customers c', 'c.CustomerId = t.CustomerId']` ### Query Examples @@ -291,6 +291,17 @@ Executes `sql` script in the server and returns either raw table or array of obj ``` +## Cancellation + +You can cancel any of your http request by setting `AbortController` + +```js +setAbortController(abortController: AbortController): SqlDataApi +``` + +setAbortController is part of chaining method of sqlDataApi or you can pass it to the constructor and factory method + + ## License A permissive MIT License (c) FalconSoft Ltd. diff --git a/package.json b/package.json index 83e9938..6a26ea3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sql-data-api", - "version": "0.3.1", + "version": "0.3.12", "description": "Sql Data Api client for Javascript", "keywords": [ "sql", @@ -35,8 +35,8 @@ }, "homepage": "https://github.com/FalconSoft/sql-data-api-client-js", "dependencies": { - "axios": "^0.21.1", - "datapipe-js": "^0.3.19" + "axios": "^1.6.7", + "datapipe-js": "^0.3.31" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^4.8.2", diff --git a/src/db-type-converter-maps.ts b/src/db-type-converter-maps.ts index 5bd97d4..5f47817 100644 --- a/src/db-type-converter-maps.ts +++ b/src/db-type-converter-maps.ts @@ -1,5 +1,5 @@ import { DataTypeName } from "datapipe-js"; -import { PostgreSqlDataTypes, SQLDataTypes } from "./db-types"; +import { PostgreSqlDataTypes, SQLDataTypes, SybaseAseDataTypes } from "./db-types"; export const SqlTypesToDataTypesMap: Record = { @@ -23,6 +23,27 @@ export const SqlTypesToDataTypesMap: Record = { [SQLDataTypes.BIT]: DataTypeName.Boolean }; +export const SybaseAseTypesToDataTypesMap: Record = { + [SybaseAseDataTypes.TEXT]: DataTypeName.LargeString, + [SybaseAseDataTypes.CHAR]: DataTypeName.String, + [SybaseAseDataTypes.VARCHAR]: DataTypeName.String, + [SybaseAseDataTypes.NVARCHAR]: DataTypeName.String, + [SybaseAseDataTypes.BINARY]: DataTypeName.LargeString, + [SybaseAseDataTypes.VARBINARY]: DataTypeName.LargeString, + [SybaseAseDataTypes.DATE]: DataTypeName.Date, + [SybaseAseDataTypes.SMALLDATETIME]: DataTypeName.DateTime, + [SybaseAseDataTypes.DATETIME]: DataTypeName.DateTime, + [SybaseAseDataTypes.DECIMAL]: DataTypeName.FloatNumber, + [SybaseAseDataTypes.FLOAT]: DataTypeName.FloatNumber, + [SybaseAseDataTypes.MONEY]: DataTypeName.FloatNumber, + [SybaseAseDataTypes.NUMERIC]: DataTypeName.FloatNumber, + [SybaseAseDataTypes.REAL]: DataTypeName.FloatNumber, + [SybaseAseDataTypes.SMALLINT]: DataTypeName.WholeNumber, + [SybaseAseDataTypes.BIGINT]: DataTypeName.BigIntNumber, + [SybaseAseDataTypes.INT]: DataTypeName.WholeNumber, + [SybaseAseDataTypes.TINYINT]: DataTypeName.WholeNumber, + [SybaseAseDataTypes.BIT]: DataTypeName.Boolean +}; export const PostgreSqlTypesToDataTypesMap: Record = { [PostgreSqlDataTypes.BOOLEAN]: DataTypeName.Boolean, [PostgreSqlDataTypes.SMALLINT]: DataTypeName.WholeNumber, @@ -65,4 +86,15 @@ export const DataTypeToSqlTypesMap: Record = { [DataTypeName.Boolean]: SQLDataTypes.BIT, [DataTypeName.BigIntNumber]: SQLDataTypes.BIGINT, [DataTypeName.LargeString]: SQLDataTypes.TEXT +}; + +export const DataTypeToSybaseAseTypesMap: Record = { + [DataTypeName.WholeNumber]: SybaseAseDataTypes.INT, + [DataTypeName.Date]: SybaseAseDataTypes.DATE, + [DataTypeName.DateTime]: SybaseAseDataTypes.DATETIME, + [DataTypeName.FloatNumber]: SybaseAseDataTypes.FLOAT, + [DataTypeName.String]: SybaseAseDataTypes.VARCHAR, + [DataTypeName.Boolean]: SybaseAseDataTypes.BIT, + [DataTypeName.BigIntNumber]: SybaseAseDataTypes.BIGINT, + [DataTypeName.LargeString]: SybaseAseDataTypes.TEXT }; \ No newline at end of file diff --git a/src/db-type-converter.ts b/src/db-type-converter.ts index e3a5749..5355f6a 100644 --- a/src/db-type-converter.ts +++ b/src/db-type-converter.ts @@ -1,48 +1,85 @@ import { DataTypeName } from "datapipe-js"; import { - DataTypeToPostgreSqlTypesMap, DataTypeToSqlTypesMap, - PostgreSqlTypesToDataTypesMap, SqlTypesToDataTypesMap + DataTypeToPostgreSqlTypesMap, + DataTypeToSqlTypesMap, + DataTypeToSybaseAseTypesMap, + PostgreSqlTypesToDataTypesMap, + SqlTypesToDataTypesMap, + SybaseAseTypesToDataTypesMap, } from "./db-type-converter-maps"; -import { DbConnectionType, PostgreSqlDataTypes, SQLDataTypes } from "./db-types"; +import { + DbConnectionType, + PostgreSqlDataTypes, + SQLDataTypes, + SybaseAseDataTypes, +} from "./db-types"; export class DbTypeConverter { - toDbType(dbType: DataTypeName, connectionType: DbConnectionType): SQLDataTypes | PostgreSqlDataTypes { - if (connectionType === 'SqlServer' || connectionType === 'SqlServerSchema'){ - return this.toSqlServerType(dbType); - } - - if (connectionType === 'PostgreSql'){ - return this.toPostgreSqlType(dbType); - } + toDbType( + dbType: DataTypeName, + connectionType: DbConnectionType + ): SQLDataTypes | PostgreSqlDataTypes | SybaseAseDataTypes { + if ( + connectionType === "SqlServer" || + connectionType === "SqlServerSchema" + ) { + return this.toSqlServerType(dbType); + } - throw new Error('Not Supported ConnectionType'); + if (connectionType === "PostgreSql") { + return this.toPostgreSqlType(dbType); } - fromDbType(dbType: SQLDataTypes | PostgreSqlDataTypes, connectionType: DbConnectionType): DataTypeName { - if (connectionType === 'SqlServer' || connectionType === 'SqlServerSchema'){ - return this.fromSqlServerType(dbType as SQLDataTypes); - } + if (connectionType === "SybaseAse") { + return this.toSybaseAseSqlType(dbType); + } - if (connectionType === 'PostgreSql'){ - return this.fromPostgreSqlType(dbType as PostgreSqlDataTypes); - } + throw new Error("Not Supported ConnectionType"); + } - throw new Error('Not Supported ConnectionType'); + fromDbType( + dbType: SQLDataTypes | PostgreSqlDataTypes | SybaseAseDataTypes, + connectionType: DbConnectionType + ): DataTypeName { + if ( + connectionType === "SqlServer" || + connectionType === "SqlServerSchema" + ) { + return this.fromSqlServerType(dbType as SQLDataTypes); } - toSqlServerType(dbType: DataTypeName): SQLDataTypes { - return DataTypeToSqlTypesMap[dbType]; + if (connectionType === "PostgreSql") { + return this.fromPostgreSqlType(dbType as PostgreSqlDataTypes); } - toPostgreSqlType(dbType: DataTypeName): PostgreSqlDataTypes { - return DataTypeToPostgreSqlTypesMap[dbType]; + if (connectionType === "SybaseAse") { + return this.fromSybaseAseSqlType(dbType as SybaseAseDataTypes); } - fromSqlServerType(dbType: SQLDataTypes): DataTypeName { - return SqlTypesToDataTypesMap[dbType]; - } + throw new Error("Not Supported ConnectionType"); + } - fromPostgreSqlType(dbType: PostgreSqlDataTypes): DataTypeName { - return PostgreSqlTypesToDataTypesMap[dbType]; - } -} \ No newline at end of file + toSqlServerType(dbType: DataTypeName): SQLDataTypes { + return DataTypeToSqlTypesMap[dbType]; + } + + toSybaseAseSqlType(dbType: DataTypeName): SybaseAseDataTypes { + return DataTypeToSybaseAseTypesMap[dbType]; + } + + toPostgreSqlType(dbType: DataTypeName): PostgreSqlDataTypes { + return DataTypeToPostgreSqlTypesMap[dbType]; + } + + fromSqlServerType(dbType: SQLDataTypes): DataTypeName { + return SqlTypesToDataTypesMap[dbType]; + } + + fromSybaseAseSqlType(dbType: SybaseAseDataTypes): DataTypeName { + return SybaseAseTypesToDataTypesMap[dbType]; + } + + fromPostgreSqlType(dbType: PostgreSqlDataTypes): DataTypeName { + return PostgreSqlTypesToDataTypesMap[dbType]; + } +} diff --git a/src/db-types.ts b/src/db-types.ts index 46f8049..74a9ff5 100644 --- a/src/db-types.ts +++ b/src/db-types.ts @@ -1,79 +1,125 @@ -export type DbConnectionType = 'SqlServer' | 'SqlServerSchema' | 'PostgreSql' | 'Oracle' | 'MySql'; +export type DbConnectionType = + | "SqlServer" + | "SqlServerSchema" + | "PostgreSql" + | "Oracle" + | "MySql" + | "SybaseAse"; export enum SQLDataTypes { - VARCHAR = 'varchar', - NVARCHAR = 'nvarchar', - FLOAT = 'float', - DATE = 'date', - DATETIME2 = 'datetime2', - DATETIME = 'datetime', - INT = 'int', + VARCHAR = "varchar", + NVARCHAR = "nvarchar", + FLOAT = "float", + DATE = "date", + DATETIME2 = "datetime2", + DATETIME = "datetime", + INT = "int", - TEXT = 'text', - CHAR = 'char', - BINARY = 'binary', - VARBINARY = 'varbinary', + TEXT = "text", + CHAR = "char", + BINARY = "binary", + VARBINARY = "varbinary", - BIT = 'bit', - TINYINT = 'tinyint', - SMALLINT = 'smallint', - BIGINT = 'bigint', - DECIMAL = 'decimal', - REAL = 'real', - NUMERIC = 'numeric', + BIT = "bit", + TINYINT = "tinyint", + SMALLINT = "smallint", + BIGINT = "bigint", + DECIMAL = "decimal", + REAL = "real", + NUMERIC = "numeric", +} + +export enum SybaseAseDataTypes { + VARCHAR = "varchar", + NVARCHAR = "nvarchar", + FLOAT = "float", + DATE = "date", + DATETIME = "datetime", + SMALLDATETIME = "smalldatetime", + + INT = "int", + + TEXT = "text", + CHAR = "char", + BINARY = "binary", + VARBINARY = "varbinary", + + BIT = "bit", + TINYINT = "tinyint", + SMALLINT = "smallint", + BIGINT = "bigint", + DECIMAL = "decimal", + REAL = "real", + NUMERIC = "numeric", + MONEY = "money", } export enum PostgreSqlDataTypes { - VARCHAR = 'varchar', - INTEGER = 'int', - DOUBLE = 'float8', - DATE = 'date', - TIMESTAMP = 'timestamp', - BOOLEAN = 'bool', - SMALLINT = 'int2', - BIGINT = 'int8', - REAL = 'float4', - INTEGER4 = 'int4', - NUMERIC = 'numeric', - MONEY = 'money', - TEXT = 'text', - CHAR = 'char', - BPCHAR = 'bpchar', - JSONB = 'jsonb', - XML = 'xml', - JSON = 'json' + VARCHAR = "varchar", + INTEGER = "int", + DOUBLE = "float8", + DATE = "date", + TIMESTAMP = "timestamp", + BOOLEAN = "bool", + SMALLINT = "int2", + BIGINT = "int8", + REAL = "float4", + INTEGER4 = "int4", + NUMERIC = "numeric", + MONEY = "money", + TEXT = "text", + CHAR = "char", + BPCHAR = "bpchar", + JSONB = "jsonb", + XML = "xml", + JSON = "json", - // , CITEXT = 'Citext', + // , CITEXT = 'Citext', - // POINT = 'Point', - // LSEG = 'LSeg', - // PATH = 'Path', - // POLYGON = 'Polygon', - // LINE = 'Line', - // CIRCLE = 'Circle', - // BOX = 'Box', - // BIT = 'Bit', - // VARBIT = 'Varbit', - // HSTORE = 'Hstore', - // UUID = 'Uuid', - // CIDR = 'Cidr', - // INET = 'Inet', - // MACADDR = 'MacAddr', - // TSQUERY = 'TsQuery', - // TSVECTOR = 'TsVector', + // POINT = 'Point', + // LSEG = 'LSeg', + // PATH = 'Path', + // POLYGON = 'Polygon', + // LINE = 'Line', + // CIRCLE = 'Circle', + // BOX = 'Box', + // BIT = 'Bit', + // VARBIT = 'Varbit', + // HSTORE = 'Hstore', + // UUID = 'Uuid', + // CIDR = 'Cidr', + // INET = 'Inet', + // MACADDR = 'MacAddr', + // TSQUERY = 'TsQuery', + // TSVECTOR = 'TsVector', - // TIMESTAMPTZ = 'TimestampTz', - // TIME = 'Time', - // TIMETZ = 'TimeTz', - // BYTEA = 'Bytea', - // OID = 'Oid', - // XID = 'Xid', - // CID = 'Cid', - // OIDVECTOR = 'Oidvector', - // NAME = 'Name', - // INTERNALCHAR = 'InternalChar', - // COMPOSITE = 'Composite', - // RANGE = 'Range', - // ENUM = 'Enum', - // ARRAY = 'Array' -}; + // TIMESTAMPTZ = 'TimestampTz', + // TIME = 'Time', + // TIMETZ = 'TimeTz', + // BYTEA = 'Bytea', + // OID = 'Oid', + // XID = 'Xid', + // CID = 'Cid', + // OIDVECTOR = 'Oidvector', + // NAME = 'Name', + // INTERNALCHAR = 'InternalChar', + // COMPOSITE = 'Composite', + // RANGE = 'Range', + // ENUM = 'Enum', + // ARRAY = 'Array' +} + +export enum JoinType { + InnerJoin = "InnerJoin", + LeftJoin = "LeftJoin", + RightJoin = "RightJoin", + FullJoin = "FullJoin", +} + +export interface TableJoinDto { + tableName: string; + tableAlias?: string; + joinType: JoinType; + joinCondition: string; + joinCondition2?: string; +} diff --git a/src/sql-data.api.ts b/src/sql-data.api.ts index 0a06948..93ac37c 100644 --- a/src/sql-data.api.ts +++ b/src/sql-data.api.ts @@ -1,4 +1,4 @@ -import axios, { AxiosRequestConfig } from "axios"; +import axios, { AxiosRequestConfig, isCancel } from "axios"; import { PrimitivesObject, PrimitiveType, @@ -9,6 +9,7 @@ import { } from "datapipe-js"; import { dateToString, fromTable, toTable } from "datapipe-js/utils"; import { DbTypeConverter } from "./db-type-converter"; +import { JoinType, TableJoinDto } from "./db-types"; export * from "./db-types"; @@ -90,7 +91,7 @@ export interface SqlReadQueryInfo { * Examples: * - ['InnerJoin', 'Customers c', 't.CustomerId = c.CustomerID'] - inner joins mainTable to Cutomers table */ - joins?: [JoinType, string, string][]; + joins?: [JoinType, string, string, string?][]; } /** @@ -158,14 +159,18 @@ export async function authenticate( username: string, password: string ): Promise { - SqlDataApi.BearerToken = ( - (await httpRequest( - "POST", - `${SqlDataApi.BaseUrl}/api/security/authenticate`, - { username, password }, - { headers: appHttpHeaders } - )) as { token: string } - ).token; + const res = await httpRequest( + "POST", + `${SqlDataApi.BaseUrl}/api/security/authenticate`, + { username, password }, + { headers: appHttpHeaders } + ); + + if (res.errorMessage) { + throw new Error(res.errorMessage); + } + + SqlDataApi.BearerToken = (res.data as { token: string }).token; return true; } @@ -177,7 +182,8 @@ export async function authenticate( */ export function sqlDataApi( connectionName: string, - config?: { baseUrl?: string; userAccessToken?: string; bearerToken?: string } + config?: { baseUrl?: string; userAccessToken?: string; bearerToken?: string }, + abortSignal?: AbortSignal ): SqlDataApi { const cfg = { userAccessToken: config?.userAccessToken || SqlDataApi.UserAccessToken, @@ -186,7 +192,8 @@ export function sqlDataApi( return new SqlDataApi( config?.baseUrl || SqlDataApi.BaseUrl, connectionName, - cfg + cfg, + abortSignal ); } @@ -199,7 +206,7 @@ export function httpRequest( url: string, body?: TRequest, config?: Record -): Promise { +): Promise> { const requestConfig: AxiosRequestConfig = { method, url, ...(config || {}) }; if (body) { @@ -213,23 +220,38 @@ export function httpRequest( } return axios.request(requestConfig).then( - (r: ServerResponse): TResponse => r.data, + (r: ServerResponse): ServerResponse => ({ + data: r.data, + isOk: true, + status: r.status, + statusText: r.statusText, + }), (e) => { // making an error more informative. // this was a reason why we switched to axios, as it gives us a real exception details, // beyond a statusText const response = e.response || {}; - let errMessage = + let errorMessage = (response.data || {}).message || response.data || response.statusText || "Http Connection Error"; - if (typeof errMessage === "object") { - errMessage = JSON.stringify(errMessage); + + if (typeof errorMessage === "object") { + errorMessage = JSON.stringify(errorMessage); } - const err = Error(errMessage); - Object.assign(err, response); - throw err; + + if (isCancel(e)) { + errorMessage = "Request cancelled"; + } + + return { + isOk: false, + status: response.status, + statusText: response.statusText, + data: null as any, + errorMessage, + }; } ); } @@ -238,7 +260,12 @@ export function httpGet( url: string, config?: Record ): Promise { - return httpRequest("GET", url, null, config); + return httpRequest("GET", url, null, config).then((r) => { + if (!r.errorMessage) { + return r.data as TResponse; + } + throw new Error(r.errorMessage); + }); } export function httpGetText( @@ -254,7 +281,12 @@ export function httpGetText( config.headers = headers; - return httpRequest("GET", url, null, config); + return httpRequest("GET", url, null, config).then((r) => { + if (!r.errorMessage) { + return r.data as string; + } + throw new Error(r.errorMessage); + }); } export function httpPost( @@ -262,7 +294,12 @@ export function httpPost( body: TRequest, config?: Record ): Promise { - return httpRequest("POST", url, body, config); + return httpRequest("POST", url, body, config).then((r) => { + if (!r.errorMessage) { + return r.data as TResponse; + } + throw new Error(r.errorMessage); + }); } export function httpPut( @@ -270,7 +307,12 @@ export function httpPut( body: TRequest, config?: Record ): Promise { - return httpRequest("PUT", url, body, config); + return httpRequest("PUT", url, body, config).then((r) => { + if (!r.errorMessage) { + return r.data as TResponse; + } + throw new Error(r.errorMessage); + }); } export function httpDelete( @@ -278,7 +320,12 @@ export function httpDelete( body: TRequest, config?: Record ): Promise { - return httpRequest("DELETE", url, body, config); + return httpRequest("DELETE", url, body, config).then((r) => { + if (!r.errorMessage) { + return r.data as TResponse; + } + throw new Error(r.errorMessage); + }); } /** @@ -298,7 +345,8 @@ export class SqlDataApi { constructor( private baseUrl: string, private connectionName: string, - config: { userAccessToken?: string; bearerToken?: string } + config: { userAccessToken?: string; bearerToken?: string }, + private abortSignal?: AbortSignal ) { this.userAccessToken = config?.userAccessToken; this.bearerToken = config?.bearerToken; @@ -321,6 +369,27 @@ export class SqlDataApi { return this; } + // fluent API methods + /** + * Append filter (with AND) and returns same + * - use this method for a fluent API + * @param filter filter string e.g. "country='UK' or city=@city" + * @param filterParams filter parameters e.g. {city: 'London'} + * @returns same SqlDataApi instance + */ + andFilter( + filter: string, + filterParams?: Record + ): SqlDataApi { + const cf = this.queryInfo.filter || ""; + this.queryInfo.filter = `${cf} ${cf ? " AND " : ""} ${filter}`.trim(); + this.queryInfo.filterParams = { + ...(this.queryInfo.filterParams || {}), + ...filterParams, + }; + return this; + } + /** * sets fields that you want to setup * - use this method for a fluent API @@ -357,12 +426,18 @@ export class SqlDataApi { join( joinType: JoinType, tableName: string, - joinCondition: string + joinCondition: string, + joinCondition2?: string ): SqlDataApi { if (!this.queryInfo.joins) { this.queryInfo.joins = []; } - this.queryInfo.joins.push([joinType, tableName, joinCondition]); + this.queryInfo.joins.push([ + joinType, + tableName, + joinCondition, + joinCondition2, + ]); return this; } @@ -409,6 +484,11 @@ export class SqlDataApi { ); } + setAbortSignal(signal: AbortSignal): SqlDataApi { + this.abortSignal = signal; + return this; + } + /** * Updates data in the table based on filter parameters * @returns Number of rows affected @@ -439,9 +519,21 @@ export class SqlDataApi { url += `?$accessToken=${this.userAccessToken}`; } - const result = (await httpRequest("POST", url, dto, { - headers: { ...appHttpHeaders, ...headers }, - })) as number; + const httpConfig: AxiosRequestConfig = { + headers: Object.assign(appHttpHeaders, headers), + }; + + if (this.abortSignal) { + httpConfig.signal = this.abortSignal; + } + + const res = await httpRequest("POST", url, dto, httpConfig); + + if (res.errorMessage) { + throw new Error(res.errorMessage); + } + + const result = res.data as number; return result; } @@ -470,9 +562,21 @@ export class SqlDataApi { url += `?$accessToken=${this.userAccessToken}`; } - const result = (await httpRequest("POST", url, dto, { - headers: { ...appHttpHeaders, ...headers }, - })) as number; + const httpConfig: AxiosRequestConfig = { + headers: Object.assign(appHttpHeaders, headers), + }; + + if (this.abortSignal) { + httpConfig.signal = this.abortSignal; + } + + const res = await httpRequest("POST", url, dto, httpConfig); + + if (res.errorMessage) { + throw new Error(res.errorMessage); + } + + const result = res.data as number; return result; } @@ -543,9 +647,21 @@ export class SqlDataApi { url += `?$accessToken=${this.userAccessToken}`; } - const result = (await httpRequest("POST", url, dto, { - headers: { ...appHttpHeaders, ...headers }, - })) as number; + const httpConfig: AxiosRequestConfig = { + headers: Object.assign(appHttpHeaders, headers), + }; + + if (this.abortSignal) { + httpConfig.signal = this.abortSignal; + } + + const res = await httpRequest("POST", url, dto, httpConfig); + + if (res.errorMessage) { + throw new Error(res.errorMessage); + } + + const result = res.data as number; return result; } @@ -587,9 +703,21 @@ export class SqlDataApi { url += `?$accessToken=${this.userAccessToken}`; } - const result = (await httpRequest("POST", url, dto, { - headers: { ...appHttpHeaders, ...headers }, - })) as SqlQueryResponse; + const httpConfig: AxiosRequestConfig = { + headers: Object.assign(appHttpHeaders, headers), + }; + + if (this.abortSignal) { + httpConfig.signal = this.abortSignal; + } + + const res = await httpRequest("POST", url, dto, httpConfig); + + if (res.errorMessage) { + throw new Error(res.errorMessage); + } + + const result = res.data as SqlQueryResponse; return result; } @@ -642,12 +770,26 @@ export class SqlDataApi { if (!items || !items.length) { if (itemsToDelete?.length) { - return (await httpRequest( + const httpConfig: AxiosRequestConfig = { + headers: Object.assign(appHttpHeaders, headersValue), + }; + + if (this.abortSignal) { + httpConfig.signal = this.abortSignal; + } + + const res = await httpRequest( "POST", url, { itemsToDelete }, - { headers: { ...appHttpHeaders, ...headersValue } } - )) as SqlSaveStatus; + httpConfig + ); + + if (res.errorMessage) { + throw new Error(res.errorMessage); + } + + return res.data as SqlSaveStatus; } else { return status; } @@ -691,9 +833,22 @@ export class SqlDataApi { url += `?$accessToken=${this.userAccessToken}`; } - const singleStatus = (await httpRequest("POST", url, body, { + const httpConfig: AxiosRequestConfig = { headers: Object.assign(appHttpHeaders, headersValue), - })) as SqlSaveStatus; + }; + + if (this.abortSignal) { + if (this.abortSignal?.aborted) break; + httpConfig.signal = this.abortSignal; + } + + const res = await httpRequest("POST", url, body, httpConfig); + + if (res.errorMessage) { + throw new Error(res.errorMessage); + } + + const singleStatus = res.data as SqlSaveStatus; if (typeof batchSavedFunc === "function") { batchSavedFunc(currentIndex + 1, singleStatus); @@ -718,7 +873,7 @@ export class SqlDataApi { if (Array.isArray(val)) { return val.map((v) => scalarToPrimitive(v)) as PrimitiveType[]; } - return val instanceof Date ? dateToString(val) : val; + return val instanceof Date ? `dt(${dateToString(val)})` : val; }; const result: PrimitivesObject = {}; @@ -792,14 +947,16 @@ export class SqlDataApi { joins: TableJoinDto[], joinType: JoinType, tableOrViewWithAlias: string, - joinCondition: string + joinCondition: string, + joinCondition2?: string ): TableJoinDto[] { const nameAlias = extractNameAndAlias(tableOrViewWithAlias); joins.push({ - joinCondition, joinType, tableAlias: nameAlias.alias, tableName: nameAlias.name, + joinCondition, + joinCondition2, }); return joins; } @@ -809,7 +966,7 @@ export class SqlDataApi { if (queryInfo.joins && queryInfo.joins.length) { for (const j of queryInfo.joins) { - join(tablesJoin, j[0] as JoinType, j[1], j[2]); + join(tablesJoin, j[0] as JoinType, j[1], j[2], j[3]); } } @@ -818,14 +975,14 @@ export class SqlDataApi { : undefined; const request = { - select: fields, - filterString: queryInfo.filter, - filterParameters: filterParams, - skip: queryInfo.skip, - top: queryInfo.top, - orderBy: queryInfo.orderBy, - mainTableAlias: mainTable.alias || queryInfo.mainTableAlias, - tablesJoin, + select: fields?.length ? fields : undefined, + filterString: queryInfo.filter || undefined, + filterParameters: filterParams || undefined, + skip: queryInfo.skip || undefined, + top: queryInfo.top || undefined, + orderBy: queryInfo.orderBy || undefined, + mainTableAlias: mainTable.alias || queryInfo.mainTableAlias || undefined, + tablesJoin: tablesJoin?.length ? tablesJoin : undefined, } as RequestSqlQueryInfo; if (!this.connectionName?.length) { @@ -844,9 +1001,21 @@ export class SqlDataApi { url += `?$accessToken=${this.userAccessToken}`; } - const response = (await httpRequest("POST", url, request, { + const httpConfig = { headers: { ...appHttpHeaders, ...headers }, - })) as SqlQueryResponse; + } as AxiosRequestConfig; + + if (this.abortSignal) { + httpConfig.signal = this.abortSignal; + } + + const res = await httpRequest("POST", url, request, httpConfig); + + if (res.errorMessage) { + throw new Error(res.errorMessage); + } + + const response = res.data as SqlQueryResponse; return response.table as TableDto; } } @@ -860,6 +1029,10 @@ interface ExecuteSqlDto { interface ServerResponse { data: T; + isOk?: boolean; + status: number; + statusText: string; + errorMessage?: string; } interface RequestSqlQueryInfo { @@ -872,17 +1045,3 @@ interface RequestSqlQueryInfo { mainTableAlias?: string; tablesJoin?: TableJoinDto[]; } - -enum JoinType { - InnerJoin = "InnerJoin", - LeftJoin = "LeftJoin", - RightJoin = "RightJoin", - FullJoin = "FullJoin", -} - -interface TableJoinDto { - tableName: string; - tableAlias?: string; - joinType: JoinType; - joinCondition: string; -}