├── .tool-versions ├── .editorconfig ├── .ncurc.js ├── .prettierignore ├── config ├── tsconfig.flexible.json ├── exports │ ├── tsconfig.module.json │ ├── rollup.config.js │ └── build-tests.js └── tsconfig.strict.json ├── .travis.yml ├── .gitignore ├── src ├── index.spec.ts ├── interfaces │ ├── obj.interaction-event.ts │ ├── obj.before-fetch.ts │ ├── obj.end-user.ts │ ├── index.ts │ ├── obj.file.ts │ ├── obj.customer.ts │ ├── obj.record.ts │ ├── obj.load-options.ts │ ├── obj.validation-response.ts │ ├── obj.meta.ts │ ├── obj.hooks.ts │ ├── results.interface.ts │ ├── obj.theme.ts │ └── obj.settings.ts ├── utils │ └── insertCss.ts ├── index.ts ├── document.contains.polyfill.ts ├── stats.ts ├── upload-file.ts ├── user.ts ├── streamed-results.ts ├── results.ts └── importer.ts ├── .github ├── codeql │ └── codeql-config.yml ├── CONTRIBUTING.md ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE.md └── workflows │ └── codeql-analysis.yml ├── tslint.json ├── .npmignore ├── .prettierrc ├── serve.js ├── tsconfig.json ├── LICENSE ├── README.md ├── package.json └── CHANGELOG.md /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 9.5.0 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.ts] 2 | indent_size=2 3 | -------------------------------------------------------------------------------- /.ncurc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reject: ['ava', 'penpal', 'rollup'] 3 | } 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | spec/adapter.js 3 | node_modules 4 | .vscode 5 | types 6 | *.html 7 | -------------------------------------------------------------------------------- /config/tsconfig.flexible.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strictNullChecks": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 15 5 | after_success: 6 | - npm run send-coverage 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | test 4 | src/**.js 5 | 6 | coverage 7 | .nyc_output 8 | *.log 9 | .idea 10 | -------------------------------------------------------------------------------- /src/index.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | /** 4 | * @TODO build tests 5 | */ 6 | test('Stub Passing Test', (t) => { 7 | t.pass() 8 | }) 9 | -------------------------------------------------------------------------------- /src/interfaces/obj.interaction-event.ts: -------------------------------------------------------------------------------- 1 | export interface IInteractionEvent { 2 | mousemove: number 3 | keydown: number 4 | mousedown: number 5 | } 6 | -------------------------------------------------------------------------------- /.github/codeql/codeql-config.yml: -------------------------------------------------------------------------------- 1 | name: "Extended security suite and code quality suite" 2 | disable-default-queries: false 3 | queries: 4 | - uses: security-and-quality 5 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint-config-standard", 4 | "tslint-config-prettier" 5 | ], 6 | "rules" : { 7 | "no-floating-promises": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | config 3 | examples 4 | test 5 | tsconfig.json 6 | tslint.json 7 | .travis.yml 8 | .github 9 | build/temp 10 | build/docs 11 | 12 | coverage 13 | .nyc_output 14 | *.log 15 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Check out GitHub's [CONTRIBUTING.md help center article](https://help.github.com/articles/setting-guidelines-for-repository-contributors/) for more information. 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "jsxSingleQuote": true, 4 | "printWidth": 100, 5 | "semi": false, 6 | "singleQuote": true, 7 | "tabWidth": 2, 8 | "trailingComma": "none", 9 | "useTabs": false 10 | } 11 | -------------------------------------------------------------------------------- /src/interfaces/obj.before-fetch.ts: -------------------------------------------------------------------------------- 1 | export interface IBeforeFetchRequest { 2 | operation: string 3 | variables: Record 4 | } 5 | 6 | export interface IBeforeFetchResponse { 7 | headers?: Record 8 | } 9 | -------------------------------------------------------------------------------- /config/exports/tsconfig.module.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "outDir": "../../build/module", 5 | "rootDir": "../../src", 6 | "module": "es6", 7 | "declaration": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/interfaces/obj.end-user.ts: -------------------------------------------------------------------------------- 1 | import { CustomerObject } from './obj.customer' 2 | 3 | export interface EndUserObject extends CustomerObject { 4 | /** 5 | * The unique UUID Flatfile assigns to the user of your import 6 | */ 7 | id: string 8 | } 9 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) 2 | 3 | 4 | 5 | * **What is the current behavior?** (You can also link to an open issue here) 6 | 7 | 8 | 9 | * **What is the new behavior (if this is a feature change)?** 10 | 11 | 12 | 13 | * **Other information**: 14 | -------------------------------------------------------------------------------- /config/tsconfig.strict.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strictNullChecks": true, 4 | "forceConsistentCasingInFileNames": true, 5 | "noFallthroughCasesInSwitch": true, 6 | "noImplicitAny" : true, 7 | "noImplicitReturns": true, 8 | "noImplicitThis": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /serve.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const path = require('path') 3 | const app = express() 4 | 5 | app.set('port', 8080) 6 | 7 | app.use(express.static(path.join(__dirname, '/build/dist/'))) 8 | 9 | const server = app.listen(app.get('port'), function () { 10 | const port = server.address().port 11 | console.log('Adapter listening on port ' + port) 12 | }) 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * **I'm submitting a ...** 2 | [ ] bug report 3 | [ ] feature request 4 | [ ] question about the decisions made in the repository 5 | [ ] question about how to use this project 6 | 7 | * **Summary** 8 | 9 | 10 | 11 | * **Other information** (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. StackOverflow, personal fork, etc.) 12 | -------------------------------------------------------------------------------- /src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from './obj.before-fetch' 2 | export * from './obj.customer' 3 | export * from './obj.end-user' 4 | export * from './obj.file' 5 | export * from './obj.hooks' 6 | export * from './obj.interaction-event' 7 | export * from './obj.load-options' 8 | export * from './obj.meta' 9 | export * from './obj.record' 10 | export * from './obj.settings' 11 | export * from './obj.theme' 12 | export * from './obj.validation-response' 13 | export * from './results.interface' 14 | -------------------------------------------------------------------------------- /src/interfaces/obj.file.ts: -------------------------------------------------------------------------------- 1 | export interface FileObject { 2 | /** 3 | * A unique UUID referencing this file in the Flatfile system 4 | */ 5 | id: string 6 | 7 | /** 8 | * The original filename on the user's system 9 | */ 10 | filename: string 11 | 12 | /** 13 | * The size of the file in bytes 14 | */ 15 | filesize: number 16 | 17 | /** 18 | * The type of file 19 | */ 20 | filetype: string 21 | 22 | /** 23 | * A securely signed url giving you temporary access to download the file 24 | */ 25 | url: string 26 | } 27 | -------------------------------------------------------------------------------- /src/interfaces/obj.customer.ts: -------------------------------------------------------------------------------- 1 | export interface CustomerObject { 2 | /** 3 | * Your internal ID reference for this user (required) 4 | */ 5 | userId: string 6 | 7 | /** 8 | * A name to reference this person 9 | */ 10 | name?: string 11 | 12 | /** 13 | * An optional email address for this person 14 | */ 15 | email?: string 16 | 17 | /** 18 | * The company name the user is currently operating under 19 | */ 20 | companyName?: string 21 | 22 | /** 23 | * Your internal ID for the company the user is currently operating under 24 | */ 25 | companyId?: string 26 | } 27 | -------------------------------------------------------------------------------- /src/utils/insertCss.ts: -------------------------------------------------------------------------------- 1 | let styleElement 2 | 3 | export const insertCss = (css: string) => { 4 | if (!css) { 5 | return 6 | } 7 | 8 | if (styleElement) { 9 | return 10 | } 11 | 12 | styleElement = document.createElement('style') 13 | styleElement.setAttribute('type', 'text/css') 14 | document.querySelector('head')?.appendChild(styleElement) 15 | 16 | if (css.charCodeAt(0) === 0xfeff) { 17 | css = css.substr(1) 18 | } 19 | 20 | if (styleElement.styleSheet) { 21 | styleElement.styleSheet.cssText = css 22 | } else { 23 | styleElement.textContent = css 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/interfaces/obj.record.ts: -------------------------------------------------------------------------------- 1 | export interface RecordObject { 2 | /** 3 | * Did the user delete this row before submitting 4 | */ 5 | deleted: boolean 6 | 7 | /** 8 | * Did this row pass validation 9 | */ 10 | valid: boolean 11 | 12 | /** 13 | * The original sequence of this row in the uploaded file 14 | */ 15 | sequence: number 16 | 17 | /** 18 | * The fully mapped final data for this row 19 | */ 20 | data: any 21 | } 22 | 23 | export type RawRecordObject = Record & { $custom?: Record } 24 | export type HookRecordObject = { [key: string]: string | number } 25 | -------------------------------------------------------------------------------- /src/interfaces/obj.load-options.ts: -------------------------------------------------------------------------------- 1 | import { IValidationResponse } from './obj.validation-response' 2 | 3 | export interface LoadOptionsObject { 4 | /** 5 | * The number of records to receive per chunk 6 | */ 7 | inChunks?: number 8 | 9 | /** 10 | * Provide a CSV string, file url, list of row arrays, or list of input objects 11 | */ 12 | source?: IPrimitive[][] | string | InputObject[] 13 | } 14 | 15 | export type IPrimitive = string | boolean | null | number 16 | 17 | export interface IPrimitiveObject { 18 | [key: string]: IPrimitive 19 | } 20 | 21 | export interface InputObject { 22 | sequence?: number 23 | data: IPrimitiveObject 24 | errors?: IValidationResponse[] 25 | } 26 | -------------------------------------------------------------------------------- /src/interfaces/obj.validation-response.ts: -------------------------------------------------------------------------------- 1 | export interface IValidationResponse { 2 | /** 3 | * A string referencing the key affected by the problem 4 | */ 5 | key: string 6 | 7 | /** 8 | * The validation error message 9 | */ 10 | message: string 11 | 12 | /** 13 | * The type of validation response 14 | */ 15 | level?: 'error' | 'warning' | 'info' 16 | } 17 | 18 | export interface IDataHookResponse { 19 | [key: string]: IDataHookRecord 20 | } 21 | 22 | export interface IDataHookRecord { 23 | value?: string | boolean | number 24 | info?: IDataHookInfo[] 25 | } 26 | 27 | export interface IDataHookInfo { 28 | message: string 29 | level?: 'error' | 'warning' | 'info' 30 | } 31 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import 'promise-polyfill/dist/polyfill' 2 | 3 | import registerDocumentContainsPolyfill from './document.contains.polyfill' 4 | registerDocumentContainsPolyfill() 5 | 6 | import { FlatfileImporter } from './importer' 7 | 8 | export { 9 | ISettings, 10 | CustomerObject, 11 | FieldHookCallback, 12 | LoadOptionsObject, 13 | IVirtualFieldOptions, 14 | IInteractionEvent, 15 | IBeforeFetchRequest, 16 | IBeforeFetchResponse, 17 | StepHookCallback, 18 | RecordObject, 19 | IDataHookResponse, 20 | ScalarDictionary, 21 | Nullable, 22 | IPrimitive, 23 | IValidationResponse, 24 | IDictionary, 25 | FlatfileResults 26 | } from './interfaces' 27 | 28 | export default FlatfileImporter 29 | -------------------------------------------------------------------------------- /src/document.contains.polyfill.ts: -------------------------------------------------------------------------------- 1 | const contains = function (other) { 2 | if (arguments.length < 1) { 3 | throw new TypeError('1 argument is required') 4 | } 5 | if (typeof other !== 'object') { 6 | throw new TypeError('Argument 1 (”other“) to Node.contains must be an instance of Node') 7 | } 8 | 9 | let node = other 10 | do { 11 | if (this === node) { 12 | return true 13 | } 14 | if (node) { 15 | node = node.parentNode 16 | } 17 | } while (node) 18 | 19 | return false 20 | } 21 | 22 | export default function registerDocumentContainsPolyfill() { 23 | // tslint:disable-next-line 24 | if (typeof document === 'object' && typeof document.contains !== 'function') { 25 | Object.getPrototypeOf(document).contains = contains 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/stats.ts: -------------------------------------------------------------------------------- 1 | import { Meta } from './interfaces' 2 | 3 | export class Stats { 4 | private $meta: Meta 5 | 6 | /** 7 | * The number of rows in the parsed data 8 | */ 9 | originalRows: number 10 | 11 | /** 12 | * The number of rows that were submitted 13 | */ 14 | acceptedRows: number | null 15 | 16 | /** 17 | * The number of columns in the parsed data 18 | */ 19 | originalColumns: number | null 20 | 21 | /** 22 | * The number of columns submitted 23 | */ 24 | matchedColumns: number | null 25 | 26 | constructor(meta: Meta) { 27 | this.$meta = meta 28 | 29 | this.originalRows = this.$meta.count_rows 30 | this.acceptedRows = this.$meta.count_rows_accepted || null 31 | this.originalColumns = this.$meta.count_columns || null 32 | this.matchedColumns = this.$meta.count_columns_matched || null 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/upload-file.ts: -------------------------------------------------------------------------------- 1 | import { FileObject } from './interfaces' 2 | 3 | export class UploadFile { 4 | private $file: FileObject 5 | 6 | /** 7 | * A unique UUID referencing this file in the Flatfile system 8 | */ 9 | id: string 10 | 11 | /** 12 | * The original filename on the user's system 13 | */ 14 | filename: string 15 | 16 | /** 17 | * The size of the file in bytes 18 | */ 19 | filesize: number 20 | 21 | /** 22 | * The type of file 23 | */ 24 | filetype: string 25 | 26 | /** 27 | * A securely signed url giving you temporary access to download the file 28 | */ 29 | url: string 30 | 31 | constructor(file: FileObject) { 32 | this.$file = file 33 | this.id = this.$file.id 34 | this.filename = this.$file.filename 35 | this.filesize = this.$file.filesize 36 | this.filetype = this.$file.filetype 37 | this.url = this.$file.url 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /config/exports/rollup.config.js: -------------------------------------------------------------------------------- 1 | // rollup.config.js 2 | import commonjs from 'rollup-plugin-commonjs'; 3 | import nodeResolve from 'rollup-plugin-node-resolve'; 4 | import alias from 'rollup-plugin-alias'; 5 | 6 | const substituteModulePaths = { 7 | 'crypto': 'build/module/adapters/crypto.browser.js', 8 | 'hash.js': 'build/temp/hash.js' 9 | } 10 | 11 | export default { 12 | entry: 'build/module/index.js', 13 | sourceMap: true, 14 | moduleName: 'FlatfileImporter', 15 | plugins: [ 16 | alias(substituteModulePaths), 17 | nodeResolve({ 18 | browser: true 19 | }), 20 | commonjs({ 21 | namedExports: { 22 | // left-hand side can be an absolute path, a path 23 | // relative to the current directory, or the name 24 | // of a module in node_modules 25 | 'eventemitter3': ['EventEmitter'] 26 | }}) 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./config/tsconfig.flexible", // also available: "./config/tsconfig.strict" 3 | "compilerOptions": { 4 | "target": "es5", 5 | "outDir": "build/main", 6 | "rootDir": "src", 7 | "moduleResolution": "node", 8 | "module": "commonjs", 9 | "declaration": true, 10 | "importHelpers": true, 11 | "inlineSourceMap": true, 12 | "listFiles": false, 13 | "traceResolution": false, 14 | "pretty": true, 15 | "skipLibCheck": true, 16 | "lib" : [ 17 | "es6", "dom" 18 | ], 19 | "types" : [ 20 | "node" 21 | ], 22 | "baseUrl": ".", // required for "paths" 23 | "paths": { 24 | "typescript-starter": ["src/index.ts"] // write tests without relative paths 25 | } 26 | }, 27 | "include": [ 28 | "src/**/*.ts", 29 | "src/**/*.d.ts" 30 | ], 31 | "exclude": [ 32 | "node_modules/**" 33 | ], 34 | "compileOnSave": false 35 | } 36 | -------------------------------------------------------------------------------- /src/user.ts: -------------------------------------------------------------------------------- 1 | import { EndUserObject } from './interfaces' 2 | 3 | export class EndUser { 4 | private $user: EndUserObject 5 | 6 | /** 7 | * The UUID referencing the user's record stored in Flatfile 8 | */ 9 | id: string 10 | 11 | /** 12 | * Your internal ID reference for this user (required) 13 | */ 14 | userId: string 15 | 16 | /** 17 | * The user's full name if you provided it 18 | */ 19 | name: string | undefined 20 | 21 | /** 22 | * The user's email if you provided it 23 | */ 24 | email: string | undefined 25 | 26 | /** 27 | * The company name the user is currently operating under 28 | */ 29 | companyName: string | undefined 30 | 31 | /** 32 | * The company name the user is currently operating under 33 | */ 34 | companyId: string | undefined 35 | 36 | constructor(meta: EndUserObject) { 37 | this.$user = meta 38 | 39 | this.id = this.$user.id 40 | this.userId = this.$user.userId 41 | this.name = this.$user.name 42 | this.email = this.$user.name 43 | this.companyName = this.$user.companyName 44 | this.companyId = this.$user.companyId 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jason Dreyzehner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/interfaces/obj.meta.ts: -------------------------------------------------------------------------------- 1 | import { EndUserObject } from './obj.end-user' 2 | import { FileObject } from './obj.file' 3 | 4 | export interface BaseMeta { 5 | batchID: string 6 | endUser?: EndUserObject 7 | status: string 8 | originalFile: FileObject | null 9 | csvFile: FileObject | null 10 | filename: string 11 | managed: boolean 12 | filetype: string 13 | manual: boolean 14 | config: object 15 | parsing_config: object 16 | count_rows: number 17 | count_rows_accepted: number 18 | count_columns: number 19 | count_columns_matched: number 20 | skipped_rows: number 21 | headers_raw: Array | null 22 | headers_matched: Array | null 23 | category_field_map?: object 24 | custom_columns: Array 25 | failure_reason: string 26 | submitted_at: string 27 | failed_at: string 28 | created_at: string 29 | handled_at: string 30 | matched_at: string 31 | stylesheet: StyleSheet | null 32 | } 33 | 34 | export interface Meta extends BaseMeta { 35 | inChunks?: number 36 | } 37 | 38 | export interface StreamedMeta extends BaseMeta { 39 | inChunks: number 40 | hasMore: boolean 41 | pointer: number 42 | } 43 | 44 | interface StyleSheet { 45 | [key: string]: { 46 | bold?: boolean 47 | italic?: boolean 48 | strike?: boolean 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/interfaces/obj.hooks.ts: -------------------------------------------------------------------------------- 1 | import { IDataHookRecord } from './obj.validation-response' 2 | 3 | export type STEP = 'upload' | 'match' | 'review' | 'match_field' 4 | export type Scalar = string | number | boolean | null | undefined 5 | export enum RULE_STATUS { 6 | PENDING, 7 | IGNORED, 8 | ACCEPTED 9 | } 10 | 11 | export type FieldHookCallback = ( 12 | values: [Scalar, number][], 13 | meta: any 14 | ) => [IDataHookRecord, number][] | Promise<[IDataHookRecord, number][]> 15 | 16 | interface IStepMeta { 17 | batchId: string 18 | fileName?: string 19 | fileSize?: number 20 | fileType?: string 21 | 22 | count_rows?: number 23 | count_columns?: number 24 | count_columns_matched?: number 25 | 26 | headers_raw?: { 27 | index: number 28 | letter: string 29 | value?: string 30 | }[] 31 | headers_matched?: { 32 | index: number 33 | letter: string 34 | value?: string 35 | matched_key: string 36 | }[] 37 | 38 | sample?: [string[], number][] 39 | } 40 | 41 | interface IRule { 42 | sourceIndex: number 43 | targetKey?: string 44 | targetType?: string 45 | isCustom?: boolean 46 | optionsMap?: Record 47 | status: RULE_STATUS 48 | editable?: boolean 49 | } 50 | 51 | export type StepHooks = Record> 52 | export type StepHookCallback = ( 53 | payload: T extends 'match_field' ? IRule : IStepMeta 54 | ) => void | boolean | Promise 55 | -------------------------------------------------------------------------------- /config/exports/build-tests.js: -------------------------------------------------------------------------------- 1 | // this script watches the tests exported by typescript, copies them to the test directories, and modifies the require("PKG.NAME") statements to test each build 2 | const cpx = require("cpx"); 3 | const separator = require("path").sep; 4 | const Transform = require("stream").Transform; 5 | const pkg = require('../../package'); 6 | const req = (path) => 'require("' + path + '")'; 7 | const pathUp = (levels) => Array.from(Array(levels), () => '../').join(''); 8 | 9 | // replace instances of pkg.name with the proper route to the build being tested 10 | const makeTransform = (filePath, buildPath) => { 11 | const buildPathParts = buildPath.split(separator); 12 | // filePath includes build/main (-2), test/BUILD is 2 deep (+2), 13 | // remove filename (-1). Total is length - 2 14 | const pathToRoot = pathUp(filePath.split(separator).length - 1); 15 | const placeholder = req(pkg.name); 16 | return new Transform({ 17 | transform(chunk, encoding, done) { 18 | const str = chunk.toString(); 19 | const parts = str.split(placeholder) 20 | const newPath = req(pathToRoot + buildPath) 21 | const result = parts.join(newPath); 22 | this.push(result); 23 | done(); 24 | } 25 | }); 26 | } 27 | 28 | // copy, then watch for changes to the tests 29 | const testsFromRoot = 'build/main/**/*.spec.js'; 30 | const watchMode = process.argv.indexOf('-w') !== -1 ? true : false; 31 | const browserTests = process.argv.indexOf('--no-browser') !== -1 ? true : false; 32 | const task = watchMode ? cpx.watch : cpx.copy; 33 | 34 | task(testsFromRoot, 'test/main', { 35 | transform: (filePath) => makeTransform(filePath, pkg.main) 36 | }); 37 | if (!browserTests) { 38 | task(testsFromRoot, 'test/browser', { 39 | transform: (filePath) => makeTransform(filePath, pkg.browser.replace('.js', '.cjs.js')) 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | paths: 7 | - "src/**" 8 | pull_request: 9 | # The branches below must be a subset of the branches above 10 | branches: [master] 11 | paths: 12 | - "src/**" 13 | schedule: 14 | - cron: '0 2 * * 4' 15 | 16 | jobs: 17 | analyze: 18 | name: Analyze 19 | runs-on: ubuntu-latest 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | # Override automatic language detection by changing the below list 25 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 26 | language: ['javascript'] 27 | # Learn more... 28 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 29 | 30 | steps: 31 | - name: Checkout repository 32 | uses: actions/checkout@v2 33 | with: 34 | # We must fetch at least the immediate parents so that if this is 35 | # a pull request then we can checkout the head. 36 | fetch-depth: 2 37 | 38 | # If this run was triggered by a pull request event, then checkout 39 | # the head of the pull request instead of the merge commit. 40 | - run: git checkout HEAD^2 41 | if: ${{ github.event_name == 'pull_request' }} 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | config-file: ./.github/codeql/codeql-config.yml 48 | languages: ${{ matrix.language }} 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /src/streamed-results.ts: -------------------------------------------------------------------------------- 1 | import { RecordObject, StreamedMeta } from './interfaces' 2 | 3 | export class StreamedResults { 4 | /** 5 | * Helpful meta information 6 | */ 7 | private $meta: Pick 8 | 9 | /** 10 | * Raw data output from the importer 11 | */ 12 | private $data: Array 13 | 14 | /** 15 | * The raw output from the importer including all deleted rows 16 | * and sequence info 17 | */ 18 | rawOutput: Array 19 | 20 | /** 21 | * An array of valid data, key-mapped to the configuration provided 22 | * (alias of validData) 23 | */ 24 | data: Array 25 | 26 | /** 27 | * An array of valid data, key-mapped to the configuration provided 28 | */ 29 | validData: Array 30 | 31 | /** 32 | * Rows of data the user excluded from the final results, 33 | * key-mapped to the configuration provided 34 | */ 35 | deletedData: Array 36 | 37 | /** 38 | * All data from the original file upload including deleted rows, 39 | * key-mapped to the configuration provided 40 | */ 41 | allData: Array 42 | 43 | /** 44 | * The number of remaining chunks in the stream 45 | */ 46 | remainingChunks: number 47 | 48 | /** 49 | * The total number of chunks that will have to be received before data processing is completed 50 | */ 51 | totalChunks: number 52 | 53 | /** 54 | * The size of chunks as configured when requesting data. 55 | */ 56 | chunkSize: number 57 | 58 | /** 59 | * The current chunk by index 60 | */ 61 | currentChunk: number 62 | 63 | /** 64 | * The current chunk by index 65 | */ 66 | hasMore: boolean 67 | 68 | constructor(data: Array, meta: StreamedMeta) { 69 | this.$meta = meta 70 | this.$data = data 71 | 72 | this.rawOutput = this.$data 73 | this.validData = this.$data 74 | .filter((v) => v.valid) 75 | .filter((v) => !v.deleted) 76 | .map((v) => v.data) 77 | 78 | this.data = this.validData 79 | this.deletedData = this.$data.filter((v) => v.deleted).map((v) => v.data) 80 | this.allData = this.$data.map((v) => v.data) 81 | this.remainingChunks = Math.ceil((this.totalChunks - this.currentChunk) / this.$meta.inChunks) 82 | this.totalChunks = Math.ceil(this.$meta.count_rows_accepted / this.$meta.inChunks) 83 | this.chunkSize = this.$meta.inChunks 84 | this.currentChunk = (this.$meta.pointer + this.chunkSize) / this.chunkSize 85 | this.hasMore = this.$meta.hasMore 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/interfaces/results.interface.ts: -------------------------------------------------------------------------------- 1 | import { EndUser } from '../user' 2 | import { Stats } from '../stats' 3 | import { UploadFile } from '../upload-file' 4 | import { StreamedResults } from '../streamed-results' 5 | import { Meta } from './obj.meta' 6 | import { RecordObject } from './obj.record' 7 | 8 | export interface FlatfileResults { 9 | /** 10 | * The raw output from the importer including all deleted rows 11 | * and sequence info 12 | */ 13 | readonly rawOutput: Array 14 | 15 | /** 16 | * An array of valid data, key-mapped to the configuration provided 17 | * (alias of validData) 18 | */ 19 | readonly data: Array 20 | 21 | /** 22 | * An array of valid data, key-mapped to the configuration provided 23 | */ 24 | readonly validData: Array 25 | 26 | /** 27 | * Rows of data the user excluded from the final results, 28 | * key-mapped to the configuration provided 29 | */ 30 | readonly deletedData: Array 31 | 32 | /** 33 | * All data from the original file upload including deleted rows, 34 | * key-mapped to the configuration provided 35 | */ 36 | readonly allData: Array 37 | 38 | /** 39 | * The uuid of the batch assigned by Flatfile (use this in internal 40 | * references for support purposes) 41 | */ 42 | readonly batchId: string 43 | 44 | /** 45 | * Stats and counts about this file upload 46 | */ 47 | readonly stats: Stats 48 | 49 | /** 50 | * The customer provided in setCustomer 51 | */ 52 | readonly customer: EndUser | null 53 | 54 | /** 55 | * A File object of the originally uploaded file stored as an AWS url 56 | */ 57 | readonly originalFile: UploadFile | null 58 | 59 | /** 60 | * Same as originalFile unless it was uploaded in xls format, in which case this is the converted csv file stored as an AWS url 61 | */ 62 | readonly csvFile: UploadFile | null 63 | 64 | /** 65 | * The filename of the originally uploaded file 66 | */ 67 | readonly fileName: string | null 68 | 69 | /** 70 | * If the final upload is managed by a private endpoint or not 71 | */ 72 | readonly managed: boolean 73 | 74 | /** 75 | * If the data was entered manually instead of via file upload or not 76 | */ 77 | readonly manual: boolean 78 | 79 | /** 80 | * The parsed and bootstrapped config object used by this importer instance 81 | */ 82 | readonly config: object 83 | 84 | /** 85 | * The configuration used by the csv parser PapaParse: https://www.papaparse.com/docs#config 86 | */ 87 | readonly parsingConfig: object 88 | 89 | /** 90 | * The invalid rows that were skipped on submission 91 | */ 92 | readonly skippedRows: number | null 93 | 94 | /** 95 | * The headers before they were matched as given in the original file 96 | */ 97 | readonly headersRaw: Array | null 98 | 99 | /** 100 | * The headers after they are matched 101 | */ 102 | readonly headersMatched: Array | null 103 | 104 | /** 105 | * readonly the next chunk of records 106 | */ 107 | nextChunk: () => Promise 108 | 109 | /** 110 | * An array of any columns that were created during import 111 | */ 112 | readonly customColumns: Array 113 | 114 | /** 115 | * A mapping of source values to tarreadonly category values 116 | */ 117 | readonly categoryFieldMap: object | null 118 | 119 | /** 120 | * The reason for the failure if there was a failure 121 | */ 122 | readonly failureReason: string | null 123 | 124 | /** 125 | * The time that the data was submitted 126 | */ 127 | readonly submittedAt: string | null 128 | 129 | /** 130 | * The time that the import failed if it failed 131 | */ 132 | readonly failedAt: string | null 133 | 134 | /** 135 | * The time the data began the import, whether via file upload or manual data entry 136 | */ 137 | readonly createdAt: string 138 | 139 | /** 140 | * The stylesheet of the cells. Make sure to use `allowFormatting=true` in the config. 141 | */ 142 | readonly stylesheet: Meta['stylesheet'] | null 143 | } 144 | -------------------------------------------------------------------------------- /src/interfaces/obj.theme.ts: -------------------------------------------------------------------------------- 1 | import * as CSS from 'csstype' 2 | 3 | export interface ITheme { 4 | global?: { 5 | /** 6 | * default background color 7 | */ 8 | backgroundColor?: CSSObject['backgroundColor'] 9 | 10 | /** 11 | * default text color 12 | */ 13 | textColor?: CSSObject['color'] 14 | 15 | /** 16 | * default primary text color 17 | */ 18 | primaryTextColor?: CSSObject['color'] 19 | 20 | /** 21 | * default secondary text color 22 | */ 23 | secondaryTextColor?: CSSObject['color'] 24 | 25 | /** 26 | * default success color 27 | */ 28 | successColor?: CSSObject['color'] 29 | 30 | /** 31 | * default warning color 32 | */ 33 | warningColor?: CSSObject['color'] 34 | 35 | borderRadius?: CSSObject['borderRadius'] 36 | overlayColor?: CSSObject['backgroundColor'] 37 | fontFamily?: CSSObject['fontFamily'] 38 | } 39 | buttons?: { 40 | primary?: IStyleCSSButton 41 | secondary?: IStyleCSSButton 42 | success?: IStyleCSSButton 43 | tertiary?: IStyleCSSButton 44 | 45 | dropzoneUpload?: IStyleCSSButton 46 | 47 | headerMatchYes?: IStyleCSSButton 48 | headerMatchNo?: IStyleCSSButton 49 | 50 | columnMatchConfirm?: IStyleCSSButton 51 | columnMatchConfirmWithDupes?: IStyleCSSButton 52 | columnMatchIgnore?: IStyleCSSButton 53 | columnMatchInclude?: IStyleCSSButton 54 | columnMatchIncludeDropdown?: IStyleCSSButton 55 | columnMatchEdit?: IStyleCSSButton 56 | 57 | dialogConfirmYes?: IStyleCSSButton 58 | dialogConfirmNo?: IStyleCSSButton 59 | 60 | dialogFinalYes?: IStyleCSSButton 61 | dialogFinalNo?: IStyleCSSButton 62 | 63 | dialogFinalSuccess?: IStyleCSSButton 64 | dialogFinalError?: IStyleCSSButton 65 | 66 | dataSourceCancel?: IStyleCSSButton 67 | dataSourceContinue?: IStyleCSSButton 68 | } 69 | header?: { 70 | root?: IStyleCSS 71 | title?: IStyleCSS 72 | closeButton?: IStyleCSSButton 73 | } 74 | footer?: { 75 | root?: IStyleCSS 76 | } 77 | progressBar?: { 78 | root?: IStyleCSS 79 | current?: IStyleCSS 80 | complete?: IStyleCSS 81 | incomplete?: IStyleCSS 82 | arrow?: IStyleCSSSvg 83 | } 84 | dropzone?: { 85 | root?: IStyleCSS 86 | content?: IStyleCSS 87 | fileTypes?: IStyleCSS 88 | accepted?: IStyleCSS 89 | } 90 | manualInput?: { 91 | root?: IStyleCSS 92 | title?: IStyleCSS 93 | table?: { 94 | th?: IStyleCSSTableColumn 95 | td?: IStyleCSSTableRow 96 | rowIndex?: IStyleCSSTableRowIndex 97 | } 98 | } 99 | headerMatch?: { 100 | root?: IStyleCSS 101 | content?: IStyleCSS 102 | info?: IStyleCSS 103 | icon?: IStyleCSSSvg 104 | table?: { 105 | th?: IStyleCSSTableColumn 106 | td?: IStyleCSSTableRow 107 | } 108 | select?: IStyleCSSSelect 109 | } 110 | columnMatch?: { 111 | root?: IStyleCSS 112 | content?: IStyleCSS 113 | rule?: IStyleCSS 114 | autoMatchRule?: { 115 | root?: IStyleCSS 116 | icon?: IStyleCSSSvg 117 | description?: IStyleCSS 118 | field?: IStyleCSS 119 | } 120 | table?: { 121 | th?: IStyleCSSTableColumn 122 | td?: IStyleCSSTableRow 123 | } 124 | } 125 | dialog?: { 126 | root?: IStyleCSS 127 | content?: IStyleCSS 128 | footer?: IStyleCSS 129 | overlayColor?: string 130 | } 131 | dataSource?: { 132 | root?: IStyleCSS 133 | title?: IStyleCSS 134 | subtitle?: IStyleCSS 135 | step?: IStyleCSS 136 | footer?: IStyleCSS 137 | select?: IStyleCSSSelect 138 | } 139 | loader?: { 140 | root?: IStyleCSS 141 | overlayColor?: string 142 | } 143 | iterator?: { 144 | root?: IStyleCSS 145 | overlayColor?: string 146 | 147 | /** 148 | * loader bar foreground color 149 | */ 150 | barColor?: string 151 | 152 | /** 153 | * loader bar background color 154 | */ 155 | barBackground?: string 156 | } 157 | } 158 | 159 | /** 160 | * @NOTE private 161 | */ 162 | 163 | type CSSProperties = CSS.Properties 164 | 165 | type CSSPseudos = { [K in CSS.Pseudos]?: CSSObject } 166 | 167 | interface CSSObject extends CSSProperties, CSSPseudos { 168 | [key: string]: CSSObject | string | number | undefined 169 | } 170 | 171 | type IStyleCSS = Pick< 172 | CSSObject, 173 | | 'backgroundColor' 174 | | 'border' 175 | | 'borderBottom' 176 | | 'borderLeft' 177 | | 'borderRadius' 178 | | 'borderRight' 179 | | 'borderTop' 180 | | 'boxShadow' 181 | | 'color' 182 | | 'fontSize' 183 | | 'fontWeight' 184 | | 'lineHeight' 185 | | 'margin' 186 | | 'padding' 187 | | 'textAlign' 188 | > 189 | 190 | type IStyleCSSButton = IStyleCSS & Pick 191 | 192 | interface IStyleCSSSvg { 193 | fill?: string 194 | } 195 | 196 | type IStyleTable = Pick< 197 | CSSObject, 198 | 'backgroundColor' | 'borderColor' | 'color' | 'fontSize' | 'fontWeight' | 'textAlign' 199 | > 200 | 201 | interface IStyleCSSTableColumn extends IStyleTable { 202 | ':focus'?: IStyleTable 203 | ':hover'?: IStyleTable 204 | } 205 | 206 | interface IStyleCSSTableRow extends IStyleTable { 207 | ':focus'?: IStyleTable 208 | ':hover'?: IStyleTable 209 | ':invalid'?: IStyleTable 210 | } 211 | 212 | interface IStyleCSSTableRowIndex extends IStyleTable { 213 | ':empty'?: IStyleTable 214 | ':focus'?: IStyleTable 215 | ':hover'?: IStyleTable 216 | } 217 | 218 | type IStyleSelect = Pick< 219 | CSSObject, 220 | | 'backgroundColor' 221 | | 'border' 222 | | 'borderBottom' 223 | | 'borderLeft' 224 | | 'borderRadius' 225 | | 'borderRight' 226 | | 'borderTop' 227 | | 'boxShadow' 228 | | 'color' 229 | > 230 | 231 | interface IStyleCSSSelect extends IStyleSelect { 232 | ':focus'?: IStyleSelect 233 | ':active'?: IStyleSelect 234 | ':selected'?: IStyleSelect 235 | } 236 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flatfile.com CSV Importer Adapter 2 | 3 | [![Build Status](https://travis-ci.org/FlatFilers/Adapter.svg?branch=master)](https://travis-ci.org/FlatFilers/Adapter) 4 | [![Standard Version](https://img.shields.io/badge/release-standard%20version-brightgreen.svg)](https://github.com/conventional-changelog/standard-version) 5 | [![dependencies Status](https://img.shields.io/david/FlatFilers/adapter)](https://david-dm.org/FlatFilers/adapter) 6 | [![devDependencies Status](https://img.shields.io/david/dev/FlatFilers/adapter)](https://david-dm.org/FlatFilers/adapter?type=dev) 7 | 8 | 9 | A simple adapter for elegantly importing data (CSV, XLS & more) via [flatfile.com](https://www.flatfile.com) (Typescript, ES6, Browser) 10 | 11 | _*Important note:*_ While the below info is a basic way to get up and running, we recommend reading the developer docs → https://flatfile.com/developers/javascript/getting-started 12 | 13 | _*Another note:*_ If you are using Angular or React, we have specific packages for those. Check out our [React package on GitHub](https://github.com/FlatFilers/react-adapter) and [Angular package on GitHub](https://github.com/FlatFilers/angular-adapter). 14 | 15 | > **License Key** 16 | > 17 | > In order to setup, you need to [create or sign in to your flatfile.com account](https://flatfile.com) and obtain a license key. 18 | 19 | #### Changelog 20 | 21 | To view information about the latest releases and any minor/major changes, check out the [changelog here](./CHANGELOG.md). 22 | 23 | > Note: In version 2.8, previously available "deep-imports" (for Interfaces) have been moved to the root level of `@flatfile/adapter`. 24 | 25 | ## Using NPM 26 | 27 | If you don't like external dependencies, or you have a nice build system like Webpack in place. You can install and use Flatfile as an npm package. 28 | 29 | ```sh 30 | npm i @flatfile/adapter --save 31 | ``` 32 | 33 | 34 | ## Using CDN 35 | 36 | The latest version of the package is available via CDN so you can just drop it into your website and start using it. 37 | 38 | ```sh 39 | https://unpkg.com/@flatfile/adapter/build/dist/index.min.js 40 | ``` 41 | 42 | ## Quickstart 43 | Add the following code before the ending `` tag in your html. 44 | 45 | ```html 46 | 47 | 48 | 98 | ``` 99 | 100 | ## ES6 / Babel 101 | 102 | ```js 103 | const LICENSE_KEY = '00000000-0000-0000-0000-000000000000' // replace this with your license key 104 | 105 | const importer = new FlatfileImporter(LICENSE_KEY, { 106 | type: 'Robot', 107 | fields: [ 108 | { 109 | label: 'Name', 110 | key: 'name' 111 | } 112 | ] 113 | }) 114 | 115 | // More info: https://flatfile.com/developers/javascript/getting-started/#the-basics 116 | importer.setCustomer({ 117 | userId: '1' 118 | }) 119 | 120 | document.querySelector('button').addEventListener('click', async () => { 121 | try{ 122 | const results = await importer.requestDataFromUser() 123 | 124 | importer.displayLoader('Please wait while your data is loading') 125 | 126 | // do something with your data 127 | setTimeout(() => { 128 | // console.log(results) 129 | 130 | importer.displaySuccess('You are all done!') 131 | }, 1000) 132 | }catch(e){ 133 | // handle a failed upload 134 | } 135 | }) 136 | ``` 137 | 138 | ## Data hooks 139 | Flatfile's Data Hooks are a useful data healing element to re-format, validate and/or correct data automatically during the import without the user having to correct manually. 140 | 141 | More information: [Getting started with Data Hooks](https://flatfile.com/developers/javascript/datahooks) 142 | 143 | ```js 144 | const importer = new FlatfileImporter(LICENSE_KEY, { 145 | type: 'Robot', 146 | fields: [ 147 | { 148 | label: 'Name', 149 | key: 'name' 150 | } 151 | ] 152 | }) 153 | 154 | importer.setCustomer({ 155 | userId: '1' 156 | }) 157 | 158 | // adding a data hook that will add 'Jr.' to the name in each row 159 | importer.registerRecordHook((row) => { 160 | // Example row: {name: 'John'} 161 | const result = {}; 162 | 163 | if (row.name) { 164 | result.name = { 165 | value: `${row.name} Jr.` 166 | }; 167 | } 168 | 169 | return result; 170 | }); 171 | ``` 172 | 173 | ## Themes 174 | Theming gives you independent control over the look and feel of Flatfile Portal. With this, you can adjust both a global styles and individual components within the importer, as well as control the CSS pseudo-class :hover & :focus on buttons. 175 | 176 | More information: [Custom Themes for Flatfile Portal](https://flatfile.com/developers/javascript/themes) 177 | 178 | ```js 179 | const importer = new FlatfileImporter(LICENSE_KEY, { 180 | type: 'Robot', 181 | fields: [ 182 | { 183 | label: 'Name', 184 | key: 'name' 185 | } 186 | ], 187 | theme: { 188 | global: { 189 | backgroundColor: '#212327', 190 | textColor: '#c2c3c3', 191 | primaryTextColor: '#c2c3c3', 192 | secondaryTextColor: '#c2c3c3', 193 | successColor: '#c2c3c3', 194 | warningColor: '#c2c3c3' 195 | }, 196 | // other keys below 197 | } 198 | }) 199 | ``` 200 | 201 | ## Promise Overrides 202 | Flatfile includes a basic native compatible Promise shim for IE support. You can override this with your preferred library by using the following global setting: 203 | 204 | ```js 205 | FlatfileImporter.Promise = Bluebird.Promise 206 | ``` 207 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@flatfile/adapter", 3 | "version": "2.9.6", 4 | "description": "A lightweight TypeScript/JavaScript adapter for working with Flatfile's Portal", 5 | "main": "build/main/index.js", 6 | "typings": "build/main/index.d.ts", 7 | "module": "build/module/index.js", 8 | "browser": "build/browser/index.js", 9 | "repository": "https://github.com/flatfilers/adapter", 10 | "author": "David Boskovic ", 11 | "homepage": "https://flatfile.com", 12 | "license": "MIT", 13 | "bugs": { 14 | "url": "https://github.com/FlatFilers/adapter/issues" 15 | }, 16 | "scripts": { 17 | "info": "npm-scripts-info", 18 | "build": "trash build && nqr build:main && nqr build:module && nqr build:browser-deps && nqr build:browser && nqr build:browser-cjs && nqr build:browser-dist && nqr build:resolve-sourcemaps", 19 | "build:main": "tsc -p tsconfig.json", 20 | "build:module": "tsc -p config/exports/tsconfig.module.json", 21 | "build:browser-deps": "mkdirp build/temp && browserify node_modules/hash.js/lib/hash.js --standalone hash -o build/temp/hash.js", 22 | "build:browser": "rollup -c config/exports/rollup.config.js -f es -o build/browser/index.js", 23 | "build:browser-dist": "rollup -c config/exports/rollup.config.js -f iife -o build/dist/index.js && nqr build:browser-dist-minify", 24 | "build:browser-dist-local": "rollup -c config/exports/rollup.config.js -f iife -o build/dist/index.js", 25 | "build:browser-dist-minify": "uglifyjs -m -c -o build/dist/index.min.js -- build/dist/index.js", 26 | "build:browser-cjs": "rollup -c config/exports/rollup.config.js -f cjs -o build/browser/index.cjs.js", 27 | "build:resolve-sourcemaps": "sorcery -i build/browser/index.js && sorcery -i build/browser/index.cjs.js", 28 | "build:tests": "trash test && node config/exports/build-tests.js", 29 | "lint": "tslint --project . --type-check src/**/*.ts", 30 | "lint:prettier": "prettier --config .prettierrc --ignore-path .prettierignore --check src", 31 | "lint:prettier:fix": "prettier --config .prettierrc --ignore-path .prettierignore --write src", 32 | "unit": "nqr build && nqr build:tests && nyc ava", 33 | "check-coverage": "nyc check-coverage --lines 100 --functions 100 --branches 100", 34 | "test": "nqr lint && nqr unit && nqr check-coverage", 35 | "watch": "nqr build && nqr build:tests -- --no-browser && concurrently -r --kill-others 'npm run --silent build:main -- -w' 'npm run --silent build:tests -- -w --no-browser' 'sleepms 2000 && ava --watch'", 36 | "cov": "nqr unit && nqr html-coverage && opn coverage/index.html", 37 | "serve:dev": "nqr build:module && nqr build:browser-dist-local; node serve.js", 38 | "html-coverage": "nyc report --reporter=html", 39 | "send-coverage": "nyc report --reporter=lcov > coverage.lcov && codecov", 40 | "docs": "nqr docs:html && opn build/docs/index.html", 41 | "docs:html": "typedoc src/index.ts --excludePrivate --mode file --theme default --out build/docs --includeDeclarations --excludeExternals", 42 | "docs:md": "typedoc src --readme none --theme markdown --excludePrivate --mode file --out ../Developers/docs/sdk --includeDeclarations --excludeExternals --platform docusaurus", 43 | "docs:json": "typedoc --mode file --json build/docs/typedoc.json src/index.ts", 44 | "docs:publish": "nqr docs:html && gh-pages -d build/docs", 45 | "changelog": "standard-version", 46 | "release": "nqr reset && nqr test && nqr docs:publish && nqr changelog", 47 | "reset": "git clean -dfx && git reset --hard && nqr" 48 | }, 49 | "scripts-info": { 50 | "info": "Display information about the scripts", 51 | "build": "(Trash and re)build the library", 52 | "lint": "Lint all typescript source files", 53 | "unit": "Build the library and run unit tests", 54 | "test": "Lint, build, and test the library", 55 | "watch": "Watch source files, rebuild library on changes, rerun relevant tests", 56 | "cov": "Run tests, generate the HTML coverage report, and open it in a browser", 57 | "docs": "Generate HTML API documentation and open it in a browser", 58 | "docs:publish": "Generate HTML API documentation and push it to GitHub Pages", 59 | "docs:json": "Generate API documentation in typedoc JSON format", 60 | "changelog": "Bump package.json version, update CHANGELOG.md, tag a release", 61 | "reset": "Delete all untracked files and reset the repo to the last commit", 62 | "release": "Clean, build, test, publish docs, and prepare release (a one-step publish process)" 63 | }, 64 | "engines": { 65 | "node": ">=4.5" 66 | }, 67 | "devDependencies": { 68 | "@types/node": "^14.14.21", 69 | "ava": "^0.21.0", 70 | "browserify": "^17.0.0", 71 | "concurrently": "^5.3.0", 72 | "cpx": "^1.5.0", 73 | "gh-pages": "^3.1.0", 74 | "hash.js": "^1.1.7", 75 | "husky": "^4.3.8", 76 | "lint-staged": "^10.5.3", 77 | "mkdirp": "^1.0.4", 78 | "npm-check-updates": "^10.2.5", 79 | "npm-scripts-info": "^0.3.9", 80 | "nyc": "^15.1.0", 81 | "opn-cli": "^4.1.0", 82 | "prettier": "^2.2.1", 83 | "rollup": "^0.44.0", 84 | "rollup-plugin-alias": "^1.2.0", 85 | "rollup-plugin-commonjs": "^8.0.2", 86 | "rollup-plugin-node-resolve": "^3.0.0", 87 | "rollup-watch": "^4.0.0", 88 | "sleep-ms": "^2.0.1", 89 | "sorcery": "^0.10.0", 90 | "standard-version": "^9.1.0", 91 | "trash-cli": "^4.0.0", 92 | "tslint": "^5.20.1", 93 | "tslint-config-prettier": "^1.18.0", 94 | "tslint-config-standard": "^9.0.0", 95 | "typedoc": "^0.20.14", 96 | "typedoc-plugin-markdown": "^3.4.0", 97 | "typescript": "^4.1.3", 98 | "uglify-js": "^3.12.4" 99 | }, 100 | "keywords": [ 101 | "csv", 102 | "importer", 103 | "import", 104 | "convert", 105 | "json", 106 | "parse", 107 | "parser", 108 | "csv-parse", 109 | "parse", 110 | "async", 111 | "await", 112 | "ES6", 113 | "ES7", 114 | "typescript" 115 | ], 116 | "nyc": { 117 | "exclude": [ 118 | "**/*.spec.js", 119 | "build/browser/**" 120 | ] 121 | }, 122 | "ava": { 123 | "source": [ 124 | "test/**/*.js", 125 | "build/**/*.js", 126 | "!build/**/*.spec.js" 127 | ] 128 | }, 129 | "dependencies": { 130 | "csstype": "^3.0.6", 131 | "element-class": "^0.2.2", 132 | "es6-promise": "^4.2.8", 133 | "eventemitter3": "^4.0.7", 134 | "express": "^4.17.1", 135 | "nqr": "^1.0.0", 136 | "penpal": "^3.0.1", 137 | "promise-polyfill": "^8.2.0", 138 | "ts-promise": "^2.2.0", 139 | "tslib": "^2.1.0", 140 | "when-dom-ready": "^1.2.12" 141 | }, 142 | "husky": { 143 | "hooks": { 144 | "pre-commit": "lint-staged" 145 | } 146 | }, 147 | "lint-staged": { 148 | "*.{js,ts,tsx}": [ 149 | "npm run lint", 150 | "npm run lint:prettier" 151 | ] 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/results.ts: -------------------------------------------------------------------------------- 1 | import { FlatfileImporter } from './importer' 2 | import { Stats } from './stats' 3 | import { Meta, RawRecordObject, RecordObject } from './interfaces' 4 | import { EndUser } from './user' 5 | import { UploadFile } from './upload-file' 6 | import { StreamedResults } from './streamed-results' 7 | import { FlatfileResults } from './interfaces/results.interface' 8 | 9 | export class Results implements FlatfileResults { 10 | /** 11 | * Information about the import 12 | */ 13 | private $meta: Meta 14 | 15 | /** 16 | * Raw data output from the importer 17 | */ 18 | private $data: Array 19 | 20 | /** 21 | * Instance of importer used to manage this file 22 | */ 23 | private $importer: FlatfileImporter 24 | 25 | /** 26 | * The raw output from the importer including all deleted rows 27 | * and sequence info 28 | */ 29 | rawOutput: Array 30 | 31 | /** 32 | * An array of valid data, key-mapped to the configuration provided 33 | * (alias of validData) 34 | */ 35 | data: Array 36 | 37 | /** 38 | * An array of valid data, key-mapped to the configuration provided 39 | */ 40 | validData: Array 41 | 42 | /** 43 | * Rows of data the user excluded from the final results, 44 | * key-mapped to the configuration provided 45 | */ 46 | deletedData: Array 47 | 48 | /** 49 | * All data from the original file upload including deleted rows, 50 | * key-mapped to the configuration provided 51 | */ 52 | allData: Array 53 | 54 | /** 55 | * The uuid of the batch assigned by Flatfile (use this in internal 56 | * references for support purposes) 57 | */ 58 | batchId: string 59 | 60 | /** 61 | * Stats and counts about this file upload 62 | */ 63 | stats: Stats 64 | 65 | /** 66 | * The customer provided in setCustomer 67 | */ 68 | customer: EndUser | null 69 | 70 | /** 71 | * A File object of the originally uploaded file stored as an AWS url 72 | */ 73 | originalFile: UploadFile | null 74 | 75 | /** 76 | * Same as originalFile unless it was uploaded in xls format, in which case this is the converted csv file stored as an AWS url 77 | */ 78 | csvFile: UploadFile | null 79 | 80 | /** 81 | * The filename of the originally uploaded file 82 | */ 83 | fileName: string | null 84 | 85 | /** 86 | * If the final upload is managed by a private endpoint or not 87 | */ 88 | managed: boolean 89 | 90 | /** 91 | * If the data was entered manually instead of via file upload or not 92 | */ 93 | manual: boolean 94 | 95 | /** 96 | * The parsed and bootstrapped config object used by this importer instance 97 | */ 98 | config: object 99 | 100 | /** 101 | * The configuration used by the csv parser PapaParse: https://www.papaparse.com/docs#config 102 | */ 103 | parsingConfig: object 104 | 105 | /** 106 | * The invalid rows that were skipped on submission 107 | */ 108 | skippedRows: number | null 109 | 110 | /** 111 | * The headers before they were matched as given in the original file 112 | */ 113 | headersRaw: Array | null 114 | 115 | /** 116 | * The headers after they are matched 117 | */ 118 | headersMatched: Array | null 119 | 120 | /** 121 | * An array of any columns that were created during import 122 | */ 123 | customColumns: Array 124 | 125 | /** 126 | * A mapping of source values to target category values 127 | */ 128 | categoryFieldMap: object | null 129 | 130 | /** 131 | * The reason for the failure if there was a failure 132 | */ 133 | failureReason: string | null 134 | 135 | /** 136 | * The time that the data was submitted 137 | */ 138 | submittedAt: string | null 139 | 140 | /** 141 | * The time that the import failed if it failed 142 | */ 143 | failedAt: string | null 144 | 145 | /** 146 | * The time the data began the import, whether via file upload or manual data entry 147 | */ 148 | createdAt: string 149 | 150 | /** 151 | * The stylesheet of the cells. Make sure to use `allowFormatting=true` in the config. 152 | */ 153 | stylesheet: Meta['stylesheet'] | null 154 | 155 | /** 156 | * Get the next chunk of records 157 | */ 158 | nextChunk: () => Promise 159 | 160 | constructor(data: Array, meta: Meta, importer: FlatfileImporter) { 161 | this.$meta = meta 162 | this.$data = data 163 | this.$importer = importer 164 | 165 | this.rawOutput = this.blobOnly(this.$data, 'rawOutput') 166 | 167 | this.validData = this.blobOnly( 168 | this.$data 169 | .filter((v) => v.valid) 170 | .filter((v) => !v.deleted) 171 | .map((v) => v.data), 172 | 'validData' 173 | ) 174 | 175 | this.data = this.blobOnly(this.validData, 'data') 176 | 177 | this.deletedData = this.blobOnly( 178 | this.$data.filter((v) => v.deleted).map((v) => v.data), 179 | 'deletedData' 180 | ) 181 | this.allData = this.blobOnly( 182 | this.$data.map((v) => v.data), 183 | 'allData' 184 | ) 185 | 186 | this.batchId = this.$meta.batchID 187 | this.stats = new Stats(this.$meta) 188 | this.customer = this.$meta.endUser ? new EndUser(this.$meta.endUser) : null 189 | this.originalFile = this.$meta.originalFile ? new UploadFile(this.$meta.originalFile) : null 190 | this.csvFile = this.getCSVFile() 191 | this.fileName = this.$meta.filename || null 192 | this.managed = this.$meta.managed || false 193 | this.manual = this.$meta.manual 194 | this.config = this.$meta.config 195 | this.parsingConfig = this.$meta.parsing_config 196 | this.skippedRows = this.$meta.skipped_rows || null 197 | this.headersRaw = this.$meta.headers_raw || null 198 | this.headersMatched = this.$meta.headers_matched || null 199 | this.customColumns = this.$meta.custom_columns 200 | this.categoryFieldMap = this.$meta.category_field_map || null 201 | this.failureReason = this.$meta.failure_reason || null 202 | this.submittedAt = this.$meta.submitted_at || null 203 | this.failedAt = this.$meta.failed_at || null 204 | this.createdAt = this.$meta.created_at 205 | this.stylesheet = this.$meta.stylesheet 206 | 207 | this.nextChunk = () => this.getNextChunk() 208 | } 209 | 210 | private getCSVFile() { 211 | if (this.$meta.originalFile) { 212 | if (this.$meta.originalFile.filetype === 'csv') { 213 | return new UploadFile(this.$meta.originalFile) 214 | } else { 215 | if (this.$meta.csvFile) { 216 | return new UploadFile(this.$meta.csvFile) 217 | } 218 | } 219 | } 220 | return null 221 | } 222 | 223 | private getNextChunk(): Promise { 224 | return new Promise((resolve, reject) => { 225 | if (!this.$meta.inChunks) { 226 | return reject( 227 | `"nextChunk()" is only accessible when using "inChunks". Please see docs for "requestDataFromUser".` 228 | ) 229 | } 230 | this.$importer.$ready.then((child) => { 231 | console.log('child.nextChunk()') 232 | child.nextChunk().then( 233 | (data) => { 234 | console.log('nextChunk()', data) 235 | resolve(data.results.length ? new StreamedResults(data.results, data.meta) : null) 236 | }, 237 | (err) => { 238 | console.log('nextChunk(err)', err) 239 | } 240 | ) 241 | }) 242 | }) 243 | } 244 | 245 | private blobOnly(v: T, method, alt = 'nextChunk()'): T { 246 | if (this.$meta.inChunks) { 247 | throw new Error( 248 | `"${method}" is not accessible when using "inChunks". Please see docs for "${alt}" instead.` 249 | ) 250 | } 251 | return v 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/interfaces/obj.settings.ts: -------------------------------------------------------------------------------- 1 | import { IPrimitive } from './obj.load-options' 2 | import { ITheme } from './obj.theme' 3 | 4 | export interface ISettings { 5 | /** 6 | * Type of record that is being imported. eg. "User", "Transaction" 7 | */ 8 | type: string 9 | 10 | /** 11 | * Adds custom header for importer 12 | */ 13 | title?: string 14 | 15 | /** 16 | * Configure the fields to map the uploaded data to. Easily setup validation, 17 | * format hinting and more. 18 | */ 19 | fields: IField[] 20 | 21 | /** 22 | * Configure a list of columns that should be ignored. 23 | */ 24 | ignoreColumns?: string[] 25 | 26 | /** 27 | * Specific overrides to allow theming. 28 | * @deprecated use `theme` 29 | */ 30 | styleOverrides?: IStyleOverrides 31 | 32 | /** 33 | * Overrides to allow theming for most Portal elements. 34 | */ 35 | theme?: ITheme 36 | 37 | /** 38 | * Boolean configuration which turns on dashboard functionality and sends data 39 | * to Flatfile's servers. 40 | */ 41 | managed?: boolean 42 | 43 | /** 44 | * Limits file upload size to the specified number of bytes. 45 | */ 46 | maxSize?: number 47 | 48 | /** 49 | * Limits file upload size to the specified number of rows. 50 | */ 51 | maxRecords?: number 52 | 53 | /** 54 | * Disables the ability of the user to input directly on the first step. 55 | */ 56 | disableManualInput?: boolean 57 | 58 | /** 59 | * Whether or not to allow importing extra fields 60 | * that you have not specified in your target field map. 61 | */ 62 | allowCustom?: boolean 63 | 64 | /** 65 | * Whether or not to allow submitting data that still has invalid cells in it. 66 | */ 67 | allowInvalidSubmit?: boolean 68 | 69 | /** 70 | * Whether or not to allow bold / italic / strike formatting of the cells. 71 | */ 72 | allowFormatting?: boolean 73 | 74 | /** 75 | * Whether or not to auto-detect headers 76 | */ 77 | autoDetectHeaders?: boolean 78 | 79 | /** 80 | * Allows use of non-standard fonts 81 | */ 82 | integrations?: IIntegrations 83 | 84 | /** 85 | * Allow overriding any internationalized value 86 | */ 87 | i18nOverrides?: IDictionary> | IDictionary 88 | 89 | /** 90 | * URL to post batch rows to on successful import 91 | */ 92 | webhookUrl?: string 93 | 94 | /** 95 | * Force the ability of the user to input directly on the first step. 96 | */ 97 | forceManualInput?: boolean 98 | 99 | /** 100 | * Specify the encoding of the file upload 101 | */ 102 | encoding?: string 103 | 104 | /** 105 | * Whether or not to allow users to select encoding 106 | */ 107 | displayEncoding?: boolean 108 | 109 | /** 110 | * Whether or not to show "Are you sure?" dialog before closing 111 | */ 112 | confirmClose?: boolean 113 | 114 | /** 115 | * Allows for additional rows to be added to the initial rows checked on import. 116 | */ 117 | preloadRowCount?: number 118 | 119 | /** 120 | * Boolean configuration which sets disables billing for development uploads 121 | */ 122 | devMode?: boolean 123 | 124 | /** 125 | * String for formatting dates when parsing Excel files on the server. 126 | */ 127 | dateFormat?: string 128 | 129 | /** 130 | * Toggles full screen mode. 131 | */ 132 | fullScreen?: boolean 133 | 134 | /** 135 | * Whether or not to hide the progress bar. 136 | */ 137 | hideProgressBar?: boolean 138 | 139 | /** 140 | * Prevent automatically trimming whitespace from cells 141 | */ 142 | preventAutoTrimming?: boolean 143 | } 144 | 145 | export type IField = IFieldBase | IFieldSelect 146 | 147 | export interface IFieldBase { 148 | key: string 149 | label: string 150 | description?: string 151 | shortDescription?: string 152 | alternates?: string[] 153 | validators?: IValidator[] 154 | type?: 'checkbox' | 'string' 155 | sizeHint?: number 156 | } 157 | 158 | export type IFieldSelectMatchStrategy = 'fuzzy' | 'exact' 159 | 160 | export interface IFieldSelect extends Omit { 161 | type: 'select' 162 | matchStrategy?: IFieldSelectMatchStrategy 163 | options: IFieldOption[] 164 | } 165 | 166 | export type IValidator = 167 | | IValidatorRegexDictionary 168 | | IValidatorRequiredWithDictionary 169 | | IValidatorOtherDictionary 170 | 171 | export interface IBaseValidatorDictionary { 172 | error?: string 173 | } 174 | 175 | export interface IRegexFlags { 176 | ignoreCase?: boolean 177 | multiline?: boolean 178 | dotAll?: boolean 179 | global?: boolean 180 | unicode?: boolean 181 | } 182 | 183 | export interface IValidatorRegexDictionary extends IBaseValidatorDictionary { 184 | validate: 'regex_matches' | 'regex_excludes' 185 | regex: string 186 | regexFlags?: IRegexFlags 187 | } 188 | 189 | export interface IValidatorRequiredWithSimpleDictionary extends IBaseValidatorDictionary { 190 | validate: 'required_with' | 'required_with_all' | 'required_without' | 'required_without_all' 191 | fields: string[] 192 | } 193 | 194 | export interface IValidatorRequiredWithValuesDictionary extends IBaseValidatorDictionary { 195 | validate: 196 | | 'required_with_values' 197 | | 'required_with_all_values' 198 | | 'required_without_values' 199 | | 'required_without_all_values' 200 | fieldValues: ScalarDictionary 201 | } 202 | 203 | export type IValidatorRequiredWithDictionary = 204 | | IValidatorRequiredWithSimpleDictionary 205 | | IValidatorRequiredWithValuesDictionary 206 | 207 | export interface IValidatorOtherDictionary extends IBaseValidatorDictionary { 208 | validate: 'required' | 'select' | 'unique' | 'boolean' 209 | } 210 | 211 | export interface IFieldOptionDictionary { 212 | value: string | number | null 213 | label: string 214 | alternates?: string[] 215 | } 216 | 217 | export type IFieldOption = IFieldOptionDictionary 218 | 219 | export interface II18nOverrides { 220 | otherLocales?: string[] 221 | 222 | /** 223 | * @deprecated use `importer.setLanguage('en')` 224 | */ 225 | setLanguage?: string | undefined 226 | 227 | overrides: IDictionary> 228 | } 229 | 230 | export interface IIntegrations { 231 | adobeFontsWebProjectId?: string 232 | googleFonts?: string 233 | } 234 | 235 | export interface IStyleOverrides { 236 | borderRadius?: string 237 | borderTop?: string 238 | borderImage?: string 239 | buttonHeight?: string 240 | primaryButtonColor?: string 241 | primaryButtonBorderColor?: string 242 | primaryButtonFontSize?: string 243 | primaryButtonFontColor?: string 244 | uploadButtonBackground?: string 245 | uploadButtonPadding?: string 246 | yesButtonBackground?: string 247 | yesButtonPadding?: string 248 | secondaryButtonColor?: string 249 | secondaryButtonBorderColor?: string 250 | secondaryButtonFontSize?: string 251 | secondaryButtonFontColor?: string 252 | noButtonColor?: string 253 | noButtonBorderColor?: string 254 | noButtonFontColor?: string 255 | yesButtonColor?: string 256 | yesButtonBorderColor?: string 257 | yesButtonFontColor?: string 258 | finalButtonSuccessColor?: string 259 | finalButtonSuccessBorderColor?: string 260 | finalButtonSuccessFontColor?: string 261 | finalButtonErrorColor?: string 262 | finalButtonErrorBorderColor?: string 263 | finalButtonErrorFontColor?: string 264 | invertedButtonColor?: string 265 | linkColor?: string 266 | linkAltColor?: string 267 | primaryTextColor?: string 268 | secondaryTextColor?: string 269 | errorColor?: string 270 | successColor?: string 271 | warningColor?: string 272 | borderColor?: string 273 | fontFamily?: string 274 | } 275 | 276 | export interface IDictionary { 277 | [key: string]: V 278 | } 279 | export type Nullable = T | undefined | null 280 | export type ScalarDictionary = IDictionary> 281 | 282 | export interface IVirtualFieldOptions { 283 | order?: number 284 | hideFields?: string[] 285 | } 286 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | 6 | ## [2.8.0](https://github.com/flatfilers/adapter/compare/v2.7.0...v2.8.0) (2020-04-20) 7 | 8 | #### Breaking Changes 9 | 10 | - All `default` exports have been removed, all publicly available interfaces or classes are now available from `@flatfile/adapter` (no deep-imports), with the exception being `FlatfileImporter` itself being exported from the default. 11 | 12 | _An example of these new imports and usage:_ 13 | 14 | ```ts 15 | import FlatfileImporter, { // <-- still found at the default export level 16 | FieldHookCallback, // <-- everything else public is now available via destructuring from @flatfile/adapter 17 | ISettings, 18 | CustomerObject, 19 | LoadOptionsObject, 20 | IVirtualFieldOptions, 21 | IInteractionEvent, 22 | IBeforeFetchRequest, 23 | IBeforeFetchResponse, 24 | StepHookCallback, 25 | RecordObject, 26 | IDataHookResponse, 27 | ScalarDictionary, 28 | Nullable, 29 | IPrimitive, 30 | IValidationResponse, 31 | IDictionary, 32 | FlatfileResults 33 | } from '@flatfile/adapter'; 34 | ``` 35 | 36 | - `FlatfileResult` is now found from the root of `import { FlatfileResult } from '@flatfile/adapter';` 37 | - Previously it was grabbed via default import from a deep-import `import FlatfileResults from '@flatfile/adapter/build/main/results';` 38 | 39 | 40 | ## [2.1.4](https://github.com/flatfilers/adapter/compare/v2.1.3...v2.1.4) (2020-11-03) 41 | 42 | 43 | 44 | 45 | ## [2.1.3](https://github.com/flatfilers/adapter/compare/v2.1.1...v2.1.3) (2020-11-03) 46 | 47 | 48 | ### Bug Fixes 49 | 50 | * **adapter:** only polyfill document if exists ([06b4ab7](https://github.com/flatfilers/adapter/commit/06b4ab7)) 51 | 52 | 53 | 54 | 55 | ## [0.2.11](https://github.com/flatfilers/adapter/compare/v0.2.9...v0.2.11) (2020-06-17) 56 | 57 | 58 | ### Bug Fixes 59 | 60 | * **imports:** release 0.2.10 ([a601586](https://github.com/flatfilers/adapter/commit/a601586)) 61 | 62 | 63 | 64 | 65 | ## [0.2.9](https://github.com/flatfilers/adapter/compare/v0.2.8...v0.2.9) (2020-02-04) 66 | 67 | 68 | 69 | 70 | ## [0.2.8](https://github.com/flatfilers/adapter/compare/v0.2.7...v0.2.8) (2020-02-04) 71 | 72 | 73 | 74 | 75 | ## [0.2.7](https://github.com/flatfilers/adapter/compare/v0.2.6...v0.2.7) (2020-01-29) 76 | 77 | 78 | 79 | 80 | ## [0.2.6](https://github.com/flatfilers/adapter/compare/v0.2.5...v0.2.6) (2020-01-20) 81 | 82 | 83 | 84 | 85 | ## [0.2.5](https://github.com/flatfilers/adapter/compare/v0.2.4...v0.2.5) (2020-01-17) 86 | 87 | 88 | 89 | 90 | ## [0.2.4](https://github.com/flatfilers/adapter/compare/v0.2.2...v0.2.4) (2020-01-07) 91 | 92 | 93 | 94 | 95 | ## [0.2.2](https://github.com/flatfilers/adapter/compare/v0.2.0...v0.2.2) (2019-10-24) 96 | 97 | 98 | 99 | 100 | # [0.2.0](https://github.com/flatfilers/adapter/compare/v0.1.16...v0.2.0) (2019-10-24) 101 | 102 | 103 | ### Features 104 | 105 | * add custom validator callbacks ([6ee8ef9](https://github.com/flatfilers/adapter/commit/6ee8ef9)) 106 | 107 | 108 | 109 | 110 | ## [0.1.16](https://github.com/flatfilers/adapter/compare/v0.1.15...v0.1.16) (2019-06-09) 111 | 112 | 113 | 114 | 115 | ## [0.1.15](https://github.com/flatfilers/adapter/compare/v0.1.14...v0.1.15) (2019-06-09) 116 | 117 | 118 | 119 | 120 | ## [0.1.14](https://github.com/flatfilers/adapter/compare/v0.1.13...v0.1.14) (2019-05-19) 121 | 122 | 123 | 124 | 125 | ## [0.1.13](https://github.com/flatfilers/adapter/compare/v0.1.12...v0.1.13) (2019-05-19) 126 | 127 | 128 | 129 | 130 | ## [0.1.12](https://github.com/flatfilers/adapter/compare/v0.1.11...v0.1.12) (2018-07-11) 131 | 132 | 133 | 134 | 135 | ## [0.1.11](https://github.com/flatfilers/adapter/compare/v0.1.10...v0.1.11) (2018-07-11) 136 | 137 | 138 | 139 | 140 | ## [0.1.10](https://github.com/flatfilers/adapter/compare/v0.1.9...v0.1.10) (2018-07-11) 141 | 142 | 143 | 144 | 145 | ## [0.1.9](https://github.com/flatfilers/adapter/compare/v0.1.8...v0.1.9) (2018-04-24) 146 | 147 | 148 | 149 | 150 | ## [0.1.8](https://github.com/flatfilers/adapter/compare/v0.1.7...v0.1.8) (2018-04-24) 151 | 152 | 153 | 154 | 155 | ## [0.1.7](https://github.com/flatfilers/adapter/compare/v0.1.6...v0.1.7) (2018-04-24) 156 | 157 | 158 | 159 | 160 | ## [0.1.6](https://github.com/flatfilers/adapter/compare/v0.1.5...v0.1.6) (2018-04-24) 161 | 162 | 163 | 164 | 165 | ## [0.1.5](https://github.com/flatfilers/adapter/compare/v0.1.4...v0.1.5) (2018-04-24) 166 | 167 | 168 | 169 | 170 | ## [0.1.4](https://github.com/flatfilers/adapter/compare/v0.1.3...v0.1.4) (2018-04-24) 171 | 172 | 173 | 174 | 175 | ## [0.1.3](https://github.com/flatfilers/adapter/compare/v0.1.2...v0.1.3) (2018-04-24) 176 | 177 | 178 | 179 | 180 | ## [0.1.2](https://github.com/flatfilers/adapter/compare/v0.1.1...v0.1.2) (2018-04-24) 181 | 182 | 183 | 184 | 185 | ## [0.1.1](https://github.com/flatfilers/adapter/compare/v0.1.0...v0.1.1) (2018-04-24) 186 | 187 | 188 | 189 | 190 | # [0.1.0](https://github.com/flatfilers/adapter/compare/v0.0.7...v0.1.0) (2018-04-24) 191 | 192 | 193 | 194 | 195 | ## [0.0.7](https://github.com/flatfilers/adapter/compare/v0.0.6...v0.0.7) (2018-03-27) 196 | 197 | 198 | 199 | 200 | ## [0.0.6](https://github.com/flatfilers/adapter/compare/v0.0.5...v0.0.6) (2018-01-03) 201 | 202 | 203 | 204 | 205 | ## [0.0.5](https://github.com/flatfilers/adapter/compare/v0.0.4...v0.0.5) (2018-01-03) 206 | 207 | 208 | 209 | 210 | ## [0.0.4](https://github.com/flatfilers/adapter/compare/v0.0.3...v0.0.4) (2018-01-03) 211 | 212 | 213 | 214 | 215 | ## [0.0.3](https://github.com/flatfilers/adapter/compare/v0.0.2...v0.0.3) (2018-01-03) 216 | 217 | 218 | 219 | 220 | ## 0.0.2 (2018-01-03) 221 | 222 | 223 | 224 | 225 | ## [1.4.1](https://github.com/bitjson/typescript-starter/compare/v1.4.0...v1.4.1) (2017-06-27) 226 | 227 | 228 | 229 | 230 | # [1.4.0](https://github.com/bitjson/typescript-starter/compare/v1.3.0...v1.4.0) (2017-03-02) 231 | 232 | 233 | ### Features 234 | 235 | * **gh-pages:** add package script for publishing docs to gh-pages ([1dfe830](https://github.com/bitjson/typescript-starter/commit/1dfe830)), closes [#14](https://github.com/bitjson/typescript-starter/issues/14) 236 | * **publish:** add one-step publish process ([7b9b857](https://github.com/bitjson/typescript-starter/commit/7b9b857)), closes [#15](https://github.com/bitjson/typescript-starter/issues/15) 237 | 238 | 239 | 240 | 241 | # [1.3.0](https://github.com/bitjson/typescript-starter/compare/v1.2.2...v1.3.0) (2017-03-01) 242 | 243 | 244 | ### Bug Fixes 245 | 246 | * **hash.js:** correctly pre-build hash.js for the browser ([1fe0b10](https://github.com/bitjson/typescript-starter/commit/1fe0b10)) 247 | * **watch:** exclude build/**/*.spec.js from ava to avoid double execution ([e365656](https://github.com/bitjson/typescript-starter/commit/e365656)) 248 | 249 | 250 | ### Features 251 | 252 | * **browser:** add browser build, tests, and sample sha256 library method ([01f67d1](https://github.com/bitjson/typescript-starter/commit/01f67d1)) 253 | * **watch:** use concurrently for the watch task ([7fa64b8](https://github.com/bitjson/typescript-starter/commit/7fa64b8)), closes [#11](https://github.com/bitjson/typescript-starter/issues/11) 254 | 255 | 256 | 257 | 258 | ## [1.2.2](https://github.com/bitjson/typescript-starter/compare/v1.2.1...v1.2.2) (2017-02-17) 259 | 260 | 261 | ### Bug Fixes 262 | 263 | * **tsconfig:** set rootDir option when outDir option is used ([3577caa](https://github.com/bitjson/typescript-starter/commit/3577caa)), closes [#9](https://github.com/bitjson/typescript-starter/issues/9) 264 | 265 | 266 | 267 | 268 | ## [1.2.1](https://github.com/bitjson/typescript-starter/compare/v1.2.0...v1.2.1) (2017-02-14) 269 | 270 | 271 | 272 | 273 | # [1.2.0](https://github.com/bitjson/node-typescript-starter/compare/v1.1.1...v1.2.0) (2017-02-14) 274 | 275 | 276 | ### Features 277 | 278 | * **github:** add sample GitHub issue template, PR template, and contributing guidelines ([9c95249](https://github.com/bitjson/node-typescript-starter/commit/9c95249)) 279 | * **watch:** add unified watch task with multiview ([973966e](https://github.com/bitjson/node-typescript-starter/commit/973966e)) 280 | 281 | 282 | 283 | 284 | ## [1.1.1](https://github.com/bitjson/node-typescript-starter/compare/v1.1.0...v1.1.1) (2017-02-13) 285 | 286 | 287 | 288 | 289 | # 1.1.0 (2017-02-13) 290 | 291 | 292 | ### Features 293 | 294 | * **examples:** improve browser usage example ([c8199e7](https://github.com/bitjson/node-typescript-starter/commit/c8199e7)) 295 | * **starter:** add changelogs and examples ([5f18048](https://github.com/bitjson/node-typescript-starter/commit/5f18048)) 296 | -------------------------------------------------------------------------------- /src/importer.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'eventemitter3' 2 | import whenDomReady from 'when-dom-ready' 3 | import elementClass from 'element-class' 4 | import Penpal from 'penpal' 5 | import { 6 | CustomerObject, 7 | FieldHookCallback, 8 | IBeforeFetchRequest, 9 | IBeforeFetchResponse, 10 | IDataHookResponse, 11 | IInteractionEvent, 12 | ISettings, 13 | IVirtualFieldOptions, 14 | HookRecordObject, 15 | LoadOptionsObject, 16 | Meta, 17 | RecordObject, 18 | StepHookCallback, 19 | StepHooks 20 | } from './interfaces' 21 | import { Results } from './results' 22 | import { insertCss } from './utils/insertCss' 23 | 24 | export class FlatfileImporter extends EventEmitter { 25 | public static Promise = Promise 26 | private static MOUNT_URL: string = 'https://portal-2.flatfile.io/?key=:key' 27 | 28 | /** 29 | * Promise that resolves when the handshake is completed between Flatfile.io and the adapter 30 | */ 31 | public $ready: Promise 32 | 33 | private apiKey: string 34 | private options: ISettings 35 | private customer?: CustomerObject 36 | private uuid: string 37 | 38 | // @ts-ignore 39 | private handshake: Penpal.IChildConnectionObject | null 40 | 41 | private $resolver: (data: any) => any 42 | private $rejecter: (err: any) => any 43 | private $networkErrorCallback?: (err: string) => void 44 | private $beforeFetchCallback?: (req: IBeforeFetchRequest) => IBeforeFetchResponse 45 | private $interactionEventCallback?: (req: IInteractionEvent) => void 46 | private $recordHook?: ( 47 | row: HookRecordObject, 48 | index: number, 49 | mode: string 50 | ) => IDataHookResponse | Promise 51 | private $virtualRecordHook?: ( 52 | row: HookRecordObject, 53 | index: number, 54 | mode: string 55 | ) => IDataHookResponse | Promise 56 | private $bulkInitRecordHook?: ( 57 | rows: [HookRecordObject, number][] 58 | ) => IDataHookResponse[] | Promise 59 | private $fieldHooks: Array<{ field: string; cb: FieldHookCallback }> = [] 60 | private $stepHooks: StepHooks = {} as StepHooks 61 | 62 | constructor(apiKey: string, options: ISettings, customer?: CustomerObject) { 63 | super() 64 | this.apiKey = apiKey 65 | this.options = options 66 | this.customer = customer 67 | this.uuid = this.$generateUuid() 68 | this.$ready = new FlatfileImporter.Promise((resolve, reject) => { 69 | this.$resolver = resolve 70 | this.$rejecter = reject 71 | }) 72 | 73 | whenDomReady(() => { 74 | this.initialize() 75 | }) 76 | } 77 | 78 | /** 79 | * This will by default always be `https://www.flatfile.io/importer/:key` unless you are 80 | * an enterprise customer that is self-hosting the application. In which case, this 81 | * will be the URL of your enterprise installd Flatfile importer index page 82 | */ 83 | public static setMountUrl(url: string): void { 84 | this.MOUNT_URL = url 85 | } 86 | 87 | /** 88 | * This allows you to opt into or out of specific versions of the Flatfile SDK 89 | */ 90 | public static setVersion(version: 1 | 2): void { 91 | switch (version) { 92 | case 1: 93 | this.MOUNT_URL = 'https://kiosk-lite.flatfile.io/?key=:key' 94 | break 95 | case 2: 96 | this.MOUNT_URL = 'https://portal-2.flatfile.io/?key=:key' 97 | break 98 | default: 99 | throw new Error(`${version} is not a valid version`) 100 | } 101 | } 102 | 103 | /** 104 | * Call open() to activate the importer overlay dialog. 105 | */ 106 | open(options = {}): void { 107 | if (this.handshake === null) { 108 | throw new Error('This importer has been destroyed.') 109 | } 110 | options = { 111 | ...options, 112 | bulkInit: true, 113 | hasVirtualRecordHook: !!this.$virtualRecordHook, 114 | hasRecordHook: !!(this.$recordHook || this.$bulkInitRecordHook), 115 | hasInteractionEventCallback: !!this.$interactionEventCallback, 116 | stepHooks: Object.keys(this.$stepHooks), 117 | fieldHooks: this.$fieldHooks.map((v) => v.field), 118 | endUser: this.customer 119 | } 120 | this.$ready.then((child) => { 121 | elementClass(document.body).add('flatfile-active') 122 | let el = document.getElementById(`flatfile-${this.uuid}`) 123 | if (el) { 124 | el.style.display = 'block' 125 | } 126 | child.open(options) 127 | }) 128 | } 129 | 130 | /** 131 | * Use load() when you want a promise returned. This is necessary if you want to use 132 | * async/await for an es6 implementation 133 | * @deprecated 134 | */ 135 | load(): Promise> { 136 | return new FlatfileImporter.Promise((resolve, reject) => { 137 | this.open() 138 | 139 | const cleanup = () => { 140 | this.removeListener('close', loadRejectHandler) 141 | this.removeListener('complete', loadResolveHandler) 142 | } 143 | 144 | function loadResolveHandler(rows: Array) { 145 | resolve(rows) 146 | cleanup() 147 | } 148 | 149 | function loadRejectHandler(err) { 150 | reject(err) 151 | cleanup() 152 | } 153 | 154 | this.on('close', loadRejectHandler) 155 | this.on('complete', loadResolveHandler) 156 | }) 157 | } 158 | 159 | /** 160 | * Use requestDataFromUser() when you want a promise returned. This is necessary if you want to use 161 | * async/await for an es6 implementation 162 | */ 163 | requestDataFromUser(options: LoadOptionsObject = {}): Promise { 164 | this.open({ ...options, inChunks: options.inChunks || null, expectsExpandedResults: true }) 165 | return this.responsePromise() 166 | } 167 | 168 | /** 169 | * This will display a progress indicator inside the importer if you anticipate that handling 170 | * the output of the importer may take some time. 171 | */ 172 | displayLoader(msg?: string): void { 173 | this.$ready.then((child) => { 174 | child.displayLoader(msg) 175 | }) 176 | } 177 | 178 | /** 179 | * This will display a dialog inside of the importer with an error icon and the message you 180 | * pass. The user will be able to acknowledge the error and be returned to the import data 181 | * spreadsheet to ideally fix any issues or attempt submitting again. 182 | */ 183 | displayError(msg?: string): void { 184 | this.$ready.then((child) => { 185 | child.displayError(msg) 186 | }) 187 | } 188 | 189 | /** 190 | * This will display a dialog inside of the importer with an error icon and the message you 191 | * pass. The user will be able to acknowledge the error and be returned to the import data 192 | * spreadsheet to ideally fix any issues or attempt submitting again. 193 | * 194 | * @param corrections - allows user to do server-side validation and provide error / warning 195 | * messages or value overrides 196 | */ 197 | requestCorrectionsFromUser(msg?: string, corrections?: IDataHookResponse[]): Promise { 198 | this.$ready.then((child) => { 199 | child.displayError(msg, corrections) 200 | }) 201 | return this.responsePromise() 202 | } 203 | 204 | /** 205 | * This will display a dialog inside of the importer with a success icon and the message you 206 | * pass. 207 | * 208 | * @return Promise that will be resolved when user closes the dialog. 209 | */ 210 | displaySuccess(msg?: string): Promise { 211 | this.$ready.then((child) => { 212 | child.displaySuccess(msg) 213 | }) 214 | 215 | return new Promise((resolve) => { 216 | const handleSuccess = () => { 217 | resolve() 218 | this.removeListener('close', handleSuccess) 219 | } 220 | 221 | this.on('close', handleSuccess) 222 | }) 223 | } 224 | 225 | /** 226 | * Set the customer information for this import 227 | */ 228 | setCustomer(customer: CustomerObject): void { 229 | this.customer = customer 230 | } 231 | 232 | /** 233 | * Set the language for the Portal 234 | */ 235 | setLanguage(lang: string): void { 236 | this.$ready.then((child) => { 237 | child.setLanguage(lang) 238 | }) 239 | } 240 | 241 | addVirtualField(field: ISettings['fields'][0], options: IVirtualFieldOptions = {}): void { 242 | this.$ready.then((child) => { 243 | child.addVirtualField({ field, options }) 244 | }) 245 | } 246 | 247 | /** 248 | * Set the customer information for this import 249 | */ 250 | registerRecordHook(callback: FlatfileImporter['$recordHook']): void { 251 | this.$recordHook = callback 252 | } 253 | 254 | registerBulkInitRecordHook(callback: FlatfileImporter['$bulkInitRecordHook']): void { 255 | this.$bulkInitRecordHook = callback 256 | } 257 | 258 | registerVirtualRecordHook(callback: FlatfileImporter['$virtualRecordHook']): void { 259 | this.$virtualRecordHook = callback 260 | } 261 | 262 | registerNetworkErrorCallback(callback: FlatfileImporter['$networkErrorCallback']): void { 263 | this.$networkErrorCallback = callback 264 | } 265 | 266 | registerBeforeFetchCallback(callback: FlatfileImporter['$beforeFetchCallback']): void { 267 | this.$beforeFetchCallback = callback 268 | } 269 | 270 | registerInteractionEventCallback(callback: FlatfileImporter['$interactionEventCallback']): void { 271 | this.$interactionEventCallback = callback 272 | } 273 | 274 | registerFieldHook(field: string, cb: FieldHookCallback): void { 275 | this.$fieldHooks.push({ field, cb }) 276 | } 277 | 278 | registerStepHook(step: T, callback: StepHookCallback): void { 279 | this.$stepHooks[step] = callback 280 | } 281 | 282 | /** 283 | * Call close() from the parent window in order to hide the importer. You can do this after 284 | * handling the import callback so your users don't have to click the confirmation button 285 | */ 286 | close() { 287 | this.$ready.then((child) => { 288 | child.close() 289 | }) 290 | } 291 | 292 | /** 293 | * This will remove the importer's HTML element and will destroy the connection. 294 | * You wouldn't be able to open an importer if this method gets called. 295 | */ 296 | destroy() { 297 | document.getElementById(`flatfile-${this.uuid}`)?.remove() 298 | this.handshake = null 299 | } 300 | 301 | private handleClose() { 302 | elementClass(document.body).remove('flatfile-active') 303 | let el = document.getElementById(`flatfile-${this.uuid}`) 304 | if (el) { 305 | el.style.display = 'none' 306 | } 307 | } 308 | 309 | private initialize() { 310 | insertCss(` 311 | .flatfile-component { 312 | position: fixed; 313 | top: 0; 314 | bottom: 0; 315 | right: 0; 316 | left: 0; 317 | display: none; 318 | z-index: 100000; 319 | } 320 | .flatfile-component iframe { 321 | width: 100%; 322 | height: 100%; 323 | position: absolute; 324 | border-width: 0; 325 | } 326 | body.flatfile-active { 327 | overflow: hidden; 328 | overscroll-behavior-x: none; 329 | } 330 | `) 331 | 332 | document.body.insertAdjacentHTML( 333 | 'beforeend', 334 | `
` 335 | ) 336 | const timeout = setTimeout( 337 | () => 338 | console.error( 339 | '[Flatfile] Looks like Portal takes too long to load. Please visit our Help Center (https://support.flatfile.com/hc/en-us/articles/4408071883924-CORS-Referrer-Policy-Recommendations) or contact Flatfile support for any help.' 340 | ), 341 | 5000 342 | ) 343 | this.handshake = Penpal.connectToChild({ 344 | appendTo: document.getElementById(`flatfile-${this.uuid}`) || undefined, 345 | url: FlatfileImporter.MOUNT_URL.replace(':key', this.apiKey), 346 | methods: { 347 | results: (data) => { 348 | this.emit('results', data.results, data.meta) 349 | }, 350 | complete: (data) => { 351 | this.emit('complete', data.rows, data.meta) 352 | }, 353 | close: () => { 354 | this.emit('close') 355 | this.handleClose() 356 | }, 357 | networkErrorCallback: (error) => { 358 | return this.$networkErrorCallback ? this.$networkErrorCallback(error) : undefined 359 | }, 360 | beforeFetchCallback: (req) => { 361 | return this.$beforeFetchCallback ? this.$beforeFetchCallback(req) : undefined 362 | }, 363 | interactionEventCallback: (req) => { 364 | return this.$interactionEventCallback ? this.$interactionEventCallback(req) : undefined 365 | }, 366 | dataHookCallback: (row, index, mode) => { 367 | try { 368 | return this.$recordHook ? this.$recordHook(row, index, mode) : undefined 369 | } catch ({ message, stack }) { 370 | console.error(`Flatfile Record Hook Error on row ${index}:\n ${stack}`, { row, mode }) 371 | 372 | return {} 373 | } 374 | }, 375 | bulkHookCallback: (rows, mode) => { 376 | if (this.$bulkInitRecordHook) { 377 | try { 378 | return this.$bulkInitRecordHook(rows) 379 | } catch ({ stack }) { 380 | console.error(`Flatfile Bulk Init Record Hook Error:\n ${stack}`, { rows }) 381 | return {} 382 | } 383 | } 384 | try { 385 | if (mode === 'virtual') { 386 | return this.$virtualRecordHook 387 | ? Promise.all( 388 | rows.map(([row, index]) => { 389 | try { 390 | return this.$virtualRecordHook!(row, index, mode) 391 | } catch (e) { 392 | e.row = row 393 | e.index = index 394 | throw e 395 | } 396 | }) 397 | ) 398 | : undefined 399 | } 400 | return this.$recordHook 401 | ? Promise.all( 402 | rows.map(([row, index]) => { 403 | try { 404 | return this.$recordHook!(row, index, mode) 405 | } catch (e) { 406 | e.row = row 407 | e.index = index 408 | throw e 409 | } 410 | }) 411 | ) 412 | : undefined 413 | } catch ({ stack, row, index }) { 414 | console.error(`Flatfile Record Hook Error on row ${index}:\n ${stack}`, { row, mode }) 415 | 416 | return {} 417 | } 418 | }, 419 | fieldHookCallback: (values, meta) => { 420 | const fieldHook = this.$fieldHooks.find((v) => v.field === meta.field) 421 | if (!fieldHook) { 422 | return undefined 423 | } 424 | try { 425 | return fieldHook.cb(values, meta) 426 | } catch ({ stack }) { 427 | console.error(`Flatfile Field Hook Error on field "${meta.field}":\n ${stack}`, { 428 | meta, 429 | values 430 | }) 431 | 432 | return [] 433 | } 434 | }, 435 | stepHookCallback: async (step, payload) => { 436 | if (!this.$stepHooks[step]) { 437 | return undefined 438 | } 439 | try { 440 | return await this.$stepHooks[step](payload) 441 | } catch ({ stack }) { 442 | console.error(`Flatfile Step Hook Error on step "${step}":\n ${stack}`, { 443 | payload 444 | }) 445 | } 446 | }, 447 | ready: () => { 448 | this.handshake.promise 449 | .then((child) => { 450 | this.$resolver(child) 451 | if (this.customer) { 452 | child.setUser(this.customer) 453 | } 454 | }) 455 | .catch((err) => { 456 | console.error(err) 457 | }) 458 | return this.options 459 | } 460 | } 461 | }) 462 | 463 | this.handshake.promise.then(() => { 464 | if (timeout) clearTimeout(timeout) 465 | }) 466 | 467 | this.handshake.promise.catch((err) => { 468 | this.$rejecter(err) 469 | }) 470 | } 471 | 472 | private $generateUuid(): string { 473 | return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) 474 | } 475 | 476 | private responsePromise(): Promise { 477 | return new Promise((resolve, reject) => { 478 | const loadResolveHandler = async (rows: Array, meta: object) => { 479 | const results = new Results(rows, meta as Meta, this) 480 | resolve(results) 481 | cleanup() 482 | } 483 | 484 | function loadRejectHandler(err) { 485 | reject(err) 486 | cleanup() 487 | } 488 | 489 | const self = this 490 | 491 | function cleanup() { 492 | self.removeListener('close', loadRejectHandler) 493 | self.removeListener('results', loadResolveHandler) 494 | } 495 | 496 | this.on('close', loadRejectHandler) 497 | this.on('results', loadResolveHandler) 498 | }) 499 | } 500 | } 501 | --------------------------------------------------------------------------------