From b9ab2884ea132254ab3df50ddb89ee1fe3409345 Mon Sep 17 00:00:00 2001 From: bfeigin Date: Fri, 21 Mar 2025 10:42:27 -0700 Subject: [PATCH 1/9] structure for ts building --- index.ts | 135 ++++++++------ lib/events/index.ts | 40 ++-- lib/favorites/endpoints/ListFavorites.ts | 37 ++++ lib/favorites/index.ts | 45 +++++ lib/favorites/sharedTypes.ts | 32 ++++ lib/favorites/types.ts | 5 + lib/types/ApiUrls.ts | 20 -- lib/types/CreateOptions.ts | 33 +++- lib/utils/apis.ts | 63 ++++--- lib/utils/createApiProvider.ts | 23 +++ lib/utils/httpRequestor.js | 224 ++++++++++++++++------- 11 files changed, 458 insertions(+), 199 deletions(-) create mode 100644 lib/favorites/endpoints/ListFavorites.ts create mode 100644 lib/favorites/index.ts create mode 100644 lib/favorites/sharedTypes.ts create mode 100644 lib/favorites/types.ts create mode 100644 lib/utils/createApiProvider.ts diff --git a/index.ts b/index.ts index 9a3e5b3..0b35b50 100644 --- a/index.ts +++ b/index.ts @@ -1,9 +1,9 @@ -import {CreateClient, CreateOptions} from "./lib/types"; -import { apiUrls } from "./lib/utils/apis"; +import { CreateClient, CreateOptions } from "./lib/types"; +import { apiUrlBySection } from "./lib/utils/apis"; import { createEvents } from "./lib/events"; -var _ = require('underscore'); -var winston = require('winston'); +var _ = require("underscore"); +var winston = require("winston"); // Possible TODO: Namespace parameters for different subcomponents // E.g. clientOptions.requestor.instance OR @@ -11,111 +11,124 @@ var winston = require('winston'); // w/ sub-paths maxRetryDurationSeconds and calcRetryBackoff function buildRequestor(clientOptions) { - if(clientOptions.requestor) return clientOptions.requestor; + if (clientOptions.requestor) return clientOptions.requestor; - var requestorConfig = - _.pick(clientOptions, 'maxRetryDurationSeconds', 'calcRetryBackoff'); + var requestorConfig = _.pick( + clientOptions, + "maxRetryDurationSeconds", + "calcRetryBackoff" + ); - if(requestorConfig.maxRetryDurationSeconds) - requestorConfig.maxRetryDurationMillis = requestorConfig.maxRetryDurationSeconds * 1000; + if (requestorConfig.maxRetryDurationSeconds) + requestorConfig.maxRetryDurationMillis = + requestorConfig.maxRetryDurationSeconds * 1000; requestorConfig.logger = buildLogger(clientOptions); - return require('./lib/utils/httpRequestor.js') - .create(requestorConfig); -}; + return require("./lib/utils/httpRequestor.js").create(requestorConfig); +} function buildLogger(clientOptions) { - if(hasMultipleLogOptions(clientOptions)) { + if (hasMultipleLogOptions(clientOptions)) { throw new Error( "Smartsheet client options may specify at most one of " + - "'logger', 'loggerContainer', and 'logLevel'."); + "'logger', 'loggerContainer', and 'logLevel'." + ); } - if(clientOptions.logger) return clientOptions.logger; + if (clientOptions.logger) return clientOptions.logger; - if(clientOptions.logLevel) return buildLoggerFromLevel(clientOptions.logLevel); + if (clientOptions.logLevel) + return buildLoggerFromLevel(clientOptions.logLevel); - if(clientOptions.loggerContainer) return buildLoggerFromContainer(clientOptions.loggerContainer); + if (clientOptions.loggerContainer) + return buildLoggerFromContainer(clientOptions.loggerContainer); return null; } function hasMultipleLogOptions(clientOptions) { - return (clientOptions.logger && clientOptions.loggerContainer) - || (clientOptions.logger && clientOptions.logLevel) - || (clientOptions.loggerContainer && clientOptions.logLevel); + return ( + (clientOptions.logger && clientOptions.loggerContainer) || + (clientOptions.logger && clientOptions.logLevel) || + (clientOptions.loggerContainer && clientOptions.logLevel) + ); } function buildLoggerFromLevel(logLevel) { - if(winston.levels[logLevel] == null) { + if (winston.levels[logLevel] == null) { throw new Error( - 'Smartsheet client received configuration with invalid log level ' + - `'${logLevel}'. Use one of the standard Winston log levels.`); + "Smartsheet client received configuration with invalid log level " + + `'${logLevel}'. Use one of the standard Winston log levels.` + ); } - return new (winston.Logger)({ + return new winston.Logger({ transports: [ new winston.transports.Console({ level: logLevel, showLevel: false, - label: 'Smartsheet' - }) - ] + label: "Smartsheet", + }), + ], }); } function buildLoggerFromContainer(container) { - if(container.has('smartsheet')) - return container.get('smartsheet'); + if (container.has("smartsheet")) return container.get("smartsheet"); else throw new Error( "Smartsheet client received a logger container, but could not find a logger named " + - "'smartsheet' inside."); + "'smartsheet' inside." + ); } -export const createClient: CreateClient = function(clientOptions) { +export const createClient: CreateClient = function (clientOptions) { var requestor = buildRequestor(clientOptions); var options: CreateOptions = { - apiUrls: apiUrls, + apiUrls: apiUrlBySection, requestor: requestor, clientOptions: { - accessToken: clientOptions.accessToken || process.env.SMARTSHEET_ACCESS_TOKEN, + accessToken: + clientOptions.accessToken || process.env.SMARTSHEET_ACCESS_TOKEN, userAgent: clientOptions.userAgent, - baseUrl: clientOptions.baseUrl - } + baseUrl: clientOptions.baseUrl, + }, }; return { - constants : require('./lib/utils/constants.js'), - contacts : require('./lib/contacts/').create(options), - events : createEvents(options), - favorites : require('./lib/favorites/').create(options), - folders : require('./lib/folders/').create(options), - groups : require('./lib/groups/').create(options), - home : require('./lib/home/').create(options), - images : require('./lib/images/').create(options), - reports : require('./lib/reports/').create(options), - request : require('./lib/request/').create(options), - search : require('./lib/search/').create(options), - server : require('./lib/server/').create(options), - sheets : require('./lib/sheets/').create(options), - sights : require('./lib/sights/').create(options), - templates : require('./lib/templates/').create(options), - tokens : require('./lib/tokens/').create(options), - users : require('./lib/users/').create(options), - webhooks : require('./lib/webhooks/').create(options), - workspaces : require('./lib/workspaces/').create(options) + constants: require("./lib/utils/constants.js"), + contacts: require("./lib/contacts/").create(options), + events: createEvents(options), + favorites: require("./lib/favorites/").create(options), + folders: require("./lib/folders/").create(options), + groups: require("./lib/groups/").create(options), + home: require("./lib/home/").create(options), + images: require("./lib/images/").create(options), + reports: require("./lib/reports/").create(options), + request: require("./lib/request/").create(options), + search: require("./lib/search/").create(options), + server: require("./lib/server/").create(options), + sheets: require("./lib/sheets/").create(options), + sights: require("./lib/sights/").create(options), + templates: require("./lib/templates/").create(options), + tokens: require("./lib/tokens/").create(options), + users: require("./lib/users/").create(options), + webhooks: require("./lib/webhooks/").create(options), + workspaces: require("./lib/workspaces/").create(options), }; }; export const smartSheetURIs = { - defaultBaseURI: 'https://api.smartsheet.com/2.0/', - govBaseURI: 'https://api.smartsheetgov.com/2.0/', - euBaseURI: 'https://api.smartsheet.eu/2.0/' -} - + defaultBaseURI: "https://api.smartsheet.com/2.0/", + govBaseURI: "https://api.smartsheetgov.com/2.0/", + euBaseURI: "https://api.smartsheet.eu/2.0/", +}; -export { CreateClient, CreateClientOptions, SmartsheetClient } from "./lib/types"; -export * from "./lib/events/types" +export { + CreateClient, + CreateClientOptions, + SmartsheetClient, +} from "./lib/types"; +export * from "./lib/events/types"; diff --git a/lib/events/index.ts b/lib/events/index.ts index 1978ff9..a2a9e61 100644 --- a/lib/events/index.ts +++ b/lib/events/index.ts @@ -1,27 +1,19 @@ -import { EventsApi, GetEventsOptions, GetEventsResponse } from './types'; -import {ClientOptions, CreateOptions, RequestCallback, RequestOptions} from "../types"; - -type OptionsToSend = Partial & { - url: string -} +import { EventsApi, GetEventsOptions, GetEventsResponse } from "./types"; +import { CreateOptions, RequestCallback, RequestOptions } from "../types"; +import { createApiProvider } from "../utils/createApiProvider"; +import { ApiSection } from "../utils/apis"; export const createEvents = (options: CreateOptions): EventsApi => { - const requester = options.requestor; - - let optionsToSend: OptionsToSend= { - url: options.apiUrls.events, - }; - - if (options.clientOptions) { - optionsToSend = { - ...optionsToSend, - ...options.clientOptions - } - } - - return { - getEvents: (options: RequestOptions, callback?: RequestCallback): Promise => { - return requester.get({ ...optionsToSend, ...options}, callback) - } - } + return createApiProvider( + options, + ApiSection.Events, + (requester, optionsToSend) => ({ + getEvents: ( + options: RequestOptions, + callback?: RequestCallback + ): Promise => { + return requester.get({ ...optionsToSend, ...options }, callback); + }, + }) + ); }; diff --git a/lib/favorites/endpoints/ListFavorites.ts b/lib/favorites/endpoints/ListFavorites.ts new file mode 100644 index 0000000..1c616a4 --- /dev/null +++ b/lib/favorites/endpoints/ListFavorites.ts @@ -0,0 +1,37 @@ +import { RequestCallback } from "../../types"; +import { + ApiCreator, + ApiEndpointCreator, + FavoriteItem, + Pagination, + PaginationResponse, +} from "../sharedTypes"; + +type IncludeOptions = string; + +type ListFavoritesParamsNonPaginated = { + includeAll: true; + include: IncludeOptions; +}; + +type ListFavoritesParamsPaginated = { + includeAll?: false; + include: IncludeOptions; +} & Pagination; + +type ListFavortesParams = + | ListFavoritesParamsPaginated + | ListFavoritesParamsNonPaginated; + +export type ListFavoritesResponse = PaginationResponse & { + data: FavoriteItem[]; +}; +export type ListFavoritesRequest = ( + params: ListFavortesParams, + callback: RequestCallback +) => {}; + +export const createListFavorites: ApiCreator = + (requestor, optionsToSend) => (params, callback) => { + return requestor.get({ ...optionsToSend, ...params }, callback); + }; diff --git a/lib/favorites/index.ts b/lib/favorites/index.ts new file mode 100644 index 0000000..1a772ae --- /dev/null +++ b/lib/favorites/index.ts @@ -0,0 +1,45 @@ +import { CreateOptions } from "../types"; +import { ApiSection } from "../utils/apis"; +import { createApiProvider } from "../utils/createApiProvider"; +import { createListFavorites } from "./endpoints/ListFavorites"; +import { ApiCreator } from "./sharedTypes"; +import { FavoritesApi } from "./types"; + +const buildFavoritesApi: ApiCreator = ( + requestor, + optionsToSend +) => { + return { + listFavorites: createListFavorites(requestor, optionsToSend), + }; +}; +//type FavoritesApi = { +// listFavorites : listFavorites, +// addItemsToFavorites : addItemsToFavorites, +// addSheetToFavorites : addSheetToFavorites, +// addFolderToFavorites : addFolderToFavorites, +// addReportToFavorites : addReportToFavorites, +// addTemplateToFavorites : addTemplateToFavorites, +// addSightToFavorites : addSightToFavorites, +// addWorkspaceToFavorites : addWorkspaceToFavorites, +// addMultipleToFavorites : addMultipleToFavorites, +// removeSheetFromFavorites : removeSheetFromFavorites, +// removeFolderFromFavorites : removeFolderFromFavorites, +// removeReportFromFavorites : removeReportFromFavorites, +// removeTemplateFromFavorites : removeTemplateFromFavorites, +// removeSightFromFavorites : removeSightFromFavorites, +// removeWorkspaceFromFavorites : removeWorkspaceFromFavorites, +// //convenience methods to remove multiples. +// //Uses the same as the singular remove methods. +// removeSheetsFromFavorites : removeSheetFromFavorites, +// removeFoldersFromFavorites : removeFolderFromFavorites, +// removeReportsFromFavorites : removeReportFromFavorites, +// removeTemplatesFromFavorites : removeTemplateFromFavorites, +// removeSightsFromFavorites : removeSightFromFavorites, +// removeWorkspacesFromFavorites : removeWorkspaceFromFavorites +// +//} + +export const createFavorites = (options: CreateOptions) => { + return createApiProvider(options, ApiSection.Favorites, buildFavoritesApi); +}; diff --git a/lib/favorites/sharedTypes.ts b/lib/favorites/sharedTypes.ts new file mode 100644 index 0000000..8572060 --- /dev/null +++ b/lib/favorites/sharedTypes.ts @@ -0,0 +1,32 @@ +import { OptionsToSend, Requestor } from "../types"; + +export type Pagination = { + page?: number; + pageSize?: number; +}; + +export type PaginationResponse = { + pageNumber: number; + pageSize?: number; + totalPages: number; + totalCount: number; +}; + +export enum FavoritableResource { + Folder = "folder", + Report = "report", + Sheet = "sheet", + Sight = "sight", + Template = "template", + Workspace = "workspace", +} + +export type FavoriteItem = { + objectId: string; + type: FavoritableResource; +}; + +export type ApiCreator = ( + requestor: Requestor, + optionsToSend: OptionsToSend +) => T; diff --git a/lib/favorites/types.ts b/lib/favorites/types.ts new file mode 100644 index 0000000..d9f26dc --- /dev/null +++ b/lib/favorites/types.ts @@ -0,0 +1,5 @@ +import { ListFavoritesRequest } from "./endpoints/ListFavorites"; + +export type FavoritesApi = { + listFavorites: ListFavoritesRequest; +}; diff --git a/lib/types/ApiUrls.ts b/lib/types/ApiUrls.ts index 640bed1..e69de29 100644 --- a/lib/types/ApiUrls.ts +++ b/lib/types/ApiUrls.ts @@ -1,20 +0,0 @@ -export interface ApiUrls { - contacts : string; - events : string; - favorites : string; - folders : string; - groups : string; - home : string; - imageUrls : string; - reports : string; - search : string; - server : string; - sheets : string; - sights : string; - templates : string; - templatesPublic : string; - token : string; - users : string; - webhooks : string; - workspaces : string; -} diff --git a/lib/types/CreateOptions.ts b/lib/types/CreateOptions.ts index 73c6053..214af6e 100644 --- a/lib/types/CreateOptions.ts +++ b/lib/types/CreateOptions.ts @@ -1,10 +1,33 @@ -import {ApiUrls} from "./ApiUrls"; -import {CreateClientOptions} from "./CreateClientOptions"; +import { ApiUrls } from "./ApiUrls"; +import { CreateClientOptions } from "./CreateClientOptions"; -export type ClientOptions = Pick +export type ClientOptions = Pick< + CreateClientOptions, + "accessToken" | "userAgent" | "baseUrl" +>; + +export type Requestor = { + get: (options: any, callback: any) => any; + put: (options: any, callback: any) => any; + post: (options: any, callback: any) => any; + postFile: (options: any, callback: any) => any; + delete: (options: any, callback: any) => any; + internal: { + buildHeaders: (options: any) => { + Accept: any; + "Content-Type": any; + "User-Agent": string; + }; + buildUrl: (options: any) => any; + }; +}; export interface CreateOptions { apiUrls: ApiUrls; - requestor: any; - clientOptions?: ClientOptions + requestor: Requestor; + clientOptions?: ClientOptions; } + +export type OptionsToSend = Partial & { + url: string; +}; diff --git a/lib/utils/apis.ts b/lib/utils/apis.ts index 03c0890..c34e7ae 100644 --- a/lib/utils/apis.ts +++ b/lib/utils/apis.ts @@ -1,22 +1,43 @@ -import {ApiUrls} from "../types"; -export const apiUrls: ApiUrls = { - contacts : 'contacts/', - events : 'events/', - favorites : 'favorites/', - folders : 'folders/', - groups : 'groups/', - home : 'home/', - imageUrls : 'imageurls/', - reports : 'reports/', - search : 'search/', - server : 'serverinfo/', - sheets : 'sheets/', - sights : 'sights/', - templates : 'templates/', - templatesPublic : 'templates/public', - token : 'token', - users : 'users/', - webhooks : 'webhooks/', - workspaces : 'workspaces/' -}; +export enum ApiSection { + Contacts = "contacts", + Events = "events", + Favorites = "favorites", + Folders = "folders", + Groups = "groups", + Home = "home", + ImageUrls = "imageurls", + Reports = "reports", + Search = "search", + Server = "serverinfo", + Sheets = "sheets", + Sights = "sights", + Templates = "templates", + TemplatesPublic = "templatespublic", + Token = "token", + Users = "users", + Webhooks = "webhooks", + Workspaces = "workspaces", +} +export const apiUrlBySection = { + [ApiSection.Contacts]: "contacts/", + [ApiSection.Events]: "events/", + [ApiSection.Favorites]: "favorites/", + [ApiSection.Folders]: "folders/", + [ApiSection.Groups]: "groups/", + [ApiSection.Home]: "home/", + [ApiSection.ImageUrls]: "imageurls/", + [ApiSection.Reports]: "reports/", + [ApiSection.Search]: "search/", + [ApiSection.Server]: "serverinfo/", + [ApiSection.Sheets]: "sheets/", + [ApiSection.Sights]: "sights/", + [ApiSection.Templates]: "templates/", + [ApiSection.TemplatesPublic]: "templates/public", + [ApiSection.Token]: "token", + [ApiSection.Users]: "users/", + [ApiSection.Webhooks]: "webhooks/", + [ApiSection.Workspaces]: "workspaces/", +} as const; + +export type ApiUrlPath = (typeof apiUrlBySection)[ApiSection]; diff --git a/lib/utils/createApiProvider.ts b/lib/utils/createApiProvider.ts new file mode 100644 index 0000000..3099791 --- /dev/null +++ b/lib/utils/createApiProvider.ts @@ -0,0 +1,23 @@ +import { CreateOptions, OptionsToSend, Requestor } from "../types"; +import { ApiSection } from "./apis"; + +export const createApiProvider = ( + options: CreateOptions, + apiSection: ApiSection, + createApi: (requestor: Requestor, optionsToSend: OptionsToSend) => ApiContract +) => { + const requestor: Requestor = options.requestor; + + let optionsToSend: OptionsToSend = { + url: options.apiUrls[apiSection], + }; + + if (options.clientOptions) { + optionsToSend = { + ...optionsToSend, + ...options.clientOptions, + }; + } + + return createApi(requestor, optionsToSend); +}; diff --git a/lib/utils/httpRequestor.js b/lib/utils/httpRequestor.js index 812860a..138d4b8 100644 --- a/lib/utils/httpRequestor.js +++ b/lib/utils/httpRequestor.js @@ -1,27 +1,30 @@ -var Promise = require('bluebird'); -var _ = require('underscore'); -var constants = require('./constants.js'); -var requestLogger = require('./requestLogger'); -var packageJson = require('../../package.json'); -var fs = require('fs'); -var mime = require('mime'); - -exports.create = function(requestorConfig) { +var Promise = require("bluebird"); +var _ = require("underscore"); +var constants = require("./constants.js"); +var requestLogger = require("./requestLogger"); +var packageJson = require("../../package.json"); +var fs = require("fs"); +var mime = require("mime"); + +exports.create = function (requestorConfig) { var logger = requestorConfig.logger ? requestLogger.create(requestorConfig.logger) : requestLogger.empty; - var request = requestorConfig.request || - Promise.promisifyAll(require("axios"), {multiArgs: true}); + var request = + requestorConfig.request || + Promise.promisifyAll(require("axios"), { multiArgs: true }); - var handleResponse = requestorConfig.handleResponse || - require('./responseHandler'); + var handleResponse = + requestorConfig.handleResponse || require("./responseHandler"); - var defaultCalcBackoff = numRetries => (Math.pow(2, numRetries) + Math.random()) * 1000; + var defaultCalcBackoff = (numRetries) => + (Math.pow(2, numRetries) + Math.random()) * 1000; var defaultRetryOptions = { - maxRetryDurationMillis: requestorConfig.maxRetryDurationMillis || - constants.maxRetryDurationMillis, - calcRetryBackoff: requestorConfig.calcRetryBackoff || defaultCalcBackoff + maxRetryDurationMillis: + requestorConfig.maxRetryDurationMillis || + constants.maxRetryDurationMillis, + calcRetryBackoff: requestorConfig.calcRetryBackoff || defaultCalcBackoff, }; var getFileSizeFromPath = (path) => { @@ -30,43 +33,47 @@ exports.create = function(requestorConfig) { return stats.size; }; - var buildHeaders = options => { + var buildHeaders = (options) => { var headers = { - Accept: options.accept || 'application/json', - 'Content-Type': options.contentType || mime.getType(options.fileName) || 'application/json', - 'User-Agent': `smartsheet-javascript-sdk/${packageJson.version}` + Accept: options.accept || "application/json", + "Content-Type": + options.contentType || + mime.getType(options.fileName) || + "application/json", + "User-Agent": `smartsheet-javascript-sdk/${packageJson.version}`, }; if (options.userAgent) { - headers['User-Agent'] += `/${options.userAgent}`; + headers["User-Agent"] += `/${options.userAgent}`; } - if(options.accessToken) { - headers.Authorization = 'Bearer ' + options.accessToken; + if (options.accessToken) { + headers.Authorization = "Bearer " + options.accessToken; } - if(options.assumeUser) { - headers['Assume-User'] = encodeURIComponent(options.assumeUser); + if (options.assumeUser) { + headers["Assume-User"] = encodeURIComponent(options.assumeUser); } if (options.fileName) { - headers['Content-Disposition'] = `attachment; filename="${options.fileName}"`; + headers[ + "Content-Disposition" + ] = `attachment; filename="${options.fileName}"`; } if (options.contentDisposition) { - headers['Content-Disposition'] = options.contentDisposition; + headers["Content-Disposition"] = options.contentDisposition; } if (options.path) { - headers['Content-Length'] = getFileSizeFromPath(options.path); + headers["Content-Length"] = getFileSizeFromPath(options.path); + } else if (options.fileSize) { + headers["Content-Length"] = options.fileSize; } - else if (options.fileSize) { - headers['Content-Length'] = options.fileSize; + if (options.apiScenario) { + headers["Api-Scenario"] = options.apiScenario; } - if(options.apiScenario) { - headers['Api-Scenario'] = options.apiScenario; + if (options.changeAgent) { + headers["Smartsheet-Change-Agent"] = options.changeAgent; } - if(options.changeAgent) { - headers['Smartsheet-Change-Agent'] = options.changeAgent; - } - if(options.customProperties) { + if (options.customProperties) { for (const [key, value] of Object.entries(options.customProperties)) { headers[key] = value; } @@ -74,19 +81,23 @@ exports.create = function(requestorConfig) { return headers; }; - var buildUrl = options => { - var baseUrl = options.baseUrl || process.env.SMARTSHEET_API_HOST || 'https://api.smartsheet.com/2.0/'; + var buildUrl = (options) => { + var baseUrl = + options.baseUrl || + process.env.SMARTSHEET_API_HOST || + "https://api.smartsheet.com/2.0/"; if (options.id) { return baseUrl + options.url + options.id; } else { - return baseUrl + (options.url || ''); + return baseUrl + (options.url || ""); } }; var get = (options, callback) => - methodRequest(options, request.get, 'GET', callback); + methodRequest(options, request.get, "GET", callback); - var post = (options, callback) => methodRequest(options, request.post, 'POST', callback, options.body); + var post = (options, callback) => + methodRequest(options, request.post, "POST", callback, options.body); var getFileBody = (options) => { if (options.path) { @@ -96,43 +107,80 @@ exports.create = function(requestorConfig) { return options.fileStream; }; - var postFile = (options, callback) => methodRequest(options, request.post, 'POST', callback, getFileBody(options)); + var postFile = (options, callback) => + methodRequest( + options, + request.post, + "POST", + callback, + getFileBody(options) + ); - var deleteFunc = (options, callback) => - methodRequest(options, request.delete, 'DELETE', callback); + var deleteFunc = (options, callback) => + methodRequest(options, request.delete, "DELETE", callback); - var put = (options, callback) => methodRequest(options, request.put, 'PUT', callback, options.body); + var put = (options, callback) => + methodRequest(options, request.put, "PUT", callback, options.body); var methodRequest = (options, method, methodName, callback, body) => { var baseRequestOptions = { url: buildUrl(options), headers: buildHeaders(options), params: options.queryParameters, - responseEncoding: options.encoding + responseEncoding: options.encoding, }; var url = buildUrl(options); var requestOptions = baseRequestOptions; - var retryOptions = _.pick(options, 'maxRetryDurationMillis', 'calcRetryBackoff'); + var retryOptions = _.pick( + options, + "maxRetryDurationMillis", + "calcRetryBackoff" + ); logger.logRequest(methodName, requestOptions); - return makeRequestWithRetries(url, method, methodName, requestOptions, body, retryOptions, callback); + return makeRequestWithRetries( + url, + method, + methodName, + requestOptions, + body, + retryOptions, + callback + ); }; - var makeRequestWithRetries = (url, method, methodName, requestOptions, body, retryOptions, callback) => { + var makeRequestWithRetries = ( + url, + method, + methodName, + requestOptions, + body, + retryOptions, + callback + ) => { var effectiveRetryOptions = _.defaults(retryOptions, defaultRetryOptions); - effectiveRetryOptions.endRetryTime = Date.now() + effectiveRetryOptions.maxRetryDurationMillis; - - return retryHelper(url, method, methodName, requestOptions, body, effectiveRetryOptions, 0) + effectiveRetryOptions.endRetryTime = + Date.now() + effectiveRetryOptions.maxRetryDurationMillis; + + return retryHelper( + url, + method, + methodName, + requestOptions, + body, + effectiveRetryOptions, + 0 + ) .then((response) => { logger.logSuccessfulResponse(response); if (callback) { callback(undefined, response.content); } - + return response.content; }) .catch((error) => { @@ -141,17 +189,34 @@ exports.create = function(requestorConfig) { if (callback) { callback(error, undefined); } - - return new Promise.reject(_.omit(error, 'headers', 'body')); + + return new Promise.reject(_.omit(error, "headers", "body")); }); }; - var retryHelper = (url, method, methodName, requestOptions, body, retryOptions, numRetries) => { + var retryHelper = ( + url, + method, + methodName, + requestOptions, + body, + retryOptions, + numRetries + ) => { return methodHandler(url, method, methodName, requestOptions, body) .then(handleResponse) - .catch(retryWithBackoffHelper(url, method, methodName, requestOptions, retryOptions, numRetries)); + .catch( + retryWithBackoffHelper( + url, + method, + methodName, + requestOptions, + retryOptions, + numRetries + ) + ); }; - + var methodHandler = (url, method, methodName, requestOptions, body) => { if (methodName === "POST" || methodName === "PUT") { return method(url, body, requestOptions); @@ -160,13 +225,23 @@ exports.create = function(requestorConfig) { return method(url, requestOptions); }; - var retryWithBackoffHelper = (url, method, methodName, requestOptions, retryOptions, numRetries) => { - return error => { + var retryWithBackoffHelper = ( + url, + method, + methodName, + requestOptions, + retryOptions, + numRetries + ) => { + return (error) => { var processedError = handleResponse(error.response); var backoffMillis = 0; if (retryOptions.calcRetryBackoff) { - backoffMillis = retryOptions.calcRetryBackoff(numRetries, processedError); + backoffMillis = retryOptions.calcRetryBackoff( + numRetries, + processedError + ); } else { backoffMillis = defaultCalcBackoff(numRetries); } @@ -182,13 +257,26 @@ exports.create = function(requestorConfig) { } var nextRetry = numRetries + 1; - logger.logRetryAttempt(methodName, requestOptions, processedError, nextRetry); - return Promise.delay(backoffMillis) - .then(() => retryHelper(url, method, methodName, requestOptions, retryOptions, nextRetry)); + logger.logRetryAttempt( + methodName, + requestOptions, + processedError, + nextRetry + ); + return Promise.delay(backoffMillis).then(() => + retryHelper( + url, + method, + methodName, + requestOptions, + retryOptions, + nextRetry + ) + ); }; }; - var shouldRetry = error => + var shouldRetry = (error) => error.errorCode === 4001 || error.errorCode === 4002 || error.errorCode === 4003 || @@ -202,7 +290,7 @@ exports.create = function(requestorConfig) { delete: deleteFunc, internal: { buildHeaders: buildHeaders, - buildUrl: buildUrl - } + buildUrl: buildUrl, + }, }; }; From 909a9b79d5ae4ca1c209fb179ac0ea04583d33e3 Mon Sep 17 00:00:00 2001 From: bfeigin Date: Fri, 21 Mar 2025 11:34:30 -0700 Subject: [PATCH 2/9] small refactor --- lib/favorites/endpoints/ListFavorites.ts | 1 - lib/types/ApiUrls.ts | 4 ++++ lib/types/CreateOptions.ts | 4 ++-- lib/utils/apis.ts | 2 -- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/favorites/endpoints/ListFavorites.ts b/lib/favorites/endpoints/ListFavorites.ts index 1c616a4..0e6946c 100644 --- a/lib/favorites/endpoints/ListFavorites.ts +++ b/lib/favorites/endpoints/ListFavorites.ts @@ -1,7 +1,6 @@ import { RequestCallback } from "../../types"; import { ApiCreator, - ApiEndpointCreator, FavoriteItem, Pagination, PaginationResponse, diff --git a/lib/types/ApiUrls.ts b/lib/types/ApiUrls.ts index e69de29..350e8c6 100644 --- a/lib/types/ApiUrls.ts +++ b/lib/types/ApiUrls.ts @@ -0,0 +1,4 @@ +import { ApiSection, apiUrlBySection } from "../utils/apis"; + +export type ApiUrlPathBySection = typeof apiUrlBySection; +export type ApiUrlPath = (typeof apiUrlBySection)[ApiSection]; diff --git a/lib/types/CreateOptions.ts b/lib/types/CreateOptions.ts index 214af6e..ac682d0 100644 --- a/lib/types/CreateOptions.ts +++ b/lib/types/CreateOptions.ts @@ -1,4 +1,4 @@ -import { ApiUrls } from "./ApiUrls"; +import { ApiUrlPathBySection } from "./ApiUrls"; import { CreateClientOptions } from "./CreateClientOptions"; export type ClientOptions = Pick< @@ -23,7 +23,7 @@ export type Requestor = { }; export interface CreateOptions { - apiUrls: ApiUrls; + apiUrls: ApiUrlPathBySection; requestor: Requestor; clientOptions?: ClientOptions; } diff --git a/lib/utils/apis.ts b/lib/utils/apis.ts index c34e7ae..e1cddbf 100644 --- a/lib/utils/apis.ts +++ b/lib/utils/apis.ts @@ -39,5 +39,3 @@ export const apiUrlBySection = { [ApiSection.Webhooks]: "webhooks/", [ApiSection.Workspaces]: "workspaces/", } as const; - -export type ApiUrlPath = (typeof apiUrlBySection)[ApiSection]; From ecb3a42c00b52d475906437ebe8eb9f4816418d9 Mon Sep 17 00:00:00 2001 From: bfeigin Date: Fri, 21 Mar 2025 11:40:44 -0700 Subject: [PATCH 3/9] rename section => resource --- lib/types/ApiUrls.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/types/ApiUrls.ts b/lib/types/ApiUrls.ts index 350e8c6..adab5ab 100644 --- a/lib/types/ApiUrls.ts +++ b/lib/types/ApiUrls.ts @@ -1,4 +1,4 @@ import { ApiSection, apiUrlBySection } from "../utils/apis"; export type ApiUrlPathBySection = typeof apiUrlBySection; -export type ApiUrlPath = (typeof apiUrlBySection)[ApiSection]; +export type ApiUrlPath = ApiUrlPathBySection[ApiSection]; From 30c4dd9c956eb50f988feaf5dc427171e39b2d38 Mon Sep 17 00:00:00 2001 From: bfeigin Date: Fri, 21 Mar 2025 11:40:49 -0700 Subject: [PATCH 4/9] rename section => resource --- index.ts | 4 ++-- lib/events/index.ts | 4 ++-- lib/favorites/index.ts | 4 ++-- lib/types/ApiUrls.ts | 9 +++++--- lib/utils/apis.ts | 40 +++++++++++++++++----------------- lib/utils/createApiProvider.ts | 6 ++--- 6 files changed, 35 insertions(+), 32 deletions(-) diff --git a/index.ts b/index.ts index 0b35b50..1473e5a 100644 --- a/index.ts +++ b/index.ts @@ -1,5 +1,5 @@ import { CreateClient, CreateOptions } from "./lib/types"; -import { apiUrlBySection } from "./lib/utils/apis"; +import { apiUrlByResource } from "./lib/utils/apis"; import { createEvents } from "./lib/events"; var _ = require("underscore"); @@ -87,7 +87,7 @@ export const createClient: CreateClient = function (clientOptions) { var requestor = buildRequestor(clientOptions); var options: CreateOptions = { - apiUrls: apiUrlBySection, + apiUrls: apiUrlByResource, requestor: requestor, clientOptions: { accessToken: diff --git a/lib/events/index.ts b/lib/events/index.ts index a2a9e61..fc8638f 100644 --- a/lib/events/index.ts +++ b/lib/events/index.ts @@ -1,12 +1,12 @@ import { EventsApi, GetEventsOptions, GetEventsResponse } from "./types"; import { CreateOptions, RequestCallback, RequestOptions } from "../types"; import { createApiProvider } from "../utils/createApiProvider"; -import { ApiSection } from "../utils/apis"; +import { ApiResource } from "../utils/apis"; export const createEvents = (options: CreateOptions): EventsApi => { return createApiProvider( options, - ApiSection.Events, + ApiResource.Events, (requester, optionsToSend) => ({ getEvents: ( options: RequestOptions, diff --git a/lib/favorites/index.ts b/lib/favorites/index.ts index 1a772ae..53ac09a 100644 --- a/lib/favorites/index.ts +++ b/lib/favorites/index.ts @@ -1,5 +1,5 @@ import { CreateOptions } from "../types"; -import { ApiSection } from "../utils/apis"; +import { ApiResource } from "../utils/apis"; import { createApiProvider } from "../utils/createApiProvider"; import { createListFavorites } from "./endpoints/ListFavorites"; import { ApiCreator } from "./sharedTypes"; @@ -41,5 +41,5 @@ const buildFavoritesApi: ApiCreator = ( //} export const createFavorites = (options: CreateOptions) => { - return createApiProvider(options, ApiSection.Favorites, buildFavoritesApi); + return createApiProvider(options, ApiResource.Favorites, buildFavoritesApi); }; diff --git a/lib/types/ApiUrls.ts b/lib/types/ApiUrls.ts index adab5ab..122ad06 100644 --- a/lib/types/ApiUrls.ts +++ b/lib/types/ApiUrls.ts @@ -1,4 +1,7 @@ -import { ApiSection, apiUrlBySection } from "../utils/apis"; +import { ApiResource, apiUrlByResource } from "../utils/apis"; -export type ApiUrlPathBySection = typeof apiUrlBySection; -export type ApiUrlPath = ApiUrlPathBySection[ApiSection]; +// Map of ApiResource to path +export type ApiUrlPathByResource = typeof apiUrlByResource; + +// Valid paths for Api Resources +export type ApiUrlPath = ApiUrlPathByResource[ApiResource]; diff --git a/lib/utils/apis.ts b/lib/utils/apis.ts index e1cddbf..1648e9e 100644 --- a/lib/utils/apis.ts +++ b/lib/utils/apis.ts @@ -1,4 +1,4 @@ -export enum ApiSection { +export enum ApiResource { Contacts = "contacts", Events = "events", Favorites = "favorites", @@ -19,23 +19,23 @@ export enum ApiSection { Workspaces = "workspaces", } -export const apiUrlBySection = { - [ApiSection.Contacts]: "contacts/", - [ApiSection.Events]: "events/", - [ApiSection.Favorites]: "favorites/", - [ApiSection.Folders]: "folders/", - [ApiSection.Groups]: "groups/", - [ApiSection.Home]: "home/", - [ApiSection.ImageUrls]: "imageurls/", - [ApiSection.Reports]: "reports/", - [ApiSection.Search]: "search/", - [ApiSection.Server]: "serverinfo/", - [ApiSection.Sheets]: "sheets/", - [ApiSection.Sights]: "sights/", - [ApiSection.Templates]: "templates/", - [ApiSection.TemplatesPublic]: "templates/public", - [ApiSection.Token]: "token", - [ApiSection.Users]: "users/", - [ApiSection.Webhooks]: "webhooks/", - [ApiSection.Workspaces]: "workspaces/", +export const apiUrlByResource = { + [ApiResource.Contacts]: "contacts/", + [ApiResource.Events]: "events/", + [ApiResource.Favorites]: "favorites/", + [ApiResource.Folders]: "folders/", + [ApiResource.Groups]: "groups/", + [ApiResource.Home]: "home/", + [ApiResource.ImageUrls]: "imageurls/", + [ApiResource.Reports]: "reports/", + [ApiResource.Search]: "search/", + [ApiResource.Server]: "serverinfo/", + [ApiResource.Sheets]: "sheets/", + [ApiResource.Sights]: "sights/", + [ApiResource.Templates]: "templates/", + [ApiResource.TemplatesPublic]: "templates/public", + [ApiResource.Token]: "token", + [ApiResource.Users]: "users/", + [ApiResource.Webhooks]: "webhooks/", + [ApiResource.Workspaces]: "workspaces/", } as const; diff --git a/lib/utils/createApiProvider.ts b/lib/utils/createApiProvider.ts index 3099791..c396746 100644 --- a/lib/utils/createApiProvider.ts +++ b/lib/utils/createApiProvider.ts @@ -1,15 +1,15 @@ import { CreateOptions, OptionsToSend, Requestor } from "../types"; -import { ApiSection } from "./apis"; +import { ApiResource } from "./apis"; export const createApiProvider = ( options: CreateOptions, - apiSection: ApiSection, + ApiResource: ApiResource, createApi: (requestor: Requestor, optionsToSend: OptionsToSend) => ApiContract ) => { const requestor: Requestor = options.requestor; let optionsToSend: OptionsToSend = { - url: options.apiUrls[apiSection], + url: options.apiUrls[ApiResource], }; if (options.clientOptions) { From d2634afcefbc2b718ee740cce488efaa7a774325 Mon Sep 17 00:00:00 2001 From: bfeigin Date: Fri, 21 Mar 2025 11:45:27 -0700 Subject: [PATCH 5/9] fix why no tsc find? --- lib/types/CreateOptions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/types/CreateOptions.ts b/lib/types/CreateOptions.ts index ac682d0..ad53a3a 100644 --- a/lib/types/CreateOptions.ts +++ b/lib/types/CreateOptions.ts @@ -1,4 +1,4 @@ -import { ApiUrlPathBySection } from "./ApiUrls"; +import { ApiUrlPathByResource } from "./ApiUrls"; import { CreateClientOptions } from "./CreateClientOptions"; export type ClientOptions = Pick< @@ -23,7 +23,7 @@ export type Requestor = { }; export interface CreateOptions { - apiUrls: ApiUrlPathBySection; + apiUrls: ApiUrlPathByResource; requestor: Requestor; clientOptions?: ClientOptions; } From 2550312c7d4d8e9b181eb5148179744f9b939470 Mon Sep 17 00:00:00 2001 From: bfeigin Date: Fri, 21 Mar 2025 12:12:27 -0700 Subject: [PATCH 6/9] revert big lint changes --- index.ts | 135 ++++++++++------------ lib/utils/httpRequestor.js | 224 +++++++++++-------------------------- 2 files changed, 129 insertions(+), 230 deletions(-) diff --git a/index.ts b/index.ts index 1473e5a..9a3e5b3 100644 --- a/index.ts +++ b/index.ts @@ -1,9 +1,9 @@ -import { CreateClient, CreateOptions } from "./lib/types"; -import { apiUrlByResource } from "./lib/utils/apis"; +import {CreateClient, CreateOptions} from "./lib/types"; +import { apiUrls } from "./lib/utils/apis"; import { createEvents } from "./lib/events"; -var _ = require("underscore"); -var winston = require("winston"); +var _ = require('underscore'); +var winston = require('winston'); // Possible TODO: Namespace parameters for different subcomponents // E.g. clientOptions.requestor.instance OR @@ -11,124 +11,111 @@ var winston = require("winston"); // w/ sub-paths maxRetryDurationSeconds and calcRetryBackoff function buildRequestor(clientOptions) { - if (clientOptions.requestor) return clientOptions.requestor; + if(clientOptions.requestor) return clientOptions.requestor; - var requestorConfig = _.pick( - clientOptions, - "maxRetryDurationSeconds", - "calcRetryBackoff" - ); + var requestorConfig = + _.pick(clientOptions, 'maxRetryDurationSeconds', 'calcRetryBackoff'); - if (requestorConfig.maxRetryDurationSeconds) - requestorConfig.maxRetryDurationMillis = - requestorConfig.maxRetryDurationSeconds * 1000; + if(requestorConfig.maxRetryDurationSeconds) + requestorConfig.maxRetryDurationMillis = requestorConfig.maxRetryDurationSeconds * 1000; requestorConfig.logger = buildLogger(clientOptions); - return require("./lib/utils/httpRequestor.js").create(requestorConfig); -} + return require('./lib/utils/httpRequestor.js') + .create(requestorConfig); +}; function buildLogger(clientOptions) { - if (hasMultipleLogOptions(clientOptions)) { + if(hasMultipleLogOptions(clientOptions)) { throw new Error( "Smartsheet client options may specify at most one of " + - "'logger', 'loggerContainer', and 'logLevel'." - ); + "'logger', 'loggerContainer', and 'logLevel'."); } - if (clientOptions.logger) return clientOptions.logger; + if(clientOptions.logger) return clientOptions.logger; - if (clientOptions.logLevel) - return buildLoggerFromLevel(clientOptions.logLevel); + if(clientOptions.logLevel) return buildLoggerFromLevel(clientOptions.logLevel); - if (clientOptions.loggerContainer) - return buildLoggerFromContainer(clientOptions.loggerContainer); + if(clientOptions.loggerContainer) return buildLoggerFromContainer(clientOptions.loggerContainer); return null; } function hasMultipleLogOptions(clientOptions) { - return ( - (clientOptions.logger && clientOptions.loggerContainer) || - (clientOptions.logger && clientOptions.logLevel) || - (clientOptions.loggerContainer && clientOptions.logLevel) - ); + return (clientOptions.logger && clientOptions.loggerContainer) + || (clientOptions.logger && clientOptions.logLevel) + || (clientOptions.loggerContainer && clientOptions.logLevel); } function buildLoggerFromLevel(logLevel) { - if (winston.levels[logLevel] == null) { + if(winston.levels[logLevel] == null) { throw new Error( - "Smartsheet client received configuration with invalid log level " + - `'${logLevel}'. Use one of the standard Winston log levels.` - ); + 'Smartsheet client received configuration with invalid log level ' + + `'${logLevel}'. Use one of the standard Winston log levels.`); } - return new winston.Logger({ + return new (winston.Logger)({ transports: [ new winston.transports.Console({ level: logLevel, showLevel: false, - label: "Smartsheet", - }), - ], + label: 'Smartsheet' + }) + ] }); } function buildLoggerFromContainer(container) { - if (container.has("smartsheet")) return container.get("smartsheet"); + if(container.has('smartsheet')) + return container.get('smartsheet'); else throw new Error( "Smartsheet client received a logger container, but could not find a logger named " + - "'smartsheet' inside." - ); + "'smartsheet' inside."); } -export const createClient: CreateClient = function (clientOptions) { +export const createClient: CreateClient = function(clientOptions) { var requestor = buildRequestor(clientOptions); var options: CreateOptions = { - apiUrls: apiUrlByResource, + apiUrls: apiUrls, requestor: requestor, clientOptions: { - accessToken: - clientOptions.accessToken || process.env.SMARTSHEET_ACCESS_TOKEN, + accessToken: clientOptions.accessToken || process.env.SMARTSHEET_ACCESS_TOKEN, userAgent: clientOptions.userAgent, - baseUrl: clientOptions.baseUrl, - }, + baseUrl: clientOptions.baseUrl + } }; return { - constants: require("./lib/utils/constants.js"), - contacts: require("./lib/contacts/").create(options), - events: createEvents(options), - favorites: require("./lib/favorites/").create(options), - folders: require("./lib/folders/").create(options), - groups: require("./lib/groups/").create(options), - home: require("./lib/home/").create(options), - images: require("./lib/images/").create(options), - reports: require("./lib/reports/").create(options), - request: require("./lib/request/").create(options), - search: require("./lib/search/").create(options), - server: require("./lib/server/").create(options), - sheets: require("./lib/sheets/").create(options), - sights: require("./lib/sights/").create(options), - templates: require("./lib/templates/").create(options), - tokens: require("./lib/tokens/").create(options), - users: require("./lib/users/").create(options), - webhooks: require("./lib/webhooks/").create(options), - workspaces: require("./lib/workspaces/").create(options), + constants : require('./lib/utils/constants.js'), + contacts : require('./lib/contacts/').create(options), + events : createEvents(options), + favorites : require('./lib/favorites/').create(options), + folders : require('./lib/folders/').create(options), + groups : require('./lib/groups/').create(options), + home : require('./lib/home/').create(options), + images : require('./lib/images/').create(options), + reports : require('./lib/reports/').create(options), + request : require('./lib/request/').create(options), + search : require('./lib/search/').create(options), + server : require('./lib/server/').create(options), + sheets : require('./lib/sheets/').create(options), + sights : require('./lib/sights/').create(options), + templates : require('./lib/templates/').create(options), + tokens : require('./lib/tokens/').create(options), + users : require('./lib/users/').create(options), + webhooks : require('./lib/webhooks/').create(options), + workspaces : require('./lib/workspaces/').create(options) }; }; export const smartSheetURIs = { - defaultBaseURI: "https://api.smartsheet.com/2.0/", - govBaseURI: "https://api.smartsheetgov.com/2.0/", - euBaseURI: "https://api.smartsheet.eu/2.0/", -}; + defaultBaseURI: 'https://api.smartsheet.com/2.0/', + govBaseURI: 'https://api.smartsheetgov.com/2.0/', + euBaseURI: 'https://api.smartsheet.eu/2.0/' +} + -export { - CreateClient, - CreateClientOptions, - SmartsheetClient, -} from "./lib/types"; -export * from "./lib/events/types"; +export { CreateClient, CreateClientOptions, SmartsheetClient } from "./lib/types"; +export * from "./lib/events/types" diff --git a/lib/utils/httpRequestor.js b/lib/utils/httpRequestor.js index 138d4b8..812860a 100644 --- a/lib/utils/httpRequestor.js +++ b/lib/utils/httpRequestor.js @@ -1,30 +1,27 @@ -var Promise = require("bluebird"); -var _ = require("underscore"); -var constants = require("./constants.js"); -var requestLogger = require("./requestLogger"); -var packageJson = require("../../package.json"); -var fs = require("fs"); -var mime = require("mime"); - -exports.create = function (requestorConfig) { +var Promise = require('bluebird'); +var _ = require('underscore'); +var constants = require('./constants.js'); +var requestLogger = require('./requestLogger'); +var packageJson = require('../../package.json'); +var fs = require('fs'); +var mime = require('mime'); + +exports.create = function(requestorConfig) { var logger = requestorConfig.logger ? requestLogger.create(requestorConfig.logger) : requestLogger.empty; - var request = - requestorConfig.request || - Promise.promisifyAll(require("axios"), { multiArgs: true }); + var request = requestorConfig.request || + Promise.promisifyAll(require("axios"), {multiArgs: true}); - var handleResponse = - requestorConfig.handleResponse || require("./responseHandler"); + var handleResponse = requestorConfig.handleResponse || + require('./responseHandler'); - var defaultCalcBackoff = (numRetries) => - (Math.pow(2, numRetries) + Math.random()) * 1000; + var defaultCalcBackoff = numRetries => (Math.pow(2, numRetries) + Math.random()) * 1000; var defaultRetryOptions = { - maxRetryDurationMillis: - requestorConfig.maxRetryDurationMillis || - constants.maxRetryDurationMillis, - calcRetryBackoff: requestorConfig.calcRetryBackoff || defaultCalcBackoff, + maxRetryDurationMillis: requestorConfig.maxRetryDurationMillis || + constants.maxRetryDurationMillis, + calcRetryBackoff: requestorConfig.calcRetryBackoff || defaultCalcBackoff }; var getFileSizeFromPath = (path) => { @@ -33,47 +30,43 @@ exports.create = function (requestorConfig) { return stats.size; }; - var buildHeaders = (options) => { + var buildHeaders = options => { var headers = { - Accept: options.accept || "application/json", - "Content-Type": - options.contentType || - mime.getType(options.fileName) || - "application/json", - "User-Agent": `smartsheet-javascript-sdk/${packageJson.version}`, + Accept: options.accept || 'application/json', + 'Content-Type': options.contentType || mime.getType(options.fileName) || 'application/json', + 'User-Agent': `smartsheet-javascript-sdk/${packageJson.version}` }; if (options.userAgent) { - headers["User-Agent"] += `/${options.userAgent}`; + headers['User-Agent'] += `/${options.userAgent}`; } - if (options.accessToken) { - headers.Authorization = "Bearer " + options.accessToken; + if(options.accessToken) { + headers.Authorization = 'Bearer ' + options.accessToken; } - if (options.assumeUser) { - headers["Assume-User"] = encodeURIComponent(options.assumeUser); + if(options.assumeUser) { + headers['Assume-User'] = encodeURIComponent(options.assumeUser); } if (options.fileName) { - headers[ - "Content-Disposition" - ] = `attachment; filename="${options.fileName}"`; + headers['Content-Disposition'] = `attachment; filename="${options.fileName}"`; } if (options.contentDisposition) { - headers["Content-Disposition"] = options.contentDisposition; + headers['Content-Disposition'] = options.contentDisposition; } if (options.path) { - headers["Content-Length"] = getFileSizeFromPath(options.path); - } else if (options.fileSize) { - headers["Content-Length"] = options.fileSize; + headers['Content-Length'] = getFileSizeFromPath(options.path); } - if (options.apiScenario) { - headers["Api-Scenario"] = options.apiScenario; + else if (options.fileSize) { + headers['Content-Length'] = options.fileSize; } - if (options.changeAgent) { - headers["Smartsheet-Change-Agent"] = options.changeAgent; + if(options.apiScenario) { + headers['Api-Scenario'] = options.apiScenario; } - if (options.customProperties) { + if(options.changeAgent) { + headers['Smartsheet-Change-Agent'] = options.changeAgent; + } + if(options.customProperties) { for (const [key, value] of Object.entries(options.customProperties)) { headers[key] = value; } @@ -81,23 +74,19 @@ exports.create = function (requestorConfig) { return headers; }; - var buildUrl = (options) => { - var baseUrl = - options.baseUrl || - process.env.SMARTSHEET_API_HOST || - "https://api.smartsheet.com/2.0/"; + var buildUrl = options => { + var baseUrl = options.baseUrl || process.env.SMARTSHEET_API_HOST || 'https://api.smartsheet.com/2.0/'; if (options.id) { return baseUrl + options.url + options.id; } else { - return baseUrl + (options.url || ""); + return baseUrl + (options.url || ''); } }; var get = (options, callback) => - methodRequest(options, request.get, "GET", callback); + methodRequest(options, request.get, 'GET', callback); - var post = (options, callback) => - methodRequest(options, request.post, "POST", callback, options.body); + var post = (options, callback) => methodRequest(options, request.post, 'POST', callback, options.body); var getFileBody = (options) => { if (options.path) { @@ -107,80 +96,43 @@ exports.create = function (requestorConfig) { return options.fileStream; }; - var postFile = (options, callback) => - methodRequest( - options, - request.post, - "POST", - callback, - getFileBody(options) - ); + var postFile = (options, callback) => methodRequest(options, request.post, 'POST', callback, getFileBody(options)); - var deleteFunc = (options, callback) => - methodRequest(options, request.delete, "DELETE", callback); + var deleteFunc = (options, callback) => + methodRequest(options, request.delete, 'DELETE', callback); - var put = (options, callback) => - methodRequest(options, request.put, "PUT", callback, options.body); + var put = (options, callback) => methodRequest(options, request.put, 'PUT', callback, options.body); var methodRequest = (options, method, methodName, callback, body) => { var baseRequestOptions = { url: buildUrl(options), headers: buildHeaders(options), params: options.queryParameters, - responseEncoding: options.encoding, + responseEncoding: options.encoding }; var url = buildUrl(options); var requestOptions = baseRequestOptions; - var retryOptions = _.pick( - options, - "maxRetryDurationMillis", - "calcRetryBackoff" - ); + var retryOptions = _.pick(options, 'maxRetryDurationMillis', 'calcRetryBackoff'); logger.logRequest(methodName, requestOptions); - return makeRequestWithRetries( - url, - method, - methodName, - requestOptions, - body, - retryOptions, - callback - ); + return makeRequestWithRetries(url, method, methodName, requestOptions, body, retryOptions, callback); }; - var makeRequestWithRetries = ( - url, - method, - methodName, - requestOptions, - body, - retryOptions, - callback - ) => { + var makeRequestWithRetries = (url, method, methodName, requestOptions, body, retryOptions, callback) => { var effectiveRetryOptions = _.defaults(retryOptions, defaultRetryOptions); - effectiveRetryOptions.endRetryTime = - Date.now() + effectiveRetryOptions.maxRetryDurationMillis; - - return retryHelper( - url, - method, - methodName, - requestOptions, - body, - effectiveRetryOptions, - 0 - ) + effectiveRetryOptions.endRetryTime = Date.now() + effectiveRetryOptions.maxRetryDurationMillis; + + return retryHelper(url, method, methodName, requestOptions, body, effectiveRetryOptions, 0) .then((response) => { logger.logSuccessfulResponse(response); if (callback) { callback(undefined, response.content); } - + return response.content; }) .catch((error) => { @@ -189,34 +141,17 @@ exports.create = function (requestorConfig) { if (callback) { callback(error, undefined); } - - return new Promise.reject(_.omit(error, "headers", "body")); + + return new Promise.reject(_.omit(error, 'headers', 'body')); }); }; - var retryHelper = ( - url, - method, - methodName, - requestOptions, - body, - retryOptions, - numRetries - ) => { + var retryHelper = (url, method, methodName, requestOptions, body, retryOptions, numRetries) => { return methodHandler(url, method, methodName, requestOptions, body) .then(handleResponse) - .catch( - retryWithBackoffHelper( - url, - method, - methodName, - requestOptions, - retryOptions, - numRetries - ) - ); + .catch(retryWithBackoffHelper(url, method, methodName, requestOptions, retryOptions, numRetries)); }; - + var methodHandler = (url, method, methodName, requestOptions, body) => { if (methodName === "POST" || methodName === "PUT") { return method(url, body, requestOptions); @@ -225,23 +160,13 @@ exports.create = function (requestorConfig) { return method(url, requestOptions); }; - var retryWithBackoffHelper = ( - url, - method, - methodName, - requestOptions, - retryOptions, - numRetries - ) => { - return (error) => { + var retryWithBackoffHelper = (url, method, methodName, requestOptions, retryOptions, numRetries) => { + return error => { var processedError = handleResponse(error.response); var backoffMillis = 0; if (retryOptions.calcRetryBackoff) { - backoffMillis = retryOptions.calcRetryBackoff( - numRetries, - processedError - ); + backoffMillis = retryOptions.calcRetryBackoff(numRetries, processedError); } else { backoffMillis = defaultCalcBackoff(numRetries); } @@ -257,26 +182,13 @@ exports.create = function (requestorConfig) { } var nextRetry = numRetries + 1; - logger.logRetryAttempt( - methodName, - requestOptions, - processedError, - nextRetry - ); - return Promise.delay(backoffMillis).then(() => - retryHelper( - url, - method, - methodName, - requestOptions, - retryOptions, - nextRetry - ) - ); + logger.logRetryAttempt(methodName, requestOptions, processedError, nextRetry); + return Promise.delay(backoffMillis) + .then(() => retryHelper(url, method, methodName, requestOptions, retryOptions, nextRetry)); }; }; - var shouldRetry = (error) => + var shouldRetry = error => error.errorCode === 4001 || error.errorCode === 4002 || error.errorCode === 4003 || @@ -290,7 +202,7 @@ exports.create = function (requestorConfig) { delete: deleteFunc, internal: { buildHeaders: buildHeaders, - buildUrl: buildUrl, - }, + buildUrl: buildUrl + } }; }; From af26eebd5bdd625fd9cc801fedd88c01ae1e644a Mon Sep 17 00:00:00 2001 From: bfeigin Date: Fri, 21 Mar 2025 12:14:06 -0700 Subject: [PATCH 7/9] just update name of apiUrls --- index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.ts b/index.ts index 9a3e5b3..63ef1af 100644 --- a/index.ts +++ b/index.ts @@ -1,5 +1,5 @@ import {CreateClient, CreateOptions} from "./lib/types"; -import { apiUrls } from "./lib/utils/apis"; +import { apiUrlByResource } from "./lib/utils/apis"; import { createEvents } from "./lib/events"; var _ = require('underscore'); @@ -78,7 +78,7 @@ export const createClient: CreateClient = function(clientOptions) { var requestor = buildRequestor(clientOptions); var options: CreateOptions = { - apiUrls: apiUrls, + apiUrls: apiUrlByResource, requestor: requestor, clientOptions: { accessToken: clientOptions.accessToken || process.env.SMARTSHEET_ACCESS_TOKEN, From 0e2f3e5bfe3a330e3dd25835e2016a34fc753a5f Mon Sep 17 00:00:00 2001 From: bfeigin Date: Mon, 24 Mar 2025 09:44:05 -0700 Subject: [PATCH 8/9] combine types and enums in single file, preserve backwards compat --- lib/types/ApiUrls.ts | 42 ++++++++++++++++++++++++++++++++++++++++- lib/utils/apis.ts | 45 +++++--------------------------------------- 2 files changed, 46 insertions(+), 41 deletions(-) diff --git a/lib/types/ApiUrls.ts b/lib/types/ApiUrls.ts index 122ad06..a25575b 100644 --- a/lib/types/ApiUrls.ts +++ b/lib/types/ApiUrls.ts @@ -1,4 +1,44 @@ -import { ApiResource, apiUrlByResource } from "../utils/apis"; +export enum ApiResource { + Contacts = "contacts", + Events = "events", + Favorites = "favorites", + Folders = "folders", + Groups = "groups", + Home = "home", + ImageUrls = "imageurls", + Reports = "reports", + Search = "search", + Server = "serverinfo", + Sheets = "sheets", + Sights = "sights", + Templates = "templates", + TemplatesPublic = "templatespublic", + Token = "token", + Users = "users", + Webhooks = "webhooks", + Workspaces = "workspaces", +} + +export const apiUrlByResource = { + [ApiResource.Contacts]: "contacts/", + [ApiResource.Events]: "events/", + [ApiResource.Favorites]: "favorites/", + [ApiResource.Folders]: "folders/", + [ApiResource.Groups]: "groups/", + [ApiResource.Home]: "home/", + [ApiResource.ImageUrls]: "imageurls/", + [ApiResource.Reports]: "reports/", + [ApiResource.Search]: "search/", + [ApiResource.Server]: "serverinfo/", + [ApiResource.Sheets]: "sheets/", + [ApiResource.Sights]: "sights/", + [ApiResource.Templates]: "templates/", + [ApiResource.TemplatesPublic]: "templates/public", + [ApiResource.Token]: "token", + [ApiResource.Users]: "users/", + [ApiResource.Webhooks]: "webhooks/", + [ApiResource.Workspaces]: "workspaces/", +} as const; // Map of ApiResource to path export type ApiUrlPathByResource = typeof apiUrlByResource; diff --git a/lib/utils/apis.ts b/lib/utils/apis.ts index 1648e9e..8fcabdc 100644 --- a/lib/utils/apis.ts +++ b/lib/utils/apis.ts @@ -1,41 +1,6 @@ -export enum ApiResource { - Contacts = "contacts", - Events = "events", - Favorites = "favorites", - Folders = "folders", - Groups = "groups", - Home = "home", - ImageUrls = "imageurls", - Reports = "reports", - Search = "search", - Server = "serverinfo", - Sheets = "sheets", - Sights = "sights", - Templates = "templates", - TemplatesPublic = "templatespublic", - Token = "token", - Users = "users", - Webhooks = "webhooks", - Workspaces = "workspaces", -} +import { apiUrlByResource } from "../types"; -export const apiUrlByResource = { - [ApiResource.Contacts]: "contacts/", - [ApiResource.Events]: "events/", - [ApiResource.Favorites]: "favorites/", - [ApiResource.Folders]: "folders/", - [ApiResource.Groups]: "groups/", - [ApiResource.Home]: "home/", - [ApiResource.ImageUrls]: "imageurls/", - [ApiResource.Reports]: "reports/", - [ApiResource.Search]: "search/", - [ApiResource.Server]: "serverinfo/", - [ApiResource.Sheets]: "sheets/", - [ApiResource.Sights]: "sights/", - [ApiResource.Templates]: "templates/", - [ApiResource.TemplatesPublic]: "templates/public", - [ApiResource.Token]: "token", - [ApiResource.Users]: "users/", - [ApiResource.Webhooks]: "webhooks/", - [ApiResource.Workspaces]: "workspaces/", -} as const; +/* + * @depricated - Will be removed in v5, prefer apiUrlByResource instead + */ +export { apiUrlByResource as apiUrls }; From 1bb4e47e9cb2372b2360102db757f6116a6e68ba Mon Sep 17 00:00:00 2001 From: bfeigin Date: Mon, 14 Apr 2025 15:09:07 -0700 Subject: [PATCH 9/9] wip --- .../endpoints/AddItemsToFavorites.ts | 60 +++++++++++++++++++ lib/favorites/endpoints/ListFavorites.ts | 38 ++++++------ lib/favorites/endpoints/RemoveFavorites.ts | 11 ++++ lib/favorites/index.ts | 23 ++++++- lib/favorites/sharedTypes.ts | 48 +++++++++++---- 5 files changed, 146 insertions(+), 34 deletions(-) create mode 100644 lib/favorites/endpoints/AddItemsToFavorites.ts create mode 100644 lib/favorites/endpoints/RemoveFavorites.ts diff --git a/lib/favorites/endpoints/AddItemsToFavorites.ts b/lib/favorites/endpoints/AddItemsToFavorites.ts new file mode 100644 index 0000000..68b052c --- /dev/null +++ b/lib/favorites/endpoints/AddItemsToFavorites.ts @@ -0,0 +1,60 @@ +import { RequestCallback } from '../../types'; +import { ApiGenerator, FavoritableResource, FavoriteItem, PostResult } from '../sharedTypes'; +const _ = require('underscore'); + +type AddItemsToFavoritesParams = { + body: FavoriteItem | FavoriteItem[]; +}; + +type AddItemsToFavoritesResponse = { + /** + * @description Favorite (object) or Array of Favorite (objects) + */ + result: FavoriteItem | FavoriteItem[]; +} & PostResult; + +type AddItemsToFavoritesRequest = ( + params: AddItemsToFavoritesParams, + callback: RequestCallback +) => Promise; + +export const addItemsToFavorites: ApiGenerator = + (requestor, optionsToSend) => (params, callback) => { + // TODO Bfeigin do i need to explicitly set this to be a body param? + return requestor.post(_.extend({}, optionsToSend, params), callback); + }; + +type AddResourceToFavoritesParams = { + objectId: string; +}; + +type AddItemOfTypeToFavoritesRequest = ( + params: AddResourceToFavoritesParams, + callback: RequestCallback +) => Promise; + +type AddFavoritesBuilder = (resourceType: FavoritableResource) => ApiGenerator; + +export const buildAddFavoriteResourceFn: AddFavoritesBuilder = (resourceType: FavoritableResource) => { + return (requestor, optionsToSend) => (params, callback) => { + const generatedBody = { + objectId: params.objectId, + type: resourceType, + }; + + // Ensure we've extracted out any type and objectId params, placing them into body instead + const constructedPostParams = { + ..._.omit(params, 'type', 'objectId'), + body: generatedBody, + }; + + return requestor.post(_.extend({}, optionsToSend, constructedPostParams), callback); + }; +}; + +export const addSheetToFavorites = buildAddFavoriteResourceFn(FavoritableResource.Sheet); +export const addFolderToFavorites = buildAddFavoriteResourceFn(FavoritableResource.Folder); +export const addReportToFavorites = buildAddFavoriteResourceFn(FavoritableResource.Report); +export const addTemplateToFavorites = buildAddFavoriteResourceFn(FavoritableResource.Template); +export const addWorkspaceToFavorites = buildAddFavoriteResourceFn(FavoritableResource.Workspace); +export const addSightToFavorites = buildAddFavoriteResourceFn(FavoritableResource.Sight); diff --git a/lib/favorites/endpoints/ListFavorites.ts b/lib/favorites/endpoints/ListFavorites.ts index 0e6946c..006c4f6 100644 --- a/lib/favorites/endpoints/ListFavorites.ts +++ b/lib/favorites/endpoints/ListFavorites.ts @@ -1,36 +1,32 @@ -import { RequestCallback } from "../../types"; -import { - ApiCreator, - FavoriteItem, - Pagination, - PaginationResponse, -} from "../sharedTypes"; +import { RequestCallback } from '../../types'; +import { ApiGenerator, FavoriteItem, Pagination, PaginationResponse } from '../sharedTypes'; type IncludeOptions = string; -type ListFavoritesParamsNonPaginated = { - includeAll: true; - include: IncludeOptions; -}; - -type ListFavoritesParamsPaginated = { - includeAll?: false; - include: IncludeOptions; +type ListFavortesParams = { + /** + * @description If true, include all results, that is, do not paginate. Mutually exclusive with page and pageSize (they are ignored if includeAll=true is specified). + */ + includeAll?: boolean; + /** + * @description A comma-separated list of optional elements to include in the response. + * Enum ["directId", "name"] + */ + include?: IncludeOptions; } & Pagination; -type ListFavortesParams = - | ListFavoritesParamsPaginated - | ListFavoritesParamsNonPaginated; - export type ListFavoritesResponse = PaginationResponse & { data: FavoriteItem[]; }; +/** + * @description Gets a list of all of the user's favorite items. + */ export type ListFavoritesRequest = ( params: ListFavortesParams, callback: RequestCallback -) => {}; +) => Promise; -export const createListFavorites: ApiCreator = +export const createListFavorites: ApiGenerator = (requestor, optionsToSend) => (params, callback) => { return requestor.get({ ...optionsToSend, ...params }, callback); }; diff --git a/lib/favorites/endpoints/RemoveFavorites.ts b/lib/favorites/endpoints/RemoveFavorites.ts new file mode 100644 index 0000000..bc103b9 --- /dev/null +++ b/lib/favorites/endpoints/RemoveFavorites.ts @@ -0,0 +1,11 @@ +import { FavoritableResource, FavoriteItem } from '../sharedTypes'; + +type RemoveSingleFavoriteItem = {}; + +type RemoveFavoritesBaseParams = { + /** + * @description A string or array of strings representing the objectIds to remove from favorites + */ + objectIds: FavoriteItem['objectId'] | FavoriteItem['objectId'][]; + favoriteType: FavoritableResource; +}; diff --git a/lib/favorites/index.ts b/lib/favorites/index.ts index e8b0934..d2f3b4c 100644 --- a/lib/favorites/index.ts +++ b/lib/favorites/index.ts @@ -1,14 +1,33 @@ import { ApiResource, CreateOptions } from '../types'; import { createApiProvider } from '../utils/createApiProvider'; import { createListFavorites } from './endpoints/ListFavorites'; -import { ApiCreator } from './sharedTypes'; +import { + addFolderToFavorites, + addItemsToFavorites, + addReportToFavorites, + addSheetToFavorites, + addSightToFavorites, + addTemplateToFavorites, + addWorkspaceToFavorites, +} from './endpoints/AddItemsToFavorites'; +import { ApiGenerator } from './sharedTypes'; import { FavoritesApi } from './types'; -const buildFavoritesApi: ApiCreator = (requestor, optionsToSend) => { +const buildFavoritesApi: ApiGenerator = (requestor, optionsToSend) => { return { listFavorites: createListFavorites(requestor, optionsToSend), + addItemsToFavorites: addItemsToFavorites(requestor, optionsToSend), + addFolderToFavorites: addFolderToFavorites(requestor, optionsToSend), + addReportToFavorites: addReportToFavorites(requestor, optionsToSend), + addSheetToFavorites: addSheetToFavorites(requestor, optionsToSend), + addSightToFavorites: addSightToFavorites(requestor, optionsToSend), + addTemplateToFavorites: addTemplateToFavorites(requestor, optionsToSend), + addWorkspaceToFavorites: addWorkspaceToFavorites(requestor, optionsToSend), + // Duplicate of addItemsToFavorites + addMultipleToFavorites: addItemsToFavorites(requestor, optionsToSend), }; }; + //type FavoritesApi = { // listFavorites : listFavorites, // addItemsToFavorites : addItemsToFavorites, diff --git a/lib/favorites/sharedTypes.ts b/lib/favorites/sharedTypes.ts index 8572060..4133ec6 100644 --- a/lib/favorites/sharedTypes.ts +++ b/lib/favorites/sharedTypes.ts @@ -1,7 +1,14 @@ -import { OptionsToSend, Requestor } from "../types"; +import { OptionsToSend, Requestor } from '../types'; export type Pagination = { + /** + * @description Which page to return. Defaults to 1 if not specified. If you specify a value greater than the total number of pages, the last page of results is returned. + * + */ page?: number; + /** + * @description The maximum number of items to return per page. Unless otherwise stated for a specific endpoint, defaults to 100. If only page is specified, defaults to a page size of 100. For reports, the default is 100 rows. If you need larger sets of data from your report, returns a maximum of 10,000 rows per request. + */ pageSize?: number; }; @@ -13,20 +20,39 @@ export type PaginationResponse = { }; export enum FavoritableResource { - Folder = "folder", - Report = "report", - Sheet = "sheet", - Sight = "sight", - Template = "template", - Workspace = "workspace", + Folder = 'folder', + Report = 'report', + Sheet = 'sheet', + Sight = 'sight', + Template = 'template', + Workspace = 'workspace', } +export enum ResponseStatusMessage { + PartialSuccess = 'PARTIAL_SUCCESS', + SUCCESS = 'SUCCESS', +} + +export enum ResultCode { + Success = 0, + PartialSuccess = 3, +} + +export type PostResult = { + /** + * @description Message that indicates the outcome of the request. (One of SUCCESS or PARTIAL_SUCCESS) + */ + message: ResponseStatusMessage; + /** + * @description number indicating result status: 0 Success, 3 Partial Success of Bulk Operation + * + */ + resultCode: ResultCode; +}; + export type FavoriteItem = { objectId: string; type: FavoritableResource; }; -export type ApiCreator = ( - requestor: Requestor, - optionsToSend: OptionsToSend -) => T; +export type ApiGenerator = (requestor: Requestor, optionsToSend: OptionsToSend) => T;