diff --git a/backend/package.json b/backend/package.json index b45ab4d9..c1ea6b6a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -20,7 +20,8 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json" + "test:e2e": "jest --config ./test/jest-e2e.json", + "test:bench": "jest --rootDir . test/bench/performance.spec.ts" }, "dependencies": { "@nestjs/common": "^10.0.0", diff --git a/backend/src/instances/blog/blog.service.ts b/backend/src/instances/blog/blog.service.ts index 147ea8c3..2d385075 100644 --- a/backend/src/instances/blog/blog.service.ts +++ b/backend/src/instances/blog/blog.service.ts @@ -1,10 +1,10 @@ import { createReadStream } from "node:fs"; -import { opendir } from "node:fs/promises"; +import { opendir, readFile, writeFile } from "node:fs/promises"; import { cpus } from "node:os"; import { join } from "node:path"; import { createInterface } from "node:readline"; import { Injectable, Logger, OnModuleInit } from "@nestjs/common"; -import { concurrent, filter, map, pipe, toArray } from "fx_utils"; +import { concurrent, filter, map, pipe, take, toArray } from "fx_utils"; import * as matter from "gray-matter"; import { FileProcessor } from "./FileProcessor"; import { @@ -16,24 +16,20 @@ import { @Injectable() export class BlogService implements OnModuleInit { - private readonly logger = new Logger(BlogService.name); private readonly POSTS_ROOT_PATH = join(process.cwd(), "../posts"); + private readonly INDEX_FILE_PATH = join(this.POSTS_ROOT_PATH, "posts.jsonl"); + public readonly logger = new Logger(BlogService.name); - private allPosts: PostFrontMatter[] = []; - private postsByCategory: Record = { - web: [], - algorithm: [], - code: [], - cs: [], - }; + private totalPostCount = 0; private fileProcessor = new FileProcessor(); async onModuleInit() { this.logger.log("BlogService 초기화를 진행합니다..."); try { - await this.processAndCacheAllPosts(); + await this.ensureIndex(); + this.totalPostCount = await this.countPosts(); this.logger.log( - `블로그 서비스가 성공적으로 초기화되었습니다. 총 게시물 수: ${this.allPosts.length}`, + `블로그 서비스가 성공적으로 초기화되었습니다. 총 게시물 수: ${this.totalPostCount}`, ); } catch (error: unknown) { this.logger.error( @@ -44,30 +40,51 @@ export class BlogService implements OnModuleInit { } public getTotalPostCount(): number { - return this.allPosts.length; + return this.totalPostCount; } - public getLatestPosts(count: number): PostFrontMatter[] { - return this.allPosts.slice(0, count); + public async getLatestPosts(count: number): Promise { + return pipe(this.readIndexLines(), (iter) => take(count, iter), toArray); } - public getPostsInRange(start: number, end: number): PostFrontMatter[] { - return this.allPosts.slice(start, end); + public async getPostsInRange( + start: number, + end: number, + ): Promise { + return pipe( + this.readIndexLines(), + (iter) => { + let index = 0; + return filter(() => { + const keep = index >= start && index < end; + index++; + return keep; + }, iter); + }, + (iter) => take(end - start, iter), + toArray, + ); } - public getAllPosts(): PostFrontMatter[] { - return this.allPosts; + public async getAllPosts(): Promise { + return pipe(this.readIndexLines(), toArray); } - public getPostsByCategory(category: PostCategory): PostFrontMatter[] { - return this.postsByCategory[category] || []; + public async getPostsByCategory( + category: PostCategory, + ): Promise { + return pipe( + this.readIndexLines(), + filter((post) => post.frontmatter.category === category), + toArray, + ); } - public getPostBySlug( + public async getPostBySlug( category: PostCategory, slug: string, - ): PostFrontMatter | undefined { - const posts = this.getPostsByCategory(category); + ): Promise { + const posts = await this.getPostsByCategory(category); return posts.find((p) => p.frontmatter.slug === slug); } @@ -79,30 +96,50 @@ export class BlogService implements OnModuleInit { return this.fileProcessor.processFile(filePath); } - async processAndCacheAllPosts(category: PostCategory | "." = ".") { - const iterator = await this.createFrontMatterIterator(category); - const allFrontMatters = await toArray(iterator); + private async *readIndexLines(): AsyncGenerator { + const stream = createReadStream(this.INDEX_FILE_PATH); + const rl = createInterface({ input: stream, crlfDelay: Infinity }); + + for await (const line of rl) { + if (line.trim()) { + yield JSON.parse(line); + } + } + } + private async countPosts(): Promise { + let count = 0; + const stream = createReadStream(this.INDEX_FILE_PATH); + const rl = createInterface({ input: stream, crlfDelay: Infinity }); + for await (const _ of rl) { + count++; + } + return count; + } + + private async ensureIndex() { + try { + await readFile(this.INDEX_FILE_PATH); + this.logger.log("인덱스 파일(NDJSON)을 확인했습니다."); + } catch { + this.logger.warn( + "인덱스 파일을 찾을 수 없습니다. 파일 시스템에서 생성합니다...", + ); + await this.buildIndexFromFiles(); + } + } + + async buildIndexFromFiles(category: PostCategory | "." = ".") { + const allFrontMatters = await this.createFrontMatterIterator(category); const sortedPosts = allFrontMatters.sort((a, b) => a.frontmatter.date > b.frontmatter.date ? -1 : 1, ); - const categorizedPosts = sortedPosts.reduce( - (acc, post) => { - const { category } = post.frontmatter; - if (acc[category]) { - acc[category].push(post); - } - return acc; - }, - { web: [], algorithm: [], code: [], cs: [] } as Record< - PostCategory, - PostFrontMatter[] - >, + const content = sortedPosts.map((p) => JSON.stringify(p)).join("\n"); + await writeFile(this.INDEX_FILE_PATH, content); + this.logger.log( + `인덱스 파일(NDJSON)을 생성했습니다: ${this.INDEX_FILE_PATH}`, ); - - this.allPosts = sortedPosts; - this.postsByCategory = categorizedPosts; } private async createFrontMatterIterator(category: PostCategory | ".") { @@ -131,6 +168,7 @@ export class BlogService implements OnModuleInit { }), concurrent(maxConcurrency), filter((data): data is PostFrontMatter => data !== null), + toArray, ); } catch (error) { throw new Error( diff --git a/backend/src/posts/posts.service.ts b/backend/src/posts/posts.service.ts index b581a4c3..b0c7457d 100644 --- a/backend/src/posts/posts.service.ts +++ b/backend/src/posts/posts.service.ts @@ -6,34 +6,32 @@ import type { PostCategory } from "../instances/blog/Schema"; export class PostsService { constructor(private readonly blogService: BlogService) {} - findAll() { + async findAll() { return this.blogService.getAllPosts(); } - findLatest(count: number = 10) { + async findLatest(count: number = 10) { const totalCount = this.blogService.getTotalPostCount(); - const latestFrontmatters = this.blogService - .getLatestPosts(count) - .map((post) => post.frontmatter); + const posts = await this.blogService.getLatestPosts(count); + const latestFrontmatters = posts.map((post) => post.frontmatter); return { totalCount, frontmatters: latestFrontmatters, }; } - findLatestInRange(start: number, end: number) { + async findLatestInRange(start: number, end: number) { const totalCount = this.blogService.getTotalPostCount(); - const latestFrontmatters = this.blogService - .getPostsInRange(start, end) - .map((post) => post.frontmatter); + const posts = await this.blogService.getPostsInRange(start, end); + const latestFrontmatters = posts.map((post) => post.frontmatter); return { totalCount, frontmatters: latestFrontmatters, }; } - findByCategory(category: PostCategory) { - const posts = this.blogService.getPostsByCategory(category); + async findByCategory(category: PostCategory) { + const posts = await this.blogService.getPostsByCategory(category); if (!posts || posts.length === 0) { throw new Error(`${category} 카테고리에 해당하는 게시물이 없습니다.`); @@ -42,7 +40,7 @@ export class PostsService { } async findOne(cateogry: PostCategory, slug: string) { - const { frontmatter } = this.blogService.getPostBySlug(cateogry, slug); + const frontmatter = await this.blogService.getPostBySlug(cateogry, slug); const { content } = await this.blogService.getPostContent(cateogry, slug); if (!frontmatter || !content) { throw new Error( diff --git a/backend/src/search/search.controller.ts b/backend/src/search/search.controller.ts index 0b0d0720..8e555ed8 100644 --- a/backend/src/search/search.controller.ts +++ b/backend/src/search/search.controller.ts @@ -6,7 +6,8 @@ export class SearchController { constructor(private readonly searchService: SearchService) {} @Get() - search(@Query("query") query: string) { - return { results: this.searchService.search(query) }; + async search(@Query("query") query: string) { + const searchResults = await this.searchService.search(query); + return { results: searchResults }; } } diff --git a/backend/src/search/search.service.ts b/backend/src/search/search.service.ts index 957e1430..eb80e88b 100644 --- a/backend/src/search/search.service.ts +++ b/backend/src/search/search.service.ts @@ -6,12 +6,12 @@ import { findMatches } from "./utils/findMatches"; export class SearchService { constructor(private readonly blogService: BlogService) {} - search(query: string) { + async search(query: string) { if (!query) { return []; } - const posts = this.blogService.getAllPosts(); + const posts = await this.blogService.getAllPosts(); const results = []; for (const post of posts) { const { title, summary } = post.frontmatter; diff --git a/backend/test/bench/performance.spec.ts b/backend/test/bench/performance.spec.ts new file mode 100644 index 00000000..1008ea5b --- /dev/null +++ b/backend/test/bench/performance.spec.ts @@ -0,0 +1,123 @@ +import { constants } from "node:fs"; +import { access, readFile, unlink, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { Test, TestingModule } from "@nestjs/testing"; +import { BlogService } from "../../src/instances/blog/blog.service"; + +describe("BlogService Performance Benchmark", () => { + let service: BlogService; + const POSTS_DIR = join(process.cwd(), "../posts"); + const MOCK_DB_PATH = join(POSTS_DIR, "posts.jsonl"); + const ORIGINAL_DB_PATH = join(POSTS_DIR, "posts.jsonl.bak"); + + const TARGET_COUNT = 10000; + const DUMMY_DATA = Array.from({ length: TARGET_COUNT }, (_, i) => ({ + frontmatter: { + title: `Post ${i}`, + date: new Date().toISOString(), + category: "code", + slug: `post-${i}`, + }, + filePath: `/path/to/post-${i}.mdx`, + })) + .map((p) => JSON.stringify(p)) + .join("\n"); + + beforeAll(async () => { + try { + await access(MOCK_DB_PATH, constants.F_OK); + const existing = await readFile(MOCK_DB_PATH, "utf-8"); + await writeFile(ORIGINAL_DB_PATH, existing); + } catch (_e) {} + + await writeFile(MOCK_DB_PATH, DUMMY_DATA); + + const module: TestingModule = await Test.createTestingModule({ + providers: [BlogService], + }).compile(); + + service = module.get(BlogService); + // Suppress logs + jest.spyOn(service.logger, "log").mockImplementation(() => {}); + jest.spyOn(service.logger, "warn").mockImplementation(() => {}); + await service.onModuleInit(); + }); + + afterAll(async () => { + try { + await unlink(MOCK_DB_PATH); + try { + await access(ORIGINAL_DB_PATH, constants.F_OK); + const backup = await readFile(ORIGINAL_DB_PATH, "utf-8"); + await writeFile(MOCK_DB_PATH, backup); + await unlink(ORIGINAL_DB_PATH); + } catch {} + } catch (_e) {} + }); + + it("should compare Lazy vs Eager evaluation across multiple fetch sizes", async () => { + const FETCH_COUNTS = [10, 100, 1000, 5000, 10000]; + + // Helper to measure memory + const getMemoryUsage = () => { + if (global.gc) global.gc(); + return process.memoryUsage().heapUsed / 1024 / 1024; // MB + }; + + console.log("\n========================================================================================"); + console.log(`Benchmark Results (Total Posts: ${TARGET_COUNT})`); + console.log("========================================================================================"); + console.log("| Fetch Count | Lazy Time (ms) | Eager Time (ms) | Speedup (x) | Lazy Mem (MB) | Eager Mem (MB) | Mem Reduction (x) |"); + console.log("|------------:|---------------:|----------------:|------------:|--------------:|---------------:|------------------:|"); + + for (const count of FETCH_COUNTS) { + // Force GC before each run to get clean baseline + if (global.gc) global.gc(); + + // 1. Measure Lazy Evaluation + const startMemLazy = getMemoryUsage(); + const startLazy = process.hrtime.bigint(); + const lazyResult = await service.getLatestPosts(count); + const endLazy = process.hrtime.bigint(); + const endMemLazy = getMemoryUsage(); + + const lazyTime = Number(endLazy - startLazy) / 1e6; + const lazyMemory = Math.max(0, endMemLazy - startMemLazy); + + // Force GC between tests + if (global.gc) global.gc(); + + // 2. Measure Eager Evaluation + const startMemEager = getMemoryUsage(); + const startEager = process.hrtime.bigint(); + // Simulate old way: read all, parse all, then slice + const fileContent = await readFile(MOCK_DB_PATH, "utf-8"); + const allPosts = fileContent + .split("\n") + .filter((line) => line.trim()) + .map((line) => JSON.parse(line)); + const eagerResult = allPosts.slice(0, count); + const endEager = process.hrtime.bigint(); + const endMemEager = getMemoryUsage(); + + const eagerTime = Number(endEager - startEager) / 1e6; + const eagerMemory = Math.max(0, endMemEager - startMemEager); + + const speedup = (eagerTime / lazyTime).toFixed(2); + const memReduction = (eagerMemory / (lazyMemory || 0.0001)).toFixed(2); + + console.log( + `| ${count.toString().padEnd(11)} | ${lazyTime.toFixed(4).padStart(14)} | ${eagerTime.toFixed(4).padStart(15)} | ${speedup.padStart(11)} | ${lazyMemory.toFixed(4).padStart(13)} | ${eagerMemory.toFixed(4).padStart(14)} | ${memReduction.padStart(17)} |` + ); + + expect(lazyResult.length).toBe(count); + expect(eagerResult.length).toBe(count); + + // Lazy should generally be faster for smaller subsets + if (count < 5000) { + expect(lazyTime).toBeLessThan(eagerTime); + } + } + console.log("========================================================================================\n"); + }); +}); diff --git a/frontend/package.json b/frontend/package.json index 6db3f5c8..1be724ca 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -45,11 +45,11 @@ "lodash.throttle": "^4.1.1", "lucide-react": "^0.555.0", "motion": "^12.23.24", - "next": "^16.0.5", + "next": "16.0.7", "next-mdx-remote": "^5.0.0", "ora": "^8.0.1", - "react": "^19.0.0", - "react-dom": "^19.0.0", + "react": "^19.2.1", + "react-dom": "^19.2.1", "react-focus-lock": "^2.9.6", "react-intersection-observer": "^9.14.1", "react-remove-scroll": "^2.5.7", @@ -59,7 +59,7 @@ }, "devDependencies": { "@next/bundle-analyzer": "^16.0.5", - "@playwright/test": "^1.54.2", + "@playwright/test": "^1.57.0", "@testing-library/jest-dom": "^6.1.3", "@testing-library/react": "^14.3.1", "@types/js-cookie": "^3.0.6", diff --git a/frontend/src/components/Main/BlogPost/ui/BlogPostListView/BlogPostListViewTrigger.tsx b/frontend/src/components/Main/BlogPost/ui/BlogPostListView/BlogPostListViewTrigger.tsx index 2295307c..fa594633 100644 --- a/frontend/src/components/Main/BlogPost/ui/BlogPostListView/BlogPostListViewTrigger.tsx +++ b/frontend/src/components/Main/BlogPost/ui/BlogPostListView/BlogPostListViewTrigger.tsx @@ -6,7 +6,9 @@ export const BlogPostListViewTrigger = memo(function BlogPostListViewTrigger() { const observerTarget = useRef(null); useEffect(() => { - if (!observerTarget.current || !hasMore) return; + if (!observerTarget.current || !hasMore) { + return; + } const observer = new IntersectionObserver( (entries) => { diff --git a/frontend/test/e2e/Search.test.tsx b/frontend/test/e2e/Search.test.tsx index 618734ca..5519ae2b 100644 --- a/frontend/test/e2e/Search.test.tsx +++ b/frontend/test/e2e/Search.test.tsx @@ -319,13 +319,13 @@ test.describe("검색 결과 키보드 네비게이션 테스트", () => { await page.waitForURL(/\/post\/[^/]+\/[^/]+/, DEFAULT_TEST_OPTION); - await expect(page.getByTestId("loading-screen")).toBeVisible( - DEFAULT_TEST_OPTION, - ); + // await expect(page.getByTestId("loading-screen")).toBeVisible( + // DEFAULT_TEST_OPTION, + // ); - await expect(page.getByTestId("loading-screen")).toBeHidden( - DEFAULT_TEST_OPTION, - ); + // await expect(page.getByTestId("loading-screen")).toBeHidden( + // DEFAULT_TEST_OPTION, + // ); await expect(page.getByTestId("post-article__main-title")).toBeVisible( DEFAULT_TEST_OPTION, diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 330d70c5..e35fb369 100755 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -1,56 +1,84 @@ /// { - "compilerOptions": { - "target": "ES2022", - "lib": ["dom", "dom.iterable", "esnext"], - "baseUrl": ".", - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "react-jsx", - "incremental": true, - "allowImportingTsExtensions": true, - "plugins": [ - { - "name": "next" - } - ], - "paths": { - "@/*": ["./src/*"], - "@/icon": ["./src/components/icon"], - "@/utils/*": ["./src/utils/*"], - "@/lib/*": ["./src/lib/*"], - "@/components/*": ["./src/components/*"], - "@/hooks/*": ["./src/hooks/*"], - "@/posts/*": ["./src/app/posts/*"], - "@/generated/*": ["./src/generated/*"], - "@/public/*": ["./public/*"], - "@/shared/*": ["./src/shared/*"], - "@/db/*": ["./db/*"] - }, - "sourceMap": true - }, - "include": [ - "./type.d.ts", - "recover/next-env.d.ts", - "**/*.js", - "**/*.jsx", - "**/*.ts", - "**/*.tsx", - ".next/types/**/*.ts", - "src/**/*", - "src/app/mdx/page.jsx", - "db", - "test", - "../backend/tmp", - ".next/dev/types/**/*.ts" - ], - "exclude": ["node_modules"] + "compilerOptions": { + "target": "ES2022", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "baseUrl": ".", + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "allowImportingTsExtensions": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": [ + "./src/*" + ], + "@/icon": [ + "./src/components/icon" + ], + "@/utils/*": [ + "./src/utils/*" + ], + "@/lib/*": [ + "./src/lib/*" + ], + "@/components/*": [ + "./src/components/*" + ], + "@/hooks/*": [ + "./src/hooks/*" + ], + "@/posts/*": [ + "./src/app/posts/*" + ], + "@/generated/*": [ + "./src/generated/*" + ], + "@/public/*": [ + "./public/*" + ], + "@/shared/*": [ + "./src/shared/*" + ], + "@/db/*": [ + "./db/*" + ] + }, + "sourceMap": true + }, + "include": [ + "./type.d.ts", + "recover/next-env.d.ts", + "**/*.js", + "**/*.jsx", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + "src/**/*", + "src/app/mdx/page.jsx", + "db", + "test", + "../backend/tmp", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] } diff --git a/fx_utils/src/index.ts b/fx_utils/src/index.ts index a2554b98..c7229845 100644 --- a/fx_utils/src/index.ts +++ b/fx_utils/src/index.ts @@ -1,22 +1,21 @@ -import { fx } from "./fx"; -import { range } from "./range"; -import { reduce } from "./reduce"; -import { take } from "./take"; -import { find } from "./find"; -import { delay } from "./delay"; -import { findUpDir } from "./findUp"; - import { - pipe, - flat, - sort, - filter, chunk, - map, - flatMap, concurrent, + filter, + flat, + flatMap, + map, + pipe, + sort, toArray, } from "@fxts/core"; +import { delay } from "./delay"; +import { find } from "./find"; +import { findUpDir } from "./findUp"; +import { fx } from "./fx"; +import { range } from "./range"; +import { reduce } from "./reduce"; +import { take } from "./take"; export { fx, diff --git a/fx_utils/src/take.ts b/fx_utils/src/take.ts index 69eb7b1f..20d5b60f 100644 --- a/fx_utils/src/take.ts +++ b/fx_utils/src/take.ts @@ -2,25 +2,23 @@ import { isIterable } from "./utils/isIterable"; function* takeSync(limit: number, iterable: Iterable) { const iterator = iterable[Symbol.iterator](); - while (true) { + while (limit-- > 0) { const { done, value } = iterator.next(); if (done) { break; } yield value; - if (limit-- === 0) break; } } async function* takeAsync(limit: number, iterable: AsyncIterable) { const iterator = iterable[Symbol.asyncIterator](); - while (true) { + while (limit-- > 0) { const { done, value } = await iterator.next(); if (done) { break; } yield value; - if (limit-- === 0) break; } } diff --git a/fx_utils/src/utils/index.ts b/fx_utils/src/utils/index.ts index 6630952a..3b8b458c 100644 --- a/fx_utils/src/utils/index.ts +++ b/fx_utils/src/utils/index.ts @@ -1,7 +1,7 @@ -import { isIterable } from "./isIterable"; -import { toAsync } from "./toAsync"; import { fromAsync } from "./fromAsync"; import { isAsyncIterable } from "./isAsyncIterable"; +import { isIterable } from "./isIterable"; import { isPromise } from "./isPromise"; +import { toAsync } from "./toAsync"; export { isIterable, toAsync, fromAsync, isAsyncIterable, isPromise }; diff --git a/package.json b/package.json index 127a8750..d7f66613 100644 --- a/package.json +++ b/package.json @@ -61,5 +61,5 @@ "@nestjs/core" ] }, - "packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a" + "packageManager": "pnpm@10.25.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d4f3ce08..b0643f08 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -136,13 +136,13 @@ importers: version: 3.1.1(webpack@5.97.1) '@mdx-js/react': specifier: ^3.1.0 - version: 3.1.1(@types/react@19.0.0)(react@19.2.0) + version: 3.1.1(@types/react@19.0.0)(react@19.2.1) '@next/mdx': specifier: ^15.4.1 - version: 15.5.6(@mdx-js/loader@3.1.1(webpack@5.97.1))(@mdx-js/react@3.1.1(@types/react@19.0.0)(react@19.2.0)) + version: 15.5.6(@mdx-js/loader@3.1.1(webpack@5.97.1))(@mdx-js/react@3.1.1(@types/react@19.0.0)(react@19.2.1)) '@radix-ui/react-slot': specifier: ^1.1.2 - version: 1.2.4(@types/react@19.0.0)(react@19.2.0) + version: 1.2.4(@types/react@19.0.0)(react@19.2.1) cross-env: specifier: ^7.0.3 version: 7.0.3 @@ -154,7 +154,7 @@ importers: version: 4.0.3 jotai: specifier: ^2.13.1 - version: 2.15.2(@babel/core@7.28.5)(@babel/template@7.27.2)(@types/react@19.0.0)(react@19.2.0) + version: 2.15.2(@babel/core@7.28.5)(@babel/template@7.27.2)(@types/react@19.0.0)(react@19.2.1) js-cookie: specifier: ^3.0.5 version: 3.0.5 @@ -163,40 +163,40 @@ importers: version: 4.1.1 lucide-react: specifier: ^0.555.0 - version: 0.555.0(react@19.2.0) + version: 0.555.0(react@19.2.1) motion: specifier: ^12.23.24 - version: 12.23.25(@emotion/is-prop-valid@1.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 12.23.25(@emotion/is-prop-valid@1.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) next: - specifier: ^16.0.5 - version: 16.0.6(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: 16.0.7 + version: 16.0.7(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) next-mdx-remote: specifier: ^5.0.0 - version: 5.0.0(@types/react@19.0.0)(react@19.2.0) + version: 5.0.0(@types/react@19.0.0)(react@19.2.1) ora: specifier: ^8.0.1 version: 8.2.0 react: - specifier: ^19.0.0 - version: 19.2.0 + specifier: ^19.2.1 + version: 19.2.1 react-dom: - specifier: ^19.0.0 - version: 19.2.0(react@19.2.0) + specifier: ^19.2.1 + version: 19.2.1(react@19.2.1) react-focus-lock: specifier: ^2.9.6 - version: 2.13.7(@types/react@19.0.0)(react@19.2.0) + version: 2.13.7(@types/react@19.0.0)(react@19.2.1) react-intersection-observer: specifier: ^9.14.1 - version: 9.16.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 9.16.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) react-remove-scroll: specifier: ^2.5.7 - version: 2.7.2(@types/react@19.0.0)(react@19.2.0) + version: 2.7.2(@types/react@19.0.0)(react@19.2.1) sharp: specifier: ^0.32.6 version: 0.32.6 styled-components: specifier: ^6.1.19 - version: 6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 6.1.19(react-dom@19.2.1(react@19.2.1))(react@19.2.1) zod: specifier: ^3.23.8 version: 3.25.76 @@ -205,14 +205,14 @@ importers: specifier: ^16.0.5 version: 16.0.6 '@playwright/test': - specifier: ^1.54.2 + specifier: ^1.57.0 version: 1.57.0 '@testing-library/jest-dom': specifier: ^6.1.3 version: 6.9.1 '@testing-library/react': specifier: ^14.3.1 - version: 14.3.1(@types/react@19.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 14.3.1(@types/react@19.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@types/js-cookie': specifier: ^3.0.6 version: 3.0.6 @@ -1143,8 +1143,8 @@ packages: '@next/bundle-analyzer@16.0.6': resolution: {integrity: sha512-zoa/w3TYl6kBcJesvj7JM96fHu1WiHRLTRMgEdLEn2kh1cGJMkIIlOGyekjnccaozd+oIyQ925lKrkCMfgt1xg==} - '@next/env@16.0.6': - resolution: {integrity: sha512-PFTK/G/vM3UJwK5XDYMFOqt8QW42mmhSgdKDapOlCqBUAOfJN2dyOnASR/xUR/JRrro0pLohh/zOJ77xUQWQAg==} + '@next/env@16.0.7': + resolution: {integrity: sha512-gpaNgUh5nftFKRkRQGnVi5dpcYSKGcZZkQffZ172OrG/XkrnS7UBTQ648YY+8ME92cC4IojpI2LqTC8sTDhAaw==} '@next/mdx@15.5.6': resolution: {integrity: sha512-lyzXcnZWPjYxbkz/5tv1bRlCOjKYX1lFg3LIuoIf9ERTOUBDzkCvUnWjtRsmFRxKv1/6uwpLVQvrJDd54gVDBw==} @@ -1157,50 +1157,50 @@ packages: '@mdx-js/react': optional: true - '@next/swc-darwin-arm64@16.0.6': - resolution: {integrity: sha512-AGzKiPlDiui+9JcPRHLI4V9WFTTcKukhJTfK9qu3e0tz+Y/88B7vo5yZoO7UaikplJEHORzG3QaBFQfkjhnL0Q==} + '@next/swc-darwin-arm64@16.0.7': + resolution: {integrity: sha512-LlDtCYOEj/rfSnEn/Idi+j1QKHxY9BJFmxx7108A6D8K0SB+bNgfYQATPk/4LqOl4C0Wo3LACg2ie6s7xqMpJg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.0.6': - resolution: {integrity: sha512-LlLLNrK9WCIUkq2GciWDcquXYIf7vLxX8XE49gz7EncssZGL1vlHwgmURiJsUZAvk0HM1a8qb1ABDezsjAE/jw==} + '@next/swc-darwin-x64@16.0.7': + resolution: {integrity: sha512-rtZ7BhnVvO1ICf3QzfW9H3aPz7GhBrnSIMZyr4Qy6boXF0b5E3QLs+cvJmg3PsTCG2M1PBoC+DANUi4wCOKXpA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.0.6': - resolution: {integrity: sha512-r04NzmLSGGfG8EPXKVK72N5zDNnq9pa9el78LhdtqIC3zqKh74QfKHnk24DoK4PEs6eY7sIK/CnNpt30oc59kg==} + '@next/swc-linux-arm64-gnu@16.0.7': + resolution: {integrity: sha512-mloD5WcPIeIeeZqAIP5c2kdaTa6StwP4/2EGy1mUw8HiexSHGK/jcM7lFuS3u3i2zn+xH9+wXJs6njO7VrAqww==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@16.0.6': - resolution: {integrity: sha512-hfB/QV0hA7lbD1OJxp52wVDlpffUMfyxUB5ysZbb/pBC5iuhyLcEKSVQo56PFUUmUQzbMsAtUu6k2Gh9bBtWXA==} + '@next/swc-linux-arm64-musl@16.0.7': + resolution: {integrity: sha512-+ksWNrZrthisXuo9gd1XnjHRowCbMtl/YgMpbRvFeDEqEBd523YHPWpBuDjomod88U8Xliw5DHhekBC3EOOd9g==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@16.0.6': - resolution: {integrity: sha512-PZJushBgfvKhJBy01yXMdgL+l5XKr7uSn5jhOQXQXiH3iPT2M9iG64yHpPNGIKitKrHJInwmhPVGogZBAJOCPw==} + '@next/swc-linux-x64-gnu@16.0.7': + resolution: {integrity: sha512-4WtJU5cRDxpEE44Ana2Xro1284hnyVpBb62lIpU5k85D8xXxatT+rXxBgPkc7C1XwkZMWpK5rXLXTh9PFipWsA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@16.0.6': - resolution: {integrity: sha512-LqY76IojrH9yS5fyATjLzlOIOgwyzBuNRqXwVxcGfZ58DWNQSyfnLGlfF6shAEqjwlDNLh4Z+P0rnOI87Y9jEw==} + '@next/swc-linux-x64-musl@16.0.7': + resolution: {integrity: sha512-HYlhqIP6kBPXalW2dbMTSuB4+8fe+j9juyxwfMwCe9kQPPeiyFn7NMjNfoFOfJ2eXkeQsoUGXg+O2SE3m4Qg2w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@16.0.6': - resolution: {integrity: sha512-eIfSNNqAkj0tqKRf0u7BVjqylJCuabSrxnpSENY3YKApqwDMeAqYPmnOwmVe6DDl3Lvkbe7cJAyP6i9hQ5PmmQ==} + '@next/swc-win32-arm64-msvc@16.0.7': + resolution: {integrity: sha512-EviG+43iOoBRZg9deGauXExjRphhuYmIOJ12b9sAPy0eQ6iwcPxfED2asb/s2/yiLYOdm37kPaiZu8uXSYPs0Q==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.0.6': - resolution: {integrity: sha512-QGs18P4OKdK9y2F3Th42+KGnwsc2iaThOe6jxQgP62kslUU4W+g6AzI6bdIn/pslhSfxjAMU5SjakfT5Fyo/xA==} + '@next/swc-win32-x64-msvc@16.0.7': + resolution: {integrity: sha512-gniPjy55zp5Eg0896qSrf3yB1dw4F/3s8VK1ephdsZZ129j2n6e1WqCbE2YgcKhW9hPB9TVZENugquWJD5x0ug==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1932,8 +1932,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.9.4: - resolution: {integrity: sha512-ZCQ9GEWl73BVm8bu5Fts8nt7MHdbt5vY9bP6WGnUh+r3l8M7CgfyTlwsgCbMC66BNxPr6Xoce3j66Ms5YUQTNA==} + baseline-browser-mapping@2.9.5: + resolution: {integrity: sha512-D5vIoztZOq1XM54LUdttJVc96ggEsIfju2JBvht06pSzpckp3C7HReun67Bghzrtdsq9XdMGbSSB3v3GhMNmAA==} hasBin: true bidi-js@1.0.3: @@ -3681,10 +3681,9 @@ packages: peerDependencies: react: '>=16' - next@16.0.6: - resolution: {integrity: sha512-2zOZ/4FdaAp5hfCU/RnzARlZzBsjaTZ/XjNQmuyYLluAPM7kcrbIkdeO2SL0Ysd1vnrSgU+GwugfeWX1cUCgCg==} + next@16.0.7: + resolution: {integrity: sha512-3mBRJyPxT4LOxAJI6IsXeFtKfiJUbjCLgvXO02fV8Wy/lIhPvP94Fe7dGhUgHXcQy4sSuYwQNcOLhIfOm0rL0A==} engines: {node: '>=20.9.0'} - deprecated: This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/CVE-2025-66478 for more details. hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 @@ -4013,10 +4012,10 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - react-dom@19.2.0: - resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} + react-dom@19.2.1: + resolution: {integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==} peerDependencies: - react: ^19.2.0 + react: ^19.2.1 react-focus-lock@2.13.7: resolution: {integrity: sha512-20lpZHEQrXPb+pp1tzd4ULL6DyO5D2KnR0G69tTDdydrmNhU7pdFmbQUYVyHUgp+xN29IuFR0PVuhOmvaZL9Og==} @@ -4079,8 +4078,8 @@ packages: '@types/react': optional: true - react@19.2.0: - resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} + react@19.2.1: + resolution: {integrity: sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==} engines: {node: '>=0.10.0'} readable-stream@3.6.2: @@ -5911,11 +5910,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@mdx-js/react@3.1.1(@types/react@19.0.0)(react@19.2.0)': + '@mdx-js/react@3.1.1(@types/react@19.0.0)(react@19.2.1)': dependencies: '@types/mdx': 2.0.13 '@types/react': 19.0.0 - react: 19.2.0 + react: 19.2.1 '@nestjs/cli@10.4.9': dependencies: @@ -6044,37 +6043,37 @@ snapshots: - bufferutil - utf-8-validate - '@next/env@16.0.6': {} + '@next/env@16.0.7': {} - '@next/mdx@15.5.6(@mdx-js/loader@3.1.1(webpack@5.97.1))(@mdx-js/react@3.1.1(@types/react@19.0.0)(react@19.2.0))': + '@next/mdx@15.5.6(@mdx-js/loader@3.1.1(webpack@5.97.1))(@mdx-js/react@3.1.1(@types/react@19.0.0)(react@19.2.1))': dependencies: source-map: 0.7.6 optionalDependencies: '@mdx-js/loader': 3.1.1(webpack@5.97.1) - '@mdx-js/react': 3.1.1(@types/react@19.0.0)(react@19.2.0) + '@mdx-js/react': 3.1.1(@types/react@19.0.0)(react@19.2.1) - '@next/swc-darwin-arm64@16.0.6': + '@next/swc-darwin-arm64@16.0.7': optional: true - '@next/swc-darwin-x64@16.0.6': + '@next/swc-darwin-x64@16.0.7': optional: true - '@next/swc-linux-arm64-gnu@16.0.6': + '@next/swc-linux-arm64-gnu@16.0.7': optional: true - '@next/swc-linux-arm64-musl@16.0.6': + '@next/swc-linux-arm64-musl@16.0.7': optional: true - '@next/swc-linux-x64-gnu@16.0.6': + '@next/swc-linux-x64-gnu@16.0.7': optional: true - '@next/swc-linux-x64-musl@16.0.6': + '@next/swc-linux-x64-musl@16.0.7': optional: true - '@next/swc-win32-arm64-msvc@16.0.6': + '@next/swc-win32-arm64-msvc@16.0.7': optional: true - '@next/swc-win32-x64-msvc@16.0.6': + '@next/swc-win32-x64-msvc@16.0.7': optional: true '@noble/hashes@1.8.0': {} @@ -6100,16 +6099,16 @@ snapshots: '@polka/url@1.0.0-next.29': {} - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.0.0)(react@19.2.0)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.0.0)(react@19.2.1)': dependencies: - react: 19.2.0 + react: 19.2.1 optionalDependencies: '@types/react': 19.0.0 - '@radix-ui/react-slot@1.2.4(@types/react@19.0.0)(react@19.2.0)': + '@radix-ui/react-slot@1.2.4(@types/react@19.0.0)(react@19.2.1)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.0)(react@19.2.0) - react: 19.2.0 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.0)(react@19.2.1) + react: 19.2.1 optionalDependencies: '@types/react': 19.0.0 @@ -6265,13 +6264,13 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/react@14.3.1(@types/react@19.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@testing-library/react@14.3.1(@types/react@19.0.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@babel/runtime': 7.28.4 '@testing-library/dom': 9.3.4 '@types/react-dom': 18.3.7(@types/react@19.0.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) transitivePeerDependencies: - '@types/react' @@ -6880,7 +6879,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.9.4: {} + baseline-browser-mapping@2.9.5: {} bidi-js@1.0.3: dependencies: @@ -6926,7 +6925,7 @@ snapshots: browserslist@4.28.0: dependencies: - baseline-browser-mapping: 2.9.4 + baseline-browser-mapping: 2.9.5 caniuse-lite: 1.0.30001757 electron-to-chromium: 1.5.263 node-releases: 2.0.27 @@ -7702,15 +7701,15 @@ snapshots: fraction.js@5.3.4: {} - framer-motion@12.23.25(@emotion/is-prop-valid@1.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + framer-motion@12.23.25(@emotion/is-prop-valid@1.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: motion-dom: 12.23.23 motion-utils: 12.23.6 tslib: 2.8.1 optionalDependencies: '@emotion/is-prop-valid': 1.2.2 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) fresh@0.5.2: {} @@ -8521,12 +8520,12 @@ snapshots: '@hapi/topo': 6.0.2 '@standard-schema/spec': 1.0.0 - jotai@2.15.2(@babel/core@7.28.5)(@babel/template@7.27.2)(@types/react@19.0.0)(react@19.2.0): + jotai@2.15.2(@babel/core@7.28.5)(@babel/template@7.27.2)(@types/react@19.0.0)(react@19.2.1): optionalDependencies: '@babel/core': 7.28.5 '@babel/template': 7.27.2 '@types/react': 19.0.0 - react: 19.2.0 + react: 19.2.1 js-cookie@3.0.5: {} @@ -8638,9 +8637,9 @@ snapshots: dependencies: yallist: 3.1.1 - lucide-react@0.555.0(react@19.2.0): + lucide-react@0.555.0(react@19.2.1): dependencies: - react: 19.2.0 + react: 19.2.1 lz-string@1.5.0: {} @@ -9032,14 +9031,14 @@ snapshots: motion-utils@12.23.6: {} - motion@12.23.25(@emotion/is-prop-valid@1.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + motion@12.23.25(@emotion/is-prop-valid@1.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: - framer-motion: 12.23.25(@emotion/is-prop-valid@1.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + framer-motion: 12.23.25(@emotion/is-prop-valid@1.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) tslib: 2.8.1 optionalDependencies: '@emotion/is-prop-valid': 1.2.2 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) mrmime@2.0.1: {} @@ -9071,12 +9070,12 @@ snapshots: neo-async@2.6.2: {} - next-mdx-remote@5.0.0(@types/react@19.0.0)(react@19.2.0): + next-mdx-remote@5.0.0(@types/react@19.0.0)(react@19.2.1): dependencies: '@babel/code-frame': 7.27.1 '@mdx-js/mdx': 3.1.1 - '@mdx-js/react': 3.1.1(@types/react@19.0.0)(react@19.2.0) - react: 19.2.0 + '@mdx-js/react': 3.1.1(@types/react@19.0.0)(react@19.2.1) + react: 19.2.1 unist-util-remove: 3.1.1 vfile: 6.0.3 vfile-matter: 5.0.1 @@ -9084,24 +9083,24 @@ snapshots: - '@types/react' - supports-color - next@16.0.6(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + next@16.0.7(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: - '@next/env': 16.0.6 + '@next/env': 16.0.7 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001757 postcss: 8.4.31 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.2.1) optionalDependencies: - '@next/swc-darwin-arm64': 16.0.6 - '@next/swc-darwin-x64': 16.0.6 - '@next/swc-linux-arm64-gnu': 16.0.6 - '@next/swc-linux-arm64-musl': 16.0.6 - '@next/swc-linux-x64-gnu': 16.0.6 - '@next/swc-linux-x64-musl': 16.0.6 - '@next/swc-win32-arm64-msvc': 16.0.6 - '@next/swc-win32-x64-msvc': 16.0.6 + '@next/swc-darwin-arm64': 16.0.7 + '@next/swc-darwin-x64': 16.0.7 + '@next/swc-linux-arm64-gnu': 16.0.7 + '@next/swc-linux-arm64-musl': 16.0.7 + '@next/swc-linux-x64-gnu': 16.0.7 + '@next/swc-linux-x64-musl': 16.0.7 + '@next/swc-win32-arm64-msvc': 16.0.7 + '@next/swc-win32-x64-msvc': 16.0.7 '@playwright/test': 1.57.0 sharp: 0.34.5 transitivePeerDependencies: @@ -9417,33 +9416,33 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-clientside-effect@1.2.8(react@19.2.0): + react-clientside-effect@1.2.8(react@19.2.1): dependencies: '@babel/runtime': 7.28.4 - react: 19.2.0 + react: 19.2.1 - react-dom@19.2.0(react@19.2.0): + react-dom@19.2.1(react@19.2.1): dependencies: - react: 19.2.0 + react: 19.2.1 scheduler: 0.27.0 - react-focus-lock@2.13.7(@types/react@19.0.0)(react@19.2.0): + react-focus-lock@2.13.7(@types/react@19.0.0)(react@19.2.1): dependencies: '@babel/runtime': 7.28.4 focus-lock: 1.3.6 prop-types: 15.8.1 - react: 19.2.0 - react-clientside-effect: 1.2.8(react@19.2.0) - use-callback-ref: 1.3.3(@types/react@19.0.0)(react@19.2.0) - use-sidecar: 1.1.3(@types/react@19.0.0)(react@19.2.0) + react: 19.2.1 + react-clientside-effect: 1.2.8(react@19.2.1) + use-callback-ref: 1.3.3(@types/react@19.0.0)(react@19.2.1) + use-sidecar: 1.1.3(@types/react@19.0.0)(react@19.2.1) optionalDependencies: '@types/react': 19.0.0 - react-intersection-observer@9.16.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + react-intersection-observer@9.16.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: - react: 19.2.0 + react: 19.2.1 optionalDependencies: - react-dom: 19.2.0(react@19.2.0) + react-dom: 19.2.1(react@19.2.1) react-is@16.13.1: {} @@ -9453,34 +9452,34 @@ snapshots: react-refresh@0.18.0: {} - react-remove-scroll-bar@2.3.8(@types/react@19.0.0)(react@19.2.0): + react-remove-scroll-bar@2.3.8(@types/react@19.0.0)(react@19.2.1): dependencies: - react: 19.2.0 - react-style-singleton: 2.2.3(@types/react@19.0.0)(react@19.2.0) + react: 19.2.1 + react-style-singleton: 2.2.3(@types/react@19.0.0)(react@19.2.1) tslib: 2.8.1 optionalDependencies: '@types/react': 19.0.0 - react-remove-scroll@2.7.2(@types/react@19.0.0)(react@19.2.0): + react-remove-scroll@2.7.2(@types/react@19.0.0)(react@19.2.1): dependencies: - react: 19.2.0 - react-remove-scroll-bar: 2.3.8(@types/react@19.0.0)(react@19.2.0) - react-style-singleton: 2.2.3(@types/react@19.0.0)(react@19.2.0) + react: 19.2.1 + react-remove-scroll-bar: 2.3.8(@types/react@19.0.0)(react@19.2.1) + react-style-singleton: 2.2.3(@types/react@19.0.0)(react@19.2.1) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.0.0)(react@19.2.0) - use-sidecar: 1.1.3(@types/react@19.0.0)(react@19.2.0) + use-callback-ref: 1.3.3(@types/react@19.0.0)(react@19.2.1) + use-sidecar: 1.1.3(@types/react@19.0.0)(react@19.2.1) optionalDependencies: '@types/react': 19.0.0 - react-style-singleton@2.2.3(@types/react@19.0.0)(react@19.2.0): + react-style-singleton@2.2.3(@types/react@19.0.0)(react@19.2.1): dependencies: get-nonce: 1.0.1 - react: 19.2.0 + react: 19.2.1 tslib: 2.8.1 optionalDependencies: '@types/react': 19.0.0 - react@19.2.0: {} + react@19.2.1: {} readable-stream@3.6.2: dependencies: @@ -9992,7 +9991,7 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - styled-components@6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + styled-components@6.1.19(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: '@emotion/is-prop-valid': 1.2.2 '@emotion/unitless': 0.8.1 @@ -10000,16 +9999,16 @@ snapshots: css-to-react-native: 3.2.0 csstype: 3.1.3 postcss: 8.4.49 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) shallowequal: 1.1.0 stylis: 4.3.2 tslib: 2.6.2 - styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.0): + styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.1): dependencies: client-only: 0.0.1 - react: 19.2.0 + react: 19.2.1 optionalDependencies: '@babel/core': 7.28.5 @@ -10346,17 +10345,17 @@ snapshots: dependencies: punycode: 2.3.1 - use-callback-ref@1.3.3(@types/react@19.0.0)(react@19.2.0): + use-callback-ref@1.3.3(@types/react@19.0.0)(react@19.2.1): dependencies: - react: 19.2.0 + react: 19.2.1 tslib: 2.8.1 optionalDependencies: '@types/react': 19.0.0 - use-sidecar@1.1.3(@types/react@19.0.0)(react@19.2.0): + use-sidecar@1.1.3(@types/react@19.0.0)(react@19.2.1): dependencies: detect-node-es: 1.1.0 - react: 19.2.0 + react: 19.2.1 tslib: 2.8.1 optionalDependencies: '@types/react': 19.0.0 diff --git a/posts/posts.jsonl b/posts/posts.jsonl new file mode 100644 index 00000000..7db89091 --- /dev/null +++ b/posts/posts.jsonl @@ -0,0 +1,69 @@ +{"frontmatter":{"title":"ESModule 에 대해 좀 더 알아봅시다","date":"2025-07-18T00:00:00.000Z","tags":["javascript","vite","setting"],"summary":"오늘날 ESModule이 어떤 기능을 제공하는지, 어떻게 발전 했는지에 대해 알아보겠습니다.","slug":"about-es-module","category":"code","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/about-es-module.mdx"} +{"frontmatter":{"title":"패키지 매니저를 Yarn으로 변경하며 발생한 문제","date":"2025-06-10T00:00:00.000Z","tags":["package manager","yarn","monorepo"],"summary":"패키지 매니저만 변경 했는데 알 수 없는 오류가 발생했다.","slug":"about-yarn-monorepo","category":"code","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/about-yarn-monorepo.mdx"} +{"frontmatter":{"title":"V8 엔진이 어떻게 JavaScript를 실행할까요?","date":"2025-05-26T00:00:00.000Z","tags":["web","javascript","v8"],"summary":"V8 엔진은 JavaScript를 어떻게 실행하고 최적화할까요? V8 엔진의 컴파일 방식과 성능 향상 기법을 알아봅시다.","slug":"about-v8-engine","category":"web","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/web/about-v8-engine.mdx"} +{"frontmatter":{"title":"웹 브라우저의 동작 원리와 JavaScript 모듈 시스템","date":"2025-04-13T00:00:00.000Z","tags":["web","commonjs","esmodule"],"summary":"클라이언트가 브라우저를 통해 사이트에 접속해서 일어나는 일과 JavaScript 모듈 시스템에 대한 설명입니다.","slug":"web-browser-mechanisms-js-module-system","category":"web","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/web/web-browser-mechanisms-js-module-system.mdx"} +{"frontmatter":{"title":"Serverless 환경에서 I/O 처리 최적화 경험기","date":"2025-04-03T00:00:00.000Z","tags":["serverless","optimization"],"summary":"Promise.all, Worker를 벤치마크하며 서버리스 환경에서의 I/O 처리 최적화 경험기를 공유합니다.","slug":"serverless-io-optimization","category":"code","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/serverless-io-optimization.mdx"} +{"frontmatter":{"title":"가상메모리는 무엇인가요?","date":"2025-03-11T00:00:00.000Z","tags":["cs","memory","virtual-memory"],"summary":"메모리 관리의 중요성과 가상 메모리를 이용한 메모리 관리 방법에 대해 알아봅니다.","slug":"virtual-memory","category":"cs","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/cs/virtual-memory.mdx"} +{"frontmatter":{"title":"자바스크립트에서도 동기화 문제가 발생할까?","date":"2025-03-08T00:00:00.000Z","tags":["computer","javascript"],"summary":"싱글 스레드 언어인 자바스크립트에서 동기화 문제가 발생할 수 있을지에 대해서 알아보겠습니다.","slug":"thread_synchronization_with_javascript","category":"cs","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/cs/thread_synchronization_with_javascript.mdx"} +{"frontmatter":{"title":"스레드와 동기화에 관하여","date":"2025-03-03T00:00:00.000Z","tags":["computer","thread"],"summary":"스레드는 무엇이고 동기화가 필요한 이유, 문제가 되는 상황에 대해 알아보자","slug":"about-thread-with-synchronization","category":"cs","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/cs/about-thread-with-synchronization.mdx"} +{"frontmatter":{"title":"운영체제가 무엇이고 프로세스는 어떻게 관리할까?","date":"2025-02-24T00:00:00.000Z","tags":["computer","operating-system","process"],"summary":"운영체제의 역할과 프로세스의 개념에 대해서 알아보고, 프로세스의 상태와 생성 방법, CPU의 스케쥴링 방법에 대해서 알아봅니다.","slug":"os_with_process","category":"cs","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/cs/os_with_process.mdx"} +{"frontmatter":{"title":"세션, JWT, OAuth2.0 인증에 대해 알아보자","date":"2025-02-03T00:00:00.000Z","tags":["network","jwt","oauth"],"summary":"authentication-methods","slug":"authentication-methods","category":"web","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/web/authentication-methods.mdx"} +{"frontmatter":{"title":"Recoil 전역 상태관리와 코드 스플리팅으로 웹 성능 극대화하기","date":"2025-01-24T00:00:00.000Z","tags":["web","recoil","code-splitting","performance-optimization"],"summary":"Recoil 전역 상태관리와 코드 스플리팅으로 웹 성능 극대화하기","slug":"recoil-code-splitting-performance-optimization","category":"web","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/web/recoil-code-splitting-performance-optimization.mdx"} +{"frontmatter":{"title":"HTTP 변천사","date":"2025-01-22T00:00:00.000Z","tags":["network","http"],"summary":"http의 변천사를 알아보자","slug":"history-of-http","category":"cs","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/cs/history-of-http.mdx"} +{"frontmatter":{"title":"상태관리는 왜 중요할까?","date":"2025-01-15T00:00:00.000Z","tags":["react","state-management"],"summary":"프론트엔드에서 상태관리는 왜 중요한 것일까?","slug":"state-management","category":"code","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/state-management.mdx"} +{"frontmatter":{"title":"Vite와 Webpack의 차이점","date":"2025-01-04T00:00:00.000Z","tags":["vite","webpack"],"summary":"Vite와 Webpack는 어떤 차이가 있을까?","slug":"difference-webpack-between-vite","category":"code","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/difference-webpack-between-vite.mdx"} +{"frontmatter":{"title":"페이지 이동 시간을 단축 시켜보자","date":"2024-12-30T00:00:00.000Z","tags":["performance"],"summary":"페이지 이동시간을 측정하고 최적화 하는 방법에 대해서 알아보자.","slug":"lets-reduce-page-loading","category":"web","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/web/lets-reduce-page-loading.mdx"} +{"frontmatter":{"title":"Next.js 15 버전에서 변경된 것","date":"2024-12-24T00:00:00.000Z","tags":["nextjs"],"summary":"Next.js 15 버전에서 변경된 Dynamic Routes에 관하여","slug":"migrate-to-nextjs15","category":"code","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/migrate-to-nextjs15.mdx"} +{"frontmatter":{"title":"리액트 딥다이브 2탄","date":"2024-12-22T00:00:00.000Z","tags":["react","rendering"],"summary":"렌더링 프로세스와 useEffect에 대해서 알아보자.","slug":"react-deep-dive-2","category":"code","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/react-deep-dive-2.mdx"} +{"frontmatter":{"title":"API 응답에 딜레이가 있던 문제","date":"2024-12-10T00:00:00.000Z","tags":["vite","setting"],"summary":"API 요청에 딜레이가 있어서 발생한 문제를 해결하기 위한 방법에 대해서 다룹니다.","slug":"handling-api-delay","category":"code","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/handling-api-delay.mdx"} +{"frontmatter":{"title":"네트워크 요청 캐싱이 필요해졌다.","date":"2024-12-04T00:00:00.000Z","tags":["network","cache"],"summary":"반복되는 네트워크 요청을 어떻게 관리할 수 있을까?","slug":"network-caching","category":"code","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/network-caching.mdx"} +{"frontmatter":{"title":"Vite 설정 가이드","date":"2024-12-04T00:00:00.000Z","tags":["vite","setting"],"summary":"미들웨어 모드, 코드 분할, 캐싱 전략, CORS 프록시 설정을 하는 방법에 대해서 다루겠습니다.","slug":"about-vite-config-setting","category":"code","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/about-vite-config-setting.mdx"} +{"frontmatter":{"title":"리액트 딥다이브 1탄","date":"2024-11-24T00:00:00.000Z","tags":["react","rendering"],"summary":"리액트의 렌더링 프로세스와 메모이제이션, useEffect에 대해서 알아보자.","slug":"react-deep-dive-1","category":"code","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/react-deep-dive-1.mdx"} +{"frontmatter":{"title":"CSS Priority","date":"2024-11-20T00:00:00.000Z","tags":["css","style"],"summary":"CSS에서 우선순위가 중요한 이유에 대해서 알아보자.","slug":"css-priority","category":"code","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/css-priority.mdx"} +{"frontmatter":{"title":"IRC (Internet Relay Chat Protocol) 란?","date":"2024-11-10T00:00:00.000Z","tags":["network","protocol"],"summary":"IRC 프로토콜이 무엇이고 어떻게 동작하는 지에 대해서 알아보자.","slug":"about-irc-protocol","category":"web","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/web/about-irc-protocol.mdx"} +{"frontmatter":{"title":"Package를 선택하는 기준","date":"2024-10-25T00:00:00.000Z","tags":["environment","build","setting"],"summary":"npm, yarn, pnpm는 어떤 차이가 있고 어떤 패키지를 선택해야 할까?","slug":"npm-yarn-pnpm-comparison","category":"code","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/npm-yarn-pnpm-comparison.mdx"} +{"frontmatter":{"title":"LCP를 단축 시켜보자","date":"2024-10-24T00:00:00.000Z","tags":["performance"],"summary":"LCP는 우리 몸에 불필요하게 쌓여 있는 지방이다. 이 지방을 어떻게 해야 할까? 줄여야한다","slug":"lets-reduce-lcp","category":"web","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/web/lets-reduce-lcp.mdx"} +{"frontmatter":{"title":"데이터 중심 애플리케이션이 뭘까?","date":"2024-10-24T00:00:00.000Z","tags":["database","data-system"],"summary":"데이터 중심 애플리케이션은 무엇이고 왜 중요한 것일까?","slug":"what-is-data-application","category":"cs","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/cs/what-is-data-application.mdx"} +{"frontmatter":{"title":"Literator가 유용한 이유","date":"2024-10-24T00:00:00.000Z","tags":["javascript","es6"],"summary":"Literator는 무엇이고 왜 유용한지 알아보자.","slug":"usefulness-iterator-generator","category":"code","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/usefulness-iterator-generator.mdx"} +{"frontmatter":{"title":"프로시져가 무엇일까?","date":"2024-08-18T00:00:00.000Z","tags":["programming","programming-structure"],"summary":"싱글턴 디자인 패턴은 어떤 상황에서 사용되고 어떻게 사용해야 할까?","slug":"what-is-procedure","category":"code","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/what-is-procedure.mdx"} +{"frontmatter":{"title":"다양한 상황에서 사용되는 싱글턴","date":"2024-08-17T00:00:00.000Z","tags":["code","design-pattern"],"summary":"싱글턴 디자인 패턴은 어떤 상황에서 사용되고 어떻게 사용해야 할까?","slug":"design-pattern-singleton","category":"code","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/design-pattern-singleton.mdx"} +{"frontmatter":{"title":"git vsc가 무엇일까?","date":"2024-08-01T00:00:00.000Z","tags":["shell"],"summary":"git에서 일어나는 마법 같은 일의 원리를 알아보자","slug":"about-git-vsc","category":"cs","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/cs/about-git-vsc.mdx"} +{"frontmatter":{"title":"가상 머신 리눅스에 익숙해지기","date":"2024-07-15T00:00:00.000Z","tags":["shell"],"summary":"가상 머신에서 시간대를 확인하고 시간대를 설정하는 방법","slug":"virtual-machine-linux-usage","category":"cs","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/cs/virtual-machine-linux-usage.mdx"} +{"frontmatter":{"title":"cpu의 최적화 기법","date":"2024-07-13T00:00:00.000Z","tags":["computer","cpu"],"summary":"cpu를 왜 최적화 시켜야 하는지에 대해서 자세하게 알아보자","slug":"learn-about-cpu-optimization","category":"cs","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/cs/learn-about-cpu-optimization.mdx"} +{"frontmatter":{"title":"cpu에 관하여","date":"2024-06-24T00:00:00.000Z","tags":["computer"],"summary":"cpu가 왜 컴퓨터의 핵심 부품이고 어떤 기능을 하는지 알아보자","slug":"learn-about-cpu","category":"cs","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/cs/learn-about-cpu.mdx"} +{"frontmatter":{"title":"메세지를 전달하기 위해 필요한 TCP","date":"2024-06-16T00:00:00.000Z","tags":["network"],"summary":"우리가 전송하는 메세지는 어떻게 인터넷을 통해서 전달 될 수 있는 것인지 알아보자!","slug":"learn-about-tcp","category":"web","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/web/learn-about-tcp.mdx"} +{"frontmatter":{"title":"다른 사람들과 통신하기 위한 수단 IP","date":"2024-06-16T00:00:00.000Z","tags":["network"],"summary":"지역을 넘어서 도시를 넘어서 다른 사람들과 통신을 하기 위해서는 어떤 과정을 거쳐야 할까?","slug":"learn-about-ip","category":"web","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/web/learn-about-ip.mdx"} +{"frontmatter":{"title":"컴퓨터에 관하여","date":"2024-05-24T00:00:00.000Z","tags":["computer"],"summary":"컴퓨터의 속을 샅샅히 파헤쳐 보자","slug":"learn-about-computer","category":"cs","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/cs/learn-about-computer.mdx"} +{"frontmatter":{"title":"Network를 활용하기 위해 알아야 할 것들","date":"2024-05-12T00:00:00.000Z","tags":["network"],"summary":"현실에서 Network는 어떤 방법을 이용하여 사용되고 활용되고 있는지에 대해서 알아보자","slug":"learn-about-network","category":"web","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/web/learn-about-network.mdx"} +{"frontmatter":{"title":"gsap를 이용하여 컴포넌트에 활력을 더 해보자.","date":"2024-01-09T00:00:00.000Z","tags":["javascript","animation"],"summary":"많은 애니메이션 라이브러리 중에서 gsap를 어떻게 사용해야 하는지 잘 다루기 위해서는 어떠한 것들을 알아야 할지에 대해서 다루어 보겠습니다.","slug":"animation-library-gsap","category":"code","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/animation-library-gsap.mdx"} +{"frontmatter":{"title":"스크롤을 이용한 애니메이션 만들기","date":"2024-01-01T00:00:00.000Z","tags":["css","animation"],"summary":"웹에서 스크롤을 이용한 애니메이션은 특정 요소를 강조 시킬 수 있고 축소 시킬 수 있는 장점이 있습니다. 이번 포스팅에서는 스크롤을 이용한 애니메이션을 만드는 방법에 대해서 알아보겠습니다.","slug":"scroll-driven-animation-using-css","category":"code","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/scroll-driven-animation-using-css.mdx"} +{"frontmatter":{"title":"남의 코드를 읽어보자!","date":"2023-12-30T00:00:00.000Z","tags":["react","reading","react-focus-lock"],"summary":"내가 사용하는 패키지의 코드를 단순히 사용만 하는 것이 아니라 내부의 코드를 읽어보고 어떠한 구조를 내가 활용할 수 있는지 생각하고 구조를 분석하여 지식을 넓혀보자!","slug":"read-somoneIs-code-reactFocusLock","category":"code","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/read-somoneIs-code-reactFocusLock.mdx"} +{"frontmatter":{"title":"Private Data Protection","date":"2023-12-22T00:00:00.000Z","tags":["web","security"],"summary":"웹에서 우리의 개인 데이터를 지키기 위해서 알아야할 것들과 지켜야 할 것이 있습니다. 이 글에서는 우리의 개인 데이터를 지키기 위한 방법들에 대해서 이야기 해보고 어떻게 하면 더 안전하게 개인 데이터를 지킬 수 있을지에 대해서 이야기 해보겠습니다.","slug":"protect-our-personal-data","category":"web","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/web/protect-our-personal-data.mdx"} +{"frontmatter":{"title":"form은 어떻게 작성해야 할까?","date":"2023-12-16T00:00:00.000Z","tags":["html","form"],"summary":"인터넷에서 가장 중요한 태그를 뽑으라고 하면 당연히 form 태그일 것입니다. 이유는 우리가 전송하고 싶은 데이터를 확실하게 전달하기 위한 가장 쉬운 방법이기 때문입니다! 이러한 form에 대해서 자세하게 알아봅시다!","slug":"how-to-write-form","category":"code","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/how-to-write-form.mdx"} +{"frontmatter":{"title":"DisjointSet에 가중치가 더해졌을 경우","date":"2023-12-15T00:00:00.000Z","tags":["disjointset","graph"],"summary":"그래프에 가중치가 더해졌을 경우 DisjointSet을 어떻게 구현해야 하는지에 대한 문제 해결 방법과 문제에 대한 접근 방법에 대해서 자세하게 알아봅시다.","slug":"weighted-disjointset","category":"algorithm","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/algorithm/weighted-disjointset.mdx"} +{"frontmatter":{"title":"Postgresql","date":"2023-12-10T00:00:00.000Z","tags":["postgresql","database"],"summary":"postgresql은 무엇인지 어떻게 작동하는지에 대해 알아보자","slug":"about-postgresql","category":"code","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/about-postgresql.mdx"} +{"frontmatter":{"title":"WGSL","date":"2023-12-09T00:00:00.000Z","tags":["webgpu","wgsl","shader"],"summary":"wgsl은 webgpu를 위해 c언어를 기반으로 만들어진 언어이다. webgpu에서 특히 shader의 기능을 사용하기 위해서는 wgsl을 이해하는 것이 중요하다. 나만의 쉐이더를 만들기 위해 wgsl을 이해해보자.","slug":"whatis-wgsl","category":"code","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/whatis-wgsl.mdx"} +{"frontmatter":{"title":"useReducer","date":"2023-12-08T00:00:00.000Z","tags":["react"],"summary":"useReducer와 useState의 차이점은 뭘까?","slug":"what-is-useReducer","category":"code","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/what-is-useReducer.mdx"} +{"frontmatter":{"title":"Dynamic 해보자.","date":"2023-11-23T00:00:00.000Z","tags":["algorithm"],"summary":"너무나도 어려운 DP를 알아보자","slug":"whatis-dp","category":"algorithm","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/algorithm/whatis-dp.mdx"} +{"frontmatter":{"title":"2차원 배열 붙이기","date":"2023-11-22T00:00:00.000Z","tags":["stack"],"summary":"가로와 세로의 길이가 같은 2차원 배열의 크기 n이 주어졌을 때 1차원의 배열로 변환하여 배열 내의 모든 인덱스를 수로 채워 반환해보자.","slug":"problem-2dArray-slice","category":"algorithm","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/algorithm/problem-2dArray-slice.mdx"} +{"frontmatter":{"title":"경우의 수를 활용하자.","date":"2023-11-22T00:00:00.000Z","tags":["hashMap"],"summary":"문제는 코니가 갖고 있는 옷장의 옷의 종류에 따라 코니가 코디할 수 있는 코디의 개수에 대한 경우의 수를 구하는 문제이다. 경우의 수를 구하는 문제를 HashMap을 이용하여 어떻게 해결할 수 있는지에 대해서 알아보자.","slug":"number-of-cases","category":"algorithm","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/algorithm/number-of-cases.mdx"} +{"frontmatter":{"title":"올바른 괄호 찾기","date":"2023-11-20T00:00:00.000Z","tags":["stack"],"summary":"문자열 내에 열고 닫히는 괄호가 적절하게 작성되어 있는지 판단하는 문제","slug":"problem-rotation-bracket","category":"algorithm","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/algorithm/problem-rotation-bracket.mdx"} +{"frontmatter":{"title":"공원 산책 문제","date":"2023-11-19T00:00:00.000Z","tags":["dfs"],"summary":"DFS를 이용하여 공원 내의 장애물과 산책이 가능한 길에 대한 정보가 담겨 있는 park와 상하좌우 방향 중 어디로 움직일 것인지에 대한 문제이다.","slug":"problem-walk-inthe-park","category":"algorithm","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/algorithm/problem-walk-inthe-park.mdx"} +{"frontmatter":{"title":"컴퓨터의 기본기","date":"2023-11-17T00:00:00.000Z","tags":["computer"],"summary":"메모리등 에대한 기본기를 갖추자","slug":"about-computer-basement","category":"cs","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/cs/about-computer-basement.mdx"} +{"frontmatter":{"title":"객체 지향의 사실과 오해","date":"2023-11-08T00:00:00.000Z","tags":["mathmatics"],"summary":"이상한 나라의 객체들","slug":"about-object-oriented-book","category":"cs","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/cs/about-object-oriented-book.mdx"} +{"frontmatter":{"title":"유명한 친구 MVC 패턴","date":"2023-11-07T00:00:00.000Z","tags":["designPattern"],"summary":"소문만 무성했던 MVC","slug":"about-mvc-pattern","category":"cs","completed":true},"filePath":"/home/sunub/workspace/sunub.github.io/posts/cs/about-mvc-pattern.mdx"} +{"frontmatter":{"title":"객체지향을 향해","date":"2023-11-06T00:00:00.000Z","tags":["mathmatics"],"summary":"객체란 무엇이고 왜 지향할까?","slug":"about-object-oriented","category":"cs","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/cs/about-object-oriented.mdx"} +{"frontmatter":{"title":"클래스와 인터페이스의 차이점","date":"2023-11-03T00:00:00.000Z","tags":["javascript"],"summary":"두가지는 뭐가 다른 걸까?","slug":"difference-between-class-and-interface","category":"code","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/difference-between-class-and-interface.mdx"} +{"frontmatter":{"title":"static은 무슨 의미일까?","date":"2023-10-31T00:00:00.000Z","tags":["javascript"],"summary":"static에 대해서 자세히 알아보자","slug":"what-is-static","category":"code","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/what-is-static.mdx"} +{"frontmatter":{"title":"테스트 코드에 관하여!","date":"2023-10-30T00:00:00.000Z","tags":["testCode"],"summary":"mocking은 뭐하는 걸까?","slug":"about-testing-code","category":"cs","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/cs/about-testing-code.mdx"} +{"frontmatter":{"title":"테스트 주도 개발이 뭘까?","date":"2023-10-25T00:00:00.000Z","tags":["designPattern"],"summary":"테스트 주도 개발은 무엇이고 왜 지향할까?","slug":"about-test-driven-dev","category":"cs","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/cs/about-test-driven-dev.mdx"} +{"frontmatter":{"title":"클린 코드책 공부","date":"2023-10-23T00:00:00.000Z","tags":["mathmatics"],"summary":"깨끗한 코드를 위한 여정","slug":"about-clean-code","category":"cs","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/cs/about-clean-code.mdx"} +{"frontmatter":{"title":"tabIndex란 무엇일까?","date":"2023-10-17T00:00:00.000Z","tags":["html"],"summary":"탭을 사용하고 싶으면 써야할까요?","slug":"whatis-tab-index","category":"code","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/whatis-tab-index.mdx"} +{"frontmatter":{"title":"자연수의 분할","date":"2023-09-12T00:00:00.000Z","tags":["mathmatics"],"summary":"알아야 할 기초 수학들","slug":"about-integer-partition","category":"cs","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/cs/about-integer-partition.mdx"} +{"frontmatter":{"title":"그래프를 탐색하는 방법들(1)","date":"2023-08-14T00:00:00.000Z","tags":["algorithm"],"summary":"여러가지 그래프를 탐색하는 방법들에 대해서","slug":"how-to-search-graph","category":"algorithm","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/algorithm/how-to-search-graph.mdx"} +{"frontmatter":{"title":"Backtracking에 관하여...","date":"2023-08-12T00:00:00.000Z","tags":["algorithm"],"summary":"모든 배열을 순환하여 탐색할 수 있는 방법","slug":"whatis-backtracking","category":"algorithm","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/algorithm/whatis-backtracking.mdx"} +{"frontmatter":{"title":"갑자기 낯선 localhost","date":"2023-07-27T00:00:00.000Z","tags":["web"],"summary":"우리가 매일 보는 localhost에 관하여","slug":"about-localhost","category":"web","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/web/about-localhost.mdx"} +{"frontmatter":{"title":"REST API란?","date":"2023-06-15T00:00:00.000Z","tags":["web"],"summary":"REST는 애플리케이션의 아키텍쳐이다.","slug":"whatis-rest","category":"web","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/web/whatis-rest.mdx"} +{"frontmatter":{"title":"스트림이란?","date":"2023-06-12T00:00:00.000Z","tags":["web"],"summary":"Web API에서 Stream 이 무엇인지에 관하여.","slug":"whatis-stream","category":"web","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/web/whatis-stream.mdx"} +{"frontmatter":{"title":"타입스크립트의 선언문들","date":"2023-06-12T00:00:00.000Z","tags":["typescript"],"summary":"타입 선언문 Interface에 대해서 이해해보자","slug":"whatis-declaration","category":"code","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/whatis-declaration.mdx"} +{"frontmatter":{"title":"자바스크립트의 작동 방식","date":"2023-06-12T00:00:00.000Z","tags":["javascript"],"summary":"자바스크립트는 어떻게 동작하는 녀석일까?","slug":"howto-activate-javascript","category":"code","completed":false},"filePath":"/home/sunub/workspace/sunub.github.io/posts/code/howto-activate-javascript.mdx"} \ No newline at end of file