@blastlabs/utils 1.20.0 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/Makefile ADDED
@@ -0,0 +1,23 @@
1
+ .PHONY: help all init-routes init-ai-rules generate-entity
2
+
3
+ help:
4
+ @echo "blastlabs CLI Tools"
5
+ @echo ""
6
+ @echo "Usage: make <command>"
7
+ @echo ""
8
+ @echo "Available commands:"
9
+ @echo " make all - Run all CLI commands"
10
+ @echo " make init-routes - Initialize routes folder structure"
11
+ @echo " make init-ai-rules - Initialize AI rules files"
12
+ @echo " make generate-entity - Generate entity scaffold"
13
+
14
+ all: init-routes init-ai-rules generate-entity
15
+
16
+ init-routes:
17
+ node init-routes.cjs
18
+
19
+ init-ai-rules:
20
+ node init-ai-rules.cjs
21
+
22
+ generate-entity:
23
+ node generate-entity.cjs
@@ -0,0 +1,422 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ // routes 폴더 구조와 내용을 정의
7
+ const routesStructure = {
8
+ "base.ts": `/**
9
+ * 기본 라우트 경로
10
+ */
11
+
12
+ export const BASE_ROUTES = {
13
+ HOME: '/',
14
+ } as const;
15
+ `,
16
+ "types.ts": `/**
17
+ * 라우트 경로 관련 타입 정의
18
+ */
19
+
20
+ import type { ROUTE_PATH } from './index';
21
+
22
+ /**
23
+ * 라우트 경로 타입
24
+ */
25
+ export type RoutePath = typeof ROUTE_PATH;
26
+ `,
27
+ "auth.ts": `/**
28
+ * 인증 관련 라우트 경로
29
+ */
30
+
31
+ import { createRoute } from './utils/helpers';
32
+
33
+ const AUTH_BASE = '/auth';
34
+
35
+ export const AUTH_ROUTES = {
36
+ ROOT: AUTH_BASE,
37
+ LOGIN: createRoute(AUTH_BASE, '/login'),
38
+ REGISTER: createRoute(AUTH_BASE, '/register'),
39
+ FORGOT_PASSWORD: createRoute(AUTH_BASE, '/forgot-password'),
40
+ RESET_PASSWORD: createRoute(AUTH_BASE, '/reset-password'),
41
+ VERIFY_EMAIL: createRoute(AUTH_BASE, '/verify-email'),
42
+ VERIFY_OTP: createRoute(AUTH_BASE, '/verify-otp'),
43
+ } as const;
44
+ `,
45
+ "post.ts": `/**
46
+ * 게시글 관련 라우트 경로
47
+ */
48
+
49
+ import { createRoute } from './utils/helpers';
50
+
51
+ const POST_BASE = '/post';
52
+
53
+ export const POST_ROUTES = {
54
+ ROOT: POST_BASE,
55
+ LIST: POST_BASE,
56
+ CREATE: createRoute(POST_BASE, '/create'),
57
+ DETAIL: createRoute(POST_BASE, '/[id]'),
58
+ } as const;
59
+ `,
60
+ "users.example.ts": `/**
61
+ * 사용자 관련 라우트 경로 예시
62
+ * 복사해서 사용하세요
63
+ */
64
+
65
+ import { createRoute } from './utils/helpers';
66
+
67
+ const USERS_BASE = '/users';
68
+
69
+ export const USERS_ROUTES = {
70
+ ROOT: USERS_BASE,
71
+ LIST: USERS_BASE,
72
+ DETAIL: createRoute(USERS_BASE, '/[id]'),
73
+ CREATE: createRoute(USERS_BASE, '/create'),
74
+ EDIT: createRoute(USERS_BASE, '/[id]/edit'),
75
+ } as const;
76
+ `,
77
+ "index.ts": `/**
78
+ * 애플리케이션 라우트 경로 상수
79
+ * Next.js App Router 기준으로 작성되었습니다.
80
+ *
81
+ * 사용 예시:
82
+ * - ROUTE_PATH.HOME
83
+ * - ROUTE_PATH.AUTH.LOGIN
84
+ * - withParam(ROUTE_PATH.USERS.DETAIL, { id: '123' })
85
+ */
86
+
87
+ import { BASE_ROUTES } from './base';
88
+ import { AUTH_ROUTES } from './auth';
89
+ import { POST_ROUTES } from './post';
90
+
91
+ /**
92
+ * 라우트 경로 상수 객체
93
+ */
94
+ export const ROUTE_PATH = {
95
+ ...BASE_ROUTES,
96
+ AUTH: AUTH_ROUTES,
97
+ POST: POST_ROUTES,
98
+ } as const;
99
+
100
+ // 유틸리티 함수 재내보내기
101
+ export { withParam, withQuery } from './utils/helpers';
102
+
103
+ // 타입 내보내기
104
+ export type { RoutePath } from './types';
105
+ `,
106
+ "utils": {
107
+ "helpers.ts": `/**
108
+ * 라우트 경로 관련 유틸리티 함수
109
+ */
110
+
111
+ /**
112
+ * 동적 라우트 파라미터를 포함한 경로를 생성하는 헬퍼 함수
113
+ * @param path - 기본 경로 (예: '/users/[id]')
114
+ * @param params - 파라미터 객체 (예: { id: '123' })
115
+ * @returns 실제 경로 (예: '/users/123')
116
+ *
117
+ * @example
118
+ * \`\`\`ts
119
+ * const userPath = withParam('/users/[id]', { id: '123' });
120
+ * // '/users/123'
121
+ * \`\`\`
122
+ */
123
+ export function withParam<T extends string>(
124
+ path: T,
125
+ params: Record<string, string | number>,
126
+ ): string {
127
+ let result: string = path;
128
+ for (const [key, value] of Object.entries(params)) {
129
+ result = result.replace(\`[\${key}]\`, String(value));
130
+ }
131
+ return result;
132
+ }
133
+
134
+ /**
135
+ * 쿼리 파라미터를 포함한 경로를 생성하는 헬퍼 함수
136
+ * @param path - 기본 경로
137
+ * @param query - 쿼리 파라미터 객체
138
+ * @returns 쿼리 파라미터가 포함된 경로
139
+ *
140
+ * @example
141
+ * \`\`\`ts
142
+ * const searchPath = withQuery('/search', { q: 'test', page: 1 });
143
+ * // '/search?q=test&page=1'
144
+ * \`\`\`
145
+ */
146
+ export function withQuery(
147
+ path: string,
148
+ query: Record<string, string | number | boolean | null | undefined>,
149
+ ): string {
150
+ const searchParams = new URLSearchParams();
151
+ for (const [key, value] of Object.entries(query)) {
152
+ if (value !== null && value !== undefined) {
153
+ searchParams.append(key, String(value));
154
+ }
155
+ }
156
+ const queryString = searchParams.toString();
157
+ return queryString ? \`\${path}?\${queryString}\` : path;
158
+ }
159
+
160
+ /**
161
+ * 라우트 경로를 생성하는 헬퍼 함수
162
+ * @param base - 기본 경로
163
+ * @param path - 추가 경로
164
+ * @returns 합쳐진 경로
165
+ *
166
+ * @example
167
+ * \`\`\`ts
168
+ * const route = createRoute('/auth', '/login');
169
+ * // '/auth/login'
170
+ * \`\`\`
171
+ */
172
+ export function createRoute(base: string, path: string): string {
173
+ return \`\${base}\${path.startsWith('/') ? path : \`/\${path}\`}\`;
174
+ }
175
+ `,
176
+ "helpers.test.ts": `import { describe, it, expect } from 'vitest';
177
+ import { withParam, withQuery, createRoute } from './helpers';
178
+
179
+ describe('withParam', () => {
180
+ it('단일 파라미터를 변환한다', () => {
181
+ const result = withParam('/users/[id]', { id: '123' });
182
+ expect(result).toBe('/users/123');
183
+ });
184
+
185
+ it('여러 파라미터를 변환한다', () => {
186
+ const result = withParam('/posts/[postId]/comments/[commentId]', {
187
+ postId: '456',
188
+ commentId: '789'
189
+ });
190
+ expect(result).toBe('/posts/456/comments/789');
191
+ });
192
+
193
+ it('숫자 파라미터를 처리한다', () => {
194
+ const result = withParam('/users/[id]', { id: 123 });
195
+ expect(result).toBe('/users/123');
196
+ });
197
+ });
198
+
199
+ describe('withQuery', () => {
200
+ it('단일 쿼리 파라미터를 추가한다', () => {
201
+ const result = withQuery('/search', { q: 'test' });
202
+ expect(result).toBe('/search?q=test');
203
+ });
204
+
205
+ it('여러 쿼리 파라미터를 추가한다', () => {
206
+ const result = withQuery('/search', { q: 'test', page: 1 });
207
+ expect(result).toBe('/search?q=test&page=1');
208
+ });
209
+
210
+ it('null과 undefined을 무시한다', () => {
211
+ const result = withQuery('/search', { q: 'test', page: null, limit: undefined });
212
+ expect(result).toBe('/search?q=test');
213
+ });
214
+
215
+ it('빈 쿼리 객체는 경로를 변경하지 않는다', () => {
216
+ const result = withQuery('/search', {});
217
+ expect(result).toBe('/search');
218
+ });
219
+
220
+ it('boolean 파라미터를 처리한다', () => {
221
+ const result = withQuery('/users', { expanded: true });
222
+ expect(result).toBe('/users?expanded=true');
223
+ });
224
+ });
225
+
226
+ describe('createRoute', () => {
227
+ it('기본 경로와 하위 경로를 합친다', () => {
228
+ const result = createRoute('/auth', '/login');
229
+ expect(result).toBe('/auth/login');
230
+ });
231
+
232
+ it('슬래시 없는 하위 경로도 처리한다', () => {
233
+ const result = createRoute('/auth', 'login');
234
+ expect(result).toBe('/auth/login');
235
+ });
236
+ });
237
+ `
238
+ },
239
+ "README.md": `# 라우터 구조
240
+
241
+ 라우트 경로를 도메인별로 분리하여 관리하는 구조입니다.
242
+
243
+ ## 폴더 구조
244
+
245
+ \`\`\`
246
+ routes/
247
+ ├── utils/ # 유틸리티 함수
248
+ │ └── helpers.ts # withParam, withQuery, createRoute 등
249
+ ├── types.ts # 타입 정의
250
+ ├── base.ts # 기본 라우트 (HOME 등)
251
+ ├── auth.ts # 인증 관련 라우트
252
+ ├── post.ts # 게시글 관련 라우트
253
+ ├── users.example.ts # 확장 예시 파일
254
+ └── index.ts # 모든 라우트 통합 및 내보내기
255
+ \`\`\`
256
+
257
+ ## 사용 방법
258
+
259
+ ### 기본 사용
260
+
261
+ \`\`\`typescript
262
+ import { ROUTE_PATH } from '@/app/routes';
263
+
264
+ // 기본 라우트
265
+ ROUTE_PATH.HOME // '/'
266
+
267
+ // 인증 라우트
268
+ ROUTE_PATH.AUTH.LOGIN // '/auth/login'
269
+ ROUTE_PATH.AUTH.REGISTER // '/auth/register'
270
+
271
+ // 게시글 라우트
272
+ ROUTE_PATH.POST.CREATE // '/post/create'
273
+ \`\`\`
274
+
275
+ ### 동적 라우트 파라미터
276
+
277
+ \`\`\`typescript
278
+ import { ROUTE_PATH, withParam } from '@/app/routes';
279
+
280
+ // 단일 파라미터
281
+ const postPath = withParam(ROUTE_PATH.POST.DETAIL, { id: '123' });
282
+ // 결과: '/post/123'
283
+
284
+ // 여러 파라미터 (파라미터가 여러 개인 경우)
285
+ const customPath = withParam('/posts/[postId]/comments/[commentId]', {
286
+ postId: '456',
287
+ commentId: '789'
288
+ });
289
+ // 결과: '/posts/456/comments/789'
290
+ \`\`\`
291
+
292
+ ### 파라미터와 쿼리 조합
293
+
294
+ \`\`\`typescript
295
+ import { ROUTE_PATH, withParam, withQuery } from '@/app/routes';
296
+
297
+ // withParam으로 파라미터 변환 후 withQuery로 쿼리 추가
298
+ const detailPath = withQuery(
299
+ withParam(ROUTE_PATH.POST.DETAIL, { id: '123' }),
300
+ { tab: 'edit', expanded: true }
301
+ );
302
+ // 결과: '/post/123?tab=edit&expanded=true'
303
+ \`\`\`
304
+
305
+ ## 새로운 라우트 그룹 추가하기
306
+
307
+ ### 1. 새로운 라우트 파일 생성
308
+
309
+ 예: \`products.ts\`
310
+
311
+ \`\`\`typescript
312
+ import { createRoute } from './utils/helpers';
313
+
314
+ const PRODUCTS_BASE = '/products';
315
+
316
+ export const PRODUCTS_ROUTES = {
317
+ ROOT: PRODUCTS_BASE,
318
+ LIST: PRODUCTS_BASE,
319
+ DETAIL: createRoute(PRODUCTS_BASE, '/[id]'),
320
+ CREATE: createRoute(PRODUCTS_BASE, '/create'),
321
+ } as const;
322
+ \`\`\`
323
+
324
+ ### 2. index.ts에 추가
325
+
326
+ \`\`\`typescript
327
+ import { PRODUCTS_ROUTES } from './products'; // 추가
328
+
329
+ export const ROUTE_PATH = {
330
+ ...BASE_ROUTES,
331
+ AUTH: AUTH_ROUTES,
332
+ POST: POST_ROUTES,
333
+ PRODUCTS: PRODUCTS_ROUTES, // 추가
334
+ } as const;
335
+ \`\`\`
336
+
337
+ ### 3. 사용하기
338
+
339
+ \`\`\`typescript
340
+ import { ROUTE_PATH } from '@/app/routes';
341
+
342
+ ROUTE_PATH.PRODUCTS.LIST // '/products'
343
+ ROUTE_PATH.PRODUCTS.DETAIL // '/products/[id]'
344
+ \`\`\`
345
+
346
+ ## 파일 역할
347
+
348
+ - **base.ts**: 애플리케이션 전체에서 공통으로 사용되는 기본 라우트 (HOME 등)
349
+ - **auth.ts**: 인증 관련 라우트
350
+ - **post.ts**: 게시글 관련 라우트
351
+ - **[domain].ts**: 특정 도메인(예: users, products, orders)의 라우트
352
+ - **utils/helpers.ts**: 라우트 생성 및 조작을 위한 유틸리티 함수
353
+ - **types.ts**: 라우트 관련 타입 정의
354
+ - **index.ts**: 모든 라우트를 통합하여 내보내는 진입점
355
+
356
+ ## 원칙
357
+
358
+ 1. **도메인별 분리**: 각 도메인은 별도 파일로 관리
359
+ 2. **일관된 네이밍**: 모든 라우트 그룹은 \`[DOMAIN]_ROUTES\` 형태로 명명
360
+ 3. **타입 안전성**: \`as const\`를 사용하여 타입 추론 최적화
361
+ 4. **재사용 가능한 유틸리티**: \`createRoute\`, \`withParam\`, \`withQuery\` 등을 활용
362
+ `
363
+ };
364
+
365
+ // 디렉토리와 파일을 재귀적으로 생성하는 함수
366
+ function createDirectoryStructure(basePath, structure) {
367
+ // basePath가 존재하지 않으면 생성
368
+ if (!fs.existsSync(basePath)) {
369
+ fs.mkdirSync(basePath, { recursive: true });
370
+ }
371
+
372
+ for (const [name, content] of Object.entries(structure)) {
373
+ const fullPath = path.join(basePath, name);
374
+
375
+ if (typeof content === 'object') {
376
+ // 디렉토리인 경우
377
+ if (!fs.existsSync(fullPath)) {
378
+ fs.mkdirSync(fullPath, { recursive: true });
379
+ }
380
+ createDirectoryStructure(fullPath, content);
381
+ } else {
382
+ // 파일인 경우
383
+ fs.writeFileSync(fullPath, content, 'utf8');
384
+ }
385
+ }
386
+ }
387
+
388
+ // CLI 실행
389
+ function main() {
390
+ const targetDir = process.cwd();
391
+ const routesPath = path.join(targetDir, 'src', 'app', 'routes');
392
+
393
+ // 이미 routes 폴더가 있는지 확인
394
+ if (fs.existsSync(routesPath)) {
395
+ console.log('⚠️ src/app/routes 폴더가 이미 존재합니다.');
396
+ console.log('📂 기존 폴더를 삭제하고 다시 생성하려면 수동으로 삭제 후 다시 실행하세요.');
397
+ process.exit(1);
398
+ }
399
+
400
+ // routes 폴더 생성
401
+ createDirectoryStructure(routesPath, routesStructure);
402
+
403
+ console.log('✅ routes 폴더가 성공적으로 생성되었습니다!\\n');
404
+ console.log('📍 위치: src/app/routes\\n');
405
+ console.log('생성된 파일들:');
406
+ console.log('📁 routes/');
407
+ console.log('├── utils/');
408
+ console.log('│ ├── helpers.ts');
409
+ console.log('│ └── helpers.test.ts');
410
+ console.log('├── base.ts');
411
+ console.log('├── types.ts');
412
+ console.log('├── auth.ts');
413
+ console.log('├── post.ts');
414
+ console.log('├── users.example.ts');
415
+ console.log('├── index.ts');
416
+ console.log('└── README.md\\n');
417
+ console.log('사용 방법:');
418
+ console.log(" import { ROUTE_PATH, withParam, withQuery } from '@/app/routes';");
419
+ console.log(" ROUTE_PATH.AUTH.LOGIN // '/auth/login'");
420
+ }
421
+
422
+ main();
package/dist/index.d.ts CHANGED
@@ -3,4 +3,5 @@ export * from './form';
3
3
  export * from './string';
4
4
  export * from './number';
5
5
  export * from './mock';
6
+ export * from './routes';
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,QAAQ,CAAC;AAGvB,cAAc,QAAQ,CAAC;AAGvB,cAAc,UAAU,CAAC;AAGzB,cAAc,UAAU,CAAC;AAGzB,cAAc,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,QAAQ,CAAC;AAGvB,cAAc,QAAQ,CAAC;AAGvB,cAAc,UAAU,CAAC;AAGzB,cAAc,UAAU,CAAC;AAGzB,cAAc,QAAQ,CAAC;AAYvB,cAAc,UAAU,CAAC"}
package/dist/index.js CHANGED
@@ -15,3 +15,4 @@ export * from './mock';
15
15
  // export * from './array';
16
16
  // Object utilities (placeholder for future)
17
17
  // export * from './object';
18
+ export * from './routes';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * 인증 관련 라우트 경로
3
+ */
4
+ export declare const AUTH_ROUTES: {
5
+ readonly ROOT: "/auth";
6
+ readonly LOGIN: string;
7
+ readonly REGISTER: string;
8
+ readonly FORGOT_PASSWORD: string;
9
+ readonly RESET_PASSWORD: string;
10
+ readonly VERIFY_EMAIL: string;
11
+ readonly VERIFY_OTP: string;
12
+ };
13
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/routes/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,eAAO,MAAM,WAAW;;;;;;;;CAQd,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 인증 관련 라우트 경로
3
+ */
4
+ import { createRoute } from './utils/helpers';
5
+ const AUTH_BASE = '/auth';
6
+ export const AUTH_ROUTES = {
7
+ ROOT: AUTH_BASE,
8
+ LOGIN: createRoute(AUTH_BASE, '/login'),
9
+ REGISTER: createRoute(AUTH_BASE, '/register'),
10
+ FORGOT_PASSWORD: createRoute(AUTH_BASE, '/forgot-password'),
11
+ RESET_PASSWORD: createRoute(AUTH_BASE, '/reset-password'),
12
+ VERIFY_EMAIL: createRoute(AUTH_BASE, '/verify-email'),
13
+ VERIFY_OTP: createRoute(AUTH_BASE, '/verify-otp'),
14
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 기본 라우트 경로
3
+ */
4
+ export declare const BASE_ROUTES: {
5
+ readonly HOME: "/";
6
+ };
7
+ //# sourceMappingURL=base.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/routes/base.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,WAAW;;CAEd,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 기본 라우트 경로
3
+ */
4
+ export const BASE_ROUTES = {
5
+ HOME: '/',
6
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 애플리케이션 라우트 경로 상수
3
+ * Next.js App Router 기준으로 작성되었습니다.
4
+ *
5
+ * 사용 예시:
6
+ * - ROUTE_PATH.HOME
7
+ * - ROUTE_PATH.AUTH.LOGIN
8
+ * - withParam(ROUTE_PATH.USERS.DETAIL, { id: '123' })
9
+ */
10
+ /**
11
+ * 라우트 경로 상수 객체
12
+ */
13
+ export declare const ROUTE_PATH: {
14
+ readonly AUTH: {
15
+ readonly ROOT: "/auth";
16
+ readonly LOGIN: string;
17
+ readonly REGISTER: string;
18
+ readonly FORGOT_PASSWORD: string;
19
+ readonly RESET_PASSWORD: string;
20
+ readonly VERIFY_EMAIL: string;
21
+ readonly VERIFY_OTP: string;
22
+ };
23
+ readonly POST: {
24
+ readonly ROOT: "/post";
25
+ readonly LIST: "/post";
26
+ readonly CREATE: string;
27
+ readonly DETAIL: string;
28
+ };
29
+ readonly HOME: "/";
30
+ };
31
+ export { withParam, withQuery } from './utils/helpers';
32
+ export type { RoutePath } from './types';
33
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/routes/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH;;GAEG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;CAIb,CAAC;AAGX,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGvD,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * 애플리케이션 라우트 경로 상수
3
+ * Next.js App Router 기준으로 작성되었습니다.
4
+ *
5
+ * 사용 예시:
6
+ * - ROUTE_PATH.HOME
7
+ * - ROUTE_PATH.AUTH.LOGIN
8
+ * - withParam(ROUTE_PATH.USERS.DETAIL, { id: '123' })
9
+ */
10
+ import { BASE_ROUTES } from './base';
11
+ import { AUTH_ROUTES } from './auth';
12
+ import { POST_ROUTES } from './post';
13
+ /**
14
+ * 라우트 경로 상수 객체
15
+ */
16
+ export const ROUTE_PATH = {
17
+ ...BASE_ROUTES,
18
+ AUTH: AUTH_ROUTES,
19
+ POST: POST_ROUTES,
20
+ };
21
+ // 유틸리티 함수 재내보내기
22
+ export { withParam, withQuery } from './utils/helpers';
@@ -0,0 +1,7 @@
1
+ export declare const POST_ROUTES: {
2
+ readonly ROOT: "/post";
3
+ readonly LIST: "/post";
4
+ readonly CREATE: string;
5
+ readonly DETAIL: string;
6
+ };
7
+ //# sourceMappingURL=post.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post.d.ts","sourceRoot":"","sources":["../../src/routes/post.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW;;;;;CAKd,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { createRoute } from './utils/helpers';
2
+ const POST_BASE = '/post';
3
+ export const POST_ROUTES = {
4
+ ROOT: POST_BASE,
5
+ LIST: POST_BASE,
6
+ CREATE: createRoute(POST_BASE, '/create'),
7
+ DETAIL: createRoute(POST_BASE, '/[id]'),
8
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 라우트 경로 관련 타입 정의
3
+ */
4
+ import type { ROUTE_PATH } from './index';
5
+ /**
6
+ * 라우트 경로 타입
7
+ */
8
+ export type RoutePath = typeof ROUTE_PATH;
9
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/routes/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,OAAO,UAAU,CAAC"}
@@ -0,0 +1,4 @@
1
+ /**
2
+ * 라우트 경로 관련 타입 정의
3
+ */
4
+ export {};
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 사용자 관련 라우트 경로 예시
3
+ *
4
+ * 새로운 도메인 라우트를 추가할 때 이 파일을 참고하세요.
5
+ *
6
+ * 사용 방법:
7
+ * 1. 이 파일을 users.ts로 이름 변경
8
+ * 2. routes/index.ts에서 import하여 ROUTE_PATH에 추가
9
+ */
10
+ export declare const USERS_ROUTES: {
11
+ readonly ROOT: "/users";
12
+ readonly LIST: "/users";
13
+ readonly DETAIL: string;
14
+ readonly CREATE: string;
15
+ readonly EDIT: string;
16
+ readonly SETTINGS: string;
17
+ readonly PROFILE: string;
18
+ };
19
+ //# sourceMappingURL=users.example.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"users.example.d.ts","sourceRoot":"","sources":["../../src/routes/users.example.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,eAAO,MAAM,YAAY;;;;;;;;CAQf,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * 사용자 관련 라우트 경로 예시
3
+ *
4
+ * 새로운 도메인 라우트를 추가할 때 이 파일을 참고하세요.
5
+ *
6
+ * 사용 방법:
7
+ * 1. 이 파일을 users.ts로 이름 변경
8
+ * 2. routes/index.ts에서 import하여 ROUTE_PATH에 추가
9
+ */
10
+ import { createRoute } from './utils/helpers';
11
+ const USERS_BASE = '/users';
12
+ export const USERS_ROUTES = {
13
+ ROOT: USERS_BASE,
14
+ LIST: USERS_BASE,
15
+ DETAIL: createRoute(USERS_BASE, '/[id]'),
16
+ CREATE: createRoute(USERS_BASE, '/create'),
17
+ EDIT: createRoute(USERS_BASE, '/[id]/edit'),
18
+ SETTINGS: createRoute(USERS_BASE, '/[id]/settings'),
19
+ PROFILE: createRoute(USERS_BASE, '/profile'),
20
+ };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * 라우트 경로 관련 유틸리티 함수
3
+ */
4
+ /**
5
+ * 동적 라우트 파라미터를 포함한 경로를 생성하는 헬퍼 함수
6
+ * @param path - 기본 경로 (예: '/users/[id]')
7
+ * @param params - 파라미터 객체 (예: { id: '123' })
8
+ * @returns 실제 경로 (예: '/users/123')
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const userPath = withParam('/users/[id]', { id: '123' });
13
+ * // '/users/123'
14
+ * ```
15
+ */
16
+ export declare function withParam<T extends string>(path: T, params: Record<string, string | number>): string;
17
+ /**
18
+ * 쿼리 파라미터를 포함한 경로를 생성하는 헬퍼 함수
19
+ * @param path - 기본 경로
20
+ * @param query - 쿼리 파라미터 객체
21
+ * @returns 쿼리 파라미터가 포함된 경로
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const searchPath = withQuery('/search', { q: 'test', page: 1 });
26
+ * // '/search?q=test&page=1'
27
+ * ```
28
+ */
29
+ export declare function withQuery(path: string, query: Record<string, string | number | boolean | null | undefined>): string;
30
+ /**
31
+ * 라우트 경로를 생성하는 헬퍼 함수
32
+ * @param base - 기본 경로
33
+ * @param path - 추가 경로
34
+ * @returns 합쳐진 경로
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * const route = createRoute('/auth', '/login');
39
+ * // '/auth/login'
40
+ * ```
41
+ */
42
+ export declare function createRoute(base: string, path: string): string;
43
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/routes/utils/helpers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,EACxC,IAAI,EAAE,CAAC,EACP,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GACtC,MAAM,CAMR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,GAClE,MAAM,CASR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9D"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * 라우트 경로 관련 유틸리티 함수
3
+ */
4
+ /**
5
+ * 동적 라우트 파라미터를 포함한 경로를 생성하는 헬퍼 함수
6
+ * @param path - 기본 경로 (예: '/users/[id]')
7
+ * @param params - 파라미터 객체 (예: { id: '123' })
8
+ * @returns 실제 경로 (예: '/users/123')
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const userPath = withParam('/users/[id]', { id: '123' });
13
+ * // '/users/123'
14
+ * ```
15
+ */
16
+ export function withParam(path, params) {
17
+ let result = path;
18
+ for (const [key, value] of Object.entries(params)) {
19
+ result = result.replace(`[${key}]`, String(value));
20
+ }
21
+ return result;
22
+ }
23
+ /**
24
+ * 쿼리 파라미터를 포함한 경로를 생성하는 헬퍼 함수
25
+ * @param path - 기본 경로
26
+ * @param query - 쿼리 파라미터 객체
27
+ * @returns 쿼리 파라미터가 포함된 경로
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const searchPath = withQuery('/search', { q: 'test', page: 1 });
32
+ * // '/search?q=test&page=1'
33
+ * ```
34
+ */
35
+ export function withQuery(path, query) {
36
+ const searchParams = new URLSearchParams();
37
+ for (const [key, value] of Object.entries(query)) {
38
+ if (value !== null && value !== undefined) {
39
+ searchParams.append(key, String(value));
40
+ }
41
+ }
42
+ const queryString = searchParams.toString();
43
+ return queryString ? `${path}?${queryString}` : path;
44
+ }
45
+ /**
46
+ * 라우트 경로를 생성하는 헬퍼 함수
47
+ * @param base - 기본 경로
48
+ * @param path - 추가 경로
49
+ * @returns 합쳐진 경로
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * const route = createRoute('/auth', '/login');
54
+ * // '/auth/login'
55
+ * ```
56
+ */
57
+ export function createRoute(base, path) {
58
+ return `${base}${path.startsWith('/') ? path : `/${path}`}`;
59
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=helpers.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.test.d.ts","sourceRoot":"","sources":["../../../src/routes/utils/helpers.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,106 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { withParam, withQuery, createRoute } from './helpers';
3
+ describe('routes/utils/helpers', () => {
4
+ describe('withParam', () => {
5
+ it('should replace single parameter', () => {
6
+ expect(withParam('/users/[id]', { id: '123' })).toBe('/users/123');
7
+ expect(withParam('/posts/[postId]', { postId: '456' })).toBe('/posts/456');
8
+ });
9
+ it('should replace multiple parameters', () => {
10
+ expect(withParam('/posts/[postId]/comments/[commentId]', {
11
+ postId: '456',
12
+ commentId: '789',
13
+ })).toBe('/posts/456/comments/789');
14
+ expect(withParam('/users/[userId]/posts/[postId]/comments/[commentId]', {
15
+ userId: '1',
16
+ postId: '2',
17
+ commentId: '3',
18
+ })).toBe('/users/1/posts/2/comments/3');
19
+ });
20
+ it('should handle parameters in any order', () => {
21
+ const path = '/posts/[postId]/comments/[commentId]';
22
+ // 순서대로
23
+ expect(withParam(path, { postId: '456', commentId: '789' })).toBe('/posts/456/comments/789');
24
+ // 반대 순서
25
+ expect(withParam(path, { commentId: '789', postId: '456' })).toBe('/posts/456/comments/789');
26
+ });
27
+ it('should handle nested key names safely', () => {
28
+ // id가 postId의 부분 문자열이지만 정확한 일치만 바꿈
29
+ expect(withParam('/posts/[id]/comments/[postId]', { id: '123', postId: '456' })).toBe('/posts/123/comments/456');
30
+ expect(withParam('/posts/[id]/comments/[postId]', { postId: '456', id: '123' })).toBe('/posts/123/comments/456');
31
+ });
32
+ it('should replace only first occurrence (using replace, not replaceAll)', () => {
33
+ // 현재 구현은 replace()를 사용하므로 첫 번째 매칭만 바뀝니다
34
+ expect(withParam('/[id]/[id]/[id]', { id: '123' })).toBe('/123/[id]/[id]');
35
+ });
36
+ it('should handle numeric values', () => {
37
+ expect(withParam('/users/[id]', { id: 123 })).toBe('/users/123');
38
+ expect(withParam('/posts/[postId]/comments/[commentId]', {
39
+ postId: 1,
40
+ commentId: 2,
41
+ })).toBe('/posts/1/comments/2');
42
+ });
43
+ it('should leave unmatched parameters as is', () => {
44
+ expect(withParam('/users/[id]', { userId: '123' })).toBe('/users/[id]');
45
+ expect(withParam('/posts/[postId]/comments/[commentId]', { id: '123' })).toBe('/posts/[postId]/comments/[commentId]');
46
+ });
47
+ it('should handle values with brackets correctly', () => {
48
+ // 값에 [id]가 있어도 이미 처리된 패턴은 다시 바뀌지 않음
49
+ expect(withParam('/[placeholder]/[actual]', { placeholder: 'fixed', actual: '[id]' })).toBe('/fixed/[id]');
50
+ });
51
+ });
52
+ describe('withQuery', () => {
53
+ it('should add single query parameter', () => {
54
+ expect(withQuery('/search', { q: 'test' })).toBe('/search?q=test');
55
+ expect(withQuery('/users', { page: 1 })).toBe('/users?page=1');
56
+ });
57
+ it('should add multiple query parameters', () => {
58
+ expect(withQuery('/search', { q: 'test', page: 1 })).toBe('/search?q=test&page=1');
59
+ expect(withQuery('/filter', { category: 'tech', sort: 'asc', limit: 10 })).toBe('/filter?category=tech&sort=asc&limit=10');
60
+ });
61
+ it('should handle numeric values', () => {
62
+ expect(withQuery('/users', { page: 1, limit: 20 })).toBe('/users?page=1&limit=20');
63
+ });
64
+ it('should handle boolean values', () => {
65
+ expect(withQuery('/posts', { featured: true, archived: false })).toBe('/posts?featured=true&archived=false');
66
+ });
67
+ it('should filter out null and undefined values', () => {
68
+ expect(withQuery('/search', { q: 'test', page: null, limit: undefined })).toBe('/search?q=test');
69
+ expect(withQuery('/users', { id: undefined })).toBe('/users');
70
+ });
71
+ it('should handle empty object', () => {
72
+ expect(withQuery('/users', {})).toBe('/users');
73
+ });
74
+ it('should append query string (does not merge with existing query)', () => {
75
+ // 현재 구현은 기존 쿼리 스트링을 병합하지 않고 뒤에 붙입니다
76
+ expect(withQuery('/search?existing=1', { new: 'value' })).toBe('/search?existing=1?new=value');
77
+ });
78
+ it('should combine withParam and withQuery', () => {
79
+ const path = withQuery(withParam('/users/[id]', { id: '123' }), { tab: 'profile' });
80
+ expect(path).toBe('/users/123?tab=profile');
81
+ const path2 = withQuery(withParam('/posts/[postId]/comments/[commentId]', {
82
+ postId: '456',
83
+ commentId: '789',
84
+ }), { sort: 'asc', page: 1 });
85
+ expect(path2).toBe('/posts/456/comments/789?sort=asc&page=1');
86
+ });
87
+ });
88
+ describe('createRoute', () => {
89
+ it('should combine base and path', () => {
90
+ expect(createRoute('/auth', '/login')).toBe('/auth/login');
91
+ expect(createRoute('/users', '/create')).toBe('/users/create');
92
+ });
93
+ it('should handle path without leading slash', () => {
94
+ expect(createRoute('/auth', 'login')).toBe('/auth/login');
95
+ expect(createRoute('/users', 'create')).toBe('/users/create');
96
+ });
97
+ it('should handle base with trailing slash', () => {
98
+ expect(createRoute('/auth/', '/login')).toBe('/auth//login');
99
+ expect(createRoute('/auth/', 'login')).toBe('/auth//login');
100
+ });
101
+ it('should handle multiple levels', () => {
102
+ expect(createRoute('/api/v1', '/users')).toBe('/api/v1/users');
103
+ expect(createRoute('/admin', '/settings/profile')).toBe('/admin/settings/profile');
104
+ });
105
+ });
106
+ });
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@blastlabs/utils",
3
- "version": "1.20.0",
3
+ "version": "1.22.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "bin": {
8
8
  "blastlabs-generate-entity": "./bin/generate-entity.cjs",
9
- "blastlabs-init-ai-rules": "./bin/init-ai-rules.cjs"
9
+ "blastlabs-init-ai-rules": "./bin/init-ai-rules.cjs",
10
+ "blastlabs-init-routes": "./bin/init-routes.cjs"
10
11
  },
11
12
  "files": [
12
13
  "dist",