๐ค Chapter 12: ํ์ ์คํฌ๋ฆฝํธ ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ์ค์ต
๐ฆ ๋น ๋ฐ์ดํฐ ๋ฐฐ์น ํ๋ก๊ทธ๋จ ๋ง๋ค๊ธฐโ
- ์ค์ต์ ๋ํ ๋ด์ฉ์ด๋ฏ๋ก ์์ธํ ๋ด์ฉ์ ์ฑ ์ ์ฐธ๊ณ (P.340 ~ P.365)
๐ ๋ ธ๋์ ์ด์์ค์์ ํ๋ก๊ทธ๋จ ๋ช ๋ น ์ค ์ธ์ ์ฝ๊ธฐโ
export type FileNameAndNumber = [string, number];
export const getFileNameAndNumber = (
defaultFilename: string,
defaultNumberOfFakeData: number,
): FileNameAndNumber => {
const [bin, node, filename, numberOfFakeData] = process.argv;
return [
filename || defaultFilename,
numberOfFakeData ? parseInt(numberOfFakeData, 10) : defaultNumberOfFakeData,
];
};
๐ ํ์ผ ์ฒ๋ฆฌ ๋น๋๊ธฐ ํจ์๋ฅผ ํ๋ก๋ฏธ์ค๋ก ๊ตฌํํ๊ฐโ
๐ fs.access API๋ก ๋๋ ํฐ๋ฆฌ๋ ํ์ผ ํ์ธํ๊ธฐโ
- ์ฝ๋๋ฅผ ์์ฑํ๋ค ๋ณด๋ฉด ํ์ผ์ด๋ ๋๋ ํฐ๋ฆฌ๊ฐ ํ์ฌ ์๋์ง ์๋์ง๋ฅผ ํ์ธํด์ผ ํ ๋๊ฐ ์๊ธด๋ค.
import * as fs from 'fs';
export const fileExist = (
filepath: string,
): Promise<boolean> => new Promise((resolve) => fs.access(filepath, (error) => resolve(!error)));
fileExists-test.ts
import { fileExist } from '../fileApi/fileExists';
const exists = async (filepath) => {
const result = await fileExist(filepath);
console.log(`${filepath} ${result ? 'exists' : 'not exists'}`);
};
exists('./package.json');
exists('./package');
๐ mkdirp ํจํค์ง๋ก ๋๋ ํฐ๋ฆฌ ์์ฑ ํจ์ ๋ง๋ค๊ธฐโ
import mkdirp from 'mkdirp';
import { fileExist } from './fileExists';
export const mkdir = (dirname: string): Promise<string> => new Promise(async (resolve, reject) => {
const alreadyExists = await fileExist(dirname);
if (alreadyExists) {
resolve(dirname);
return;
}
mkdirp(dirname)
.then(() => resolve(dirname))
.catch((error) => reject(error));
});
๐ rimraf ํจํค์ง๋ก ๋๋ ํฐ๋ฆฌ ์ญ์ ํจ์ ๋ง๋ค๊ธฐโ
import rimraf from 'rimraf';
import { fileExist } from './fileExists';
export const rmdir = (dirname: string): Promise<string> => new Promise(async (resolve, reject) => {
const alreadyExists = await fileExist(dirname);
if (!alreadyExists) {
resolve(dirname);
return;
}
rimraf(dirname, (error) => (error ? reject(error) : resolve(dirname)));
});
๐ fs.writeFile API๋ก ํ์ผ ์์ฑํ๊ธฐโ
- ๋ ธ๋์ ์ด์์ค ํ๊ฒฝ์์ ํ์ผ์ ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ฑฐ๋ ์ธ ๋๋ ๋๋ถ๋ถ ํ ์คํธ ๋ฐ์ดํฐ๋ฅผ ๋์์ผ๋ก ํ๋ค.
- ์ด๋ ํ ์คํธ ๋ฐ์ดํฐ๋ ์ ๋์ฝ๋๋ก ์ฒ๋ฆฌํด์ผ ํ๋ค.
import * as fs from 'fs';
export const writeFile = (
filename: string, data: any,
): Promise<any> => new Promise((resolve, reject) => {
fs.writeFile(filename, data, 'utf8', (error: Error) => {
if (error) {
reject(error);
return;
}
resolve(data);
});
});
writeFile-test.ts
import { writeFile } from '../fileApi/writeFile';
import { mkdir } from '../fileApi/mkdir';
const writeTest = async (filename: string, data: any) => {
const result = await writeFile(filename, data);
console.log(`write ${result} to ${filename}`);
};
mkdir('./data')
.then(() => writeTest('./data/hello.txt', 'hello world'))
.then(() => writeTest('./data/test.json', JSON.stringify({ name: 'Jack', age: 32 }, null, 2)))
.catch((e: Error) => console.log(e.message))
๐ fs.readFile API๋ก ํ์ผ ๋ด์ฉ ์ฝ๊ธฐโ
import * as fs from 'fs';
export const readFile = (filename: string): Promise<any> => new Promise<any>((resolve, reject) => {
fs.readFile(filename, 'utf8', (error: Error, data: any) => {
if (error) {
reject(error);
return;
}
resolve(data);
});
});
readFile-test.ts
import { readFile } from '../fileApi/readFile';
const readTest = async (filename: string) => {
const result = await readFile(filename);
console.log(`read ${result} from ${filename} file.`);
};
readTest('./data/hello.txt')
.then(() => readTest('./data/test.json'))
.catch((e: Error) => console.log(e.message));
๐ fs.appendFile API๋ก ํ์ผ์ ๋ด์ฉ ์ถ๊ฐํ๊ธฐโ
import * as fs from 'fs';
export const appendFile = (
filename: string, data: any,
): Promise<any> => new Promise((resolve, reject) => {
fs.appendFile(filename, data, 'utf8', (error: Error) => {
if (error) {
reject(error);
return;
}
resolve(data);
});
});
appendFile-test.ts
import { appendFile } from '../fileApi/appendFile';
import { mkdir } from '../fileApi/mkdir';
const appendTest = async (filename: string, data: any) => {
const result = await appendFile(filename, data);
console.log(`append ${result} to ${filename}`);
};
mkdir('./data')
.then(() => appendTest('./data/hello.txt', 'Hi there!'))
.catch((e: Error) => console.log(e.message));
๐ fs.unlink API๋ก ํ์ผ ์ญ์ ํ๊ธฐโ
import * as fs from 'fs';
import { fileExists } from './fileExists';
export const deleteFile = (
filename: string,
): Promise<string> => new Promise<any>(async (resolve, reject) => {
const alreadyExists = await fileExists(filename);
if (!alreadyExists) {
resolve(filename);
return;
}
fs.unlink(filename, (error) => (error ? reject(error) : resolve(filename)));
});
deleteFile-test.ts
import { deleteFile } from '../fileApi/deleteFile';
import { rmdir } from '../fileApi/rmdir';
const deleteTest = async (filename: string) => {
const result = await deleteFile(filename);
console.log(`delete ${result} file.`);
};
Promise.all([deleteTest('./data/hello.txt'), deleteTest('./data/test.json')])
.then(() => rmdir('./data'))
.then((dirname) => console.log(`delete ${dirname} dir`))
.catch((e: Error) => console.log(e.message));
๐ src/fileApi/index.ts ํ์ผ ๋ง๋ค๊ธฐโ
import { fileExists } from './fileExists';
import { mkdir } from './mkdir';
import { rmdir } from './rmdir';
import { writeFile } from './writeFile';
import { readFile } from './readFile';
import { appendFile } from './appendFile';
import { deleteFile } from './deleteFile';
export {
fileExists, mkdir, rmdir, writeFile, readFile, appendFile, deleteFile,
};
๐ ๊ทธ๋ด๋ฏํ ๊ฐ์ง ๋ฐ์ดํฐ ๋ง๋ค๊ธฐโ
export interface IFake {
name: string;
email: string;
sentence: string;
profession: string;
birthday: Date;
}
makeFakeData.ts
import { Chance } from 'chance';
import { IFake } from './IFake';
const c = new Chance();
export const makeFakeData = (): IFake => ({
name: c.name(),
email: c.email(),
profession: c.profession(),
birthday: c.birthday(),
sentence: c.sentence(),
});
export { IFake };
makeFakeData-test.ts
import { makeFakeData, IFake } from '../fake/makeFakeData';
const fakeData: IFake = makeFakeData();
console.log(fakeData);
๐ Object.keys์ Object.values ํจ์ ์ฌ์ฉํ๊ธฐโ
import { IFake, makeFakeData } from '../fake/makeFakeData';
const data: IFake = makeFakeData();
const keys = Object.keys(data);
console.log('keys: ', keys);
const values = Object.values(data);
console.log('values: ', values);
๐ CSV ํ์ผ ๋ง๋ค๊ธฐโ
- ๊ฐ์ง ๋ฐ์ดํฐ๋ฅผ ์ฌ๋ฌ ๊ฐ ์์ฑ
export function* range(max: number, min: number = 0) {
while (min < max) {
yield min++;
}
}
makeFakeData
๋ฅผ ์ฌ์ฉํดnumberOfItems
๋งํผIFake
๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค. ๊ทธ๋ฆฌ๊ณ ์์ฑ๋ช ๊ณผ ์์ฑ๊ฐ์ ๋ฐฐ์ด์ ๊ฐ๊ฐ ์ถ์ถํดfilename
ํ์ผ์ ๋ง๋ ๋ค.
import * as path from 'path';
import { mkdir } from '../fileApi/mkdir';
import { range } from '../utils/range';
import { IFake } from './IFake';
import { makeFakeData } from './makeFakeData';
import { writeFile } from '../fileApi/writeFile';
import { appendFile } from '../fileApi/appendFile';
export const writeCsvFormatFakeData = async (
filename: string, numberOfItems: number,
) : Promise<string> => {
const dirname = path.dirname(filename);
await mkdir(dirname);
const comma = ',';
const newLine = '\n';
for (const n of range(numberOfItems)) {
const fake: IFake = makeFakeData();
if (n === 0) {
const keys = Object.keys(fake).join(comma);
await writeFile(filename, keys);
}
const values = Object.values(fake).join(comma);
await appendFile(filename, newLine + values);
}
return `write ${numberOfItems} items to ${filename} file`;
};
๐ ๋ฐ์ดํฐ๋ฅผ CSV ํ์ผ์ ์ฐ๊ธฐโ
- CSV ํ์ผ ํฌ๋งท์ผ๋ก
IFake
ํ์ ๊ฐ์ฒด๋ฅผ ์ ์ฅํ๋ ํ์ผ์ ๋ง๋ค๊ณ ๋ค์๊ณผ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํ๋ค.
import { getFileNameAndNumber } from './utils/getFileNameAndNumber';
import { writeCsvFormatFakeData } from './fake/writeCsvFormatFakeData';
const [filename, numberOfFakeData] = getFileNameAndNumber('./data/fake', 1);
const csvFilename = `${filename}-${numberOfFakeData}.csv`;
writeCsvFormatFakeData(csvFilename, numberOfFakeData)
.then((result) => console.log(result))
.catch((e: Error) => console.log(e.message));
๐ zip ํจ์ ๋ง๋ค๊ธฐโ
- CSV ํฌ๋งท ํ์ผ์ ์ฝ๋ ์ฝ๋๋ฅผ ์์ฑ
- ๊ฐ์ฒด์ ์์ฑ๋ช
๋ฐฐ์ด๊ณผ ์์ฑ๊ฐ ๋ฐฐ์ด์ ๊ฒฐํฉํด ๊ฐ์ฒด ๋ฅผ ๋ง๋๋ ํจ์๊ฐ ํ์ํ๋ฐ ์ด๋ฌํ ๊ธฐ๋ฅ์ ํ๋ ํจ์๋ ๋ณดํต
zip
๋ผ๋ ์ด๋ฆ์ผ๋ก ๊ตฌํํ๋ค.
export const zip = (keys: string[], values: any[]) => {
const makeObject = (key: string, value: any) => ({ [key]: value });
const mergeObject = (a: any[]) => a.reduce((sum, val) => ({ ...sum, ...val }), {});
const tmp = keys
.map((key, index) => [key, values[index]])
.filter((a) => a[0] && a[1])
.map((a) => makeObject(a[0], a[1]));
return mergeObject(tmp);
};
zip-test.ts
import { zip } from '../utils';
import { makeFakeData, IFake } from '../fake';
const data = makeFakeData();
const keys = Object.keys(data);
const values = Object.values(data);
const fake: IFake = zip(keys, values) as IFake;
console.log(fake);
๐ CSV ํ์ผ ๋ฐ์ดํฐ ์ฝ๊ธฐโ
- ๋ค์ ์ฝ๋๋ 1,024Byte์ Buffer ํ์
๊ฐ์ฒด๋ฅผ ์์ฑํด ํ์ผ์ 1,024Byte์ฉ ์ฝ์ผ๋ฉด์ ํ ์ค์ฉ ์ฐพ์ ๋ค, ์ฐพ์ ์ค(์ฆ,
\n
์ผ๋ก ๋๋ ์ค)์ ๋ฐ์ดํฐ๋ฅผyield
๋ฌธ์ผ๋ก ๋ฐ์์ํค๋ ์์ด๋ค.
import * as fs from 'fs';
function readLine(fd: any, buffer: Buffer, bufferSize: number, position: number): [string, number] {
let line = '';
let readSize;
const crSize = '\n'.length;
while (true) {
readSize = fs.readSync(fd, buffer, 0, bufferSize, position);
if (readSize > 0) {
const temp = buffer.toString('utf8', 0, readSize);
const index = temp.indexOf('\n');
if (index > -1) {
line += temp.substr(0, index);
position += index + crSize;
break;
} else {
line += temp;
position += temp.length;
}
} else {
position = -1; // end of file
break;
}
}
return [line.trim(), position];
}
export function* readFileGenerator(filename: string): any {
let fd: any;
try {
fd = fs.openSync(filename, 'rs');
const stats = fs.fstatSync(fd);
const bufferSize = Math.min(stats.size, 1024);
const buffer = Buffer.alloc(bufferSize + 4);
let filepos = 0;
let line;
while (filepos > -1) {
[line, filepos] = readLine(fd, buffer, bufferSize, filepos);
if (filepos > -1) {
yield line;
}
}
yield buffer.toString(); // ๋ง์ง๋ง ์ค
} catch (error) {
console.log('readLine: ', error.message);
} finally {
fd && fs.closeSync(fd);
}
}
readFileGenerator-test.ts
readFileGenerator
๋ ๋จ์ํ ํ ์ค ํ ์ค ์ฝ๋๋ค.
import { readFileGenerator } from '../fileApi';
for (const value of readFileGenerator('data/fake-10000.csv')) {
console.log('<line>', value, '</line>');
break;
}
// <line> name,email,profession,birthday,sentence </line>
- CSV ํ์ผ์ ํด์ํ๋ฉด์ ์ฝ๋ ์ฝ๋์ด๋ค.
import { readFileGenerator } from '../fileApi';
import { zip } from '../utils';
export function* csvFileReaderGenerator(filename: string, delim: string = ',') {
let header = [];
for (const line of readFileGenerator(filename)) {
if (!header.length) {
header = line.split(delim);
} else {
yield zip(header, line.split(delim));
}
}
}
readCsv.ts
import { getFileNameAndNumber } from './utils';
import { csvFileReaderGenerator } from './csv/csvFileReaderGenerator';
const [filename] = getFileNameAndNumber('./data/fake-10000.csv', 1);
let line = 1;
for (const object of csvFileReaderGenerator(filename)) {
console.log(`[${line++}] ${JSON.stringify(object)}`);
}
console.log('\n read complete.');
๐ฆ ๋ชฝ๊ณ DB์ ๋ฐ์ดํฐ ์ ์ฅํ๊ธฐโ
๐ ๋ชฝ๊ณ DB์ ์ ์ํ๊ธฐโ
- mongodb ํจํค์ง๊ฐ ์ ๊ณตํ๋ MongoClient ๊ฐ์ฒด์
connect
ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ๋ชฝ๊ณ DB์ ์ ์
import { MongoClient } from 'mongodb';
export const connect = (mongoUrl: string = 'mongodb://localhost:27017') => MongoClient.connect(mongoUrl, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
- ์ ์ ์ฐ๊ฒฐ
import { connect } from '../mongodb/connect';
const connectTest = async () => {
let connection;
try {
connection = await connect();
console.log('connection OK.', connection);
} catch (error) {
console.log(error.message);
} finally {
connection.close();
}
};
connectTest();
๐ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐโ
const db = await connection.db('ch12-2');
๐ ์ปฌ๋ ์ ์ ์์ฑโ
const personsCollection = db.collection('persons');
const addressesCollection = db.collection('addresses');