@wheeparam/library 0.0.1

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.
@@ -0,0 +1,179 @@
1
+ import { AxiosInstance } from 'axios';
2
+
3
+ /**
4
+ * @packageDocumentation
5
+ * Axios Client (Client-only)
6
+ *
7
+ * 공통 요구사항:
8
+ * - accessToken이 있으면 Authorization 헤더에 첨부
9
+ * - 401 발생 시 refreshToken으로 토큰 재발급을 시도하고, 원래 요청을 재시도
10
+ * - refresh 중복 호출 방지(큐 방식)
11
+ *
12
+ * 단순화 정책:
13
+ * - refresh URL만 옵션으로 받아 내부에서 fetch로 토큰 재발급을 수행한다.
14
+ * - 토큰 저장은 "localStorage" 또는 "cookie" 2가지 모드만 지원한다.
15
+ *
16
+ * ⚠️ 이 모듈은 브라우저 전용이다(window/localStorage 사용 가능).
17
+ * Next.js에서는 반드시 Client Component 또는 클라이언트 전용 모듈에서 import 해야 한다.
18
+ */
19
+
20
+ /**
21
+ * 토큰 저장 방식
22
+ * - localStorage: accessToken/refreshToken을 localStorage에 저장
23
+ * - cookie: 토큰을 쿠키 기반으로 운용(클라 저장 최소화)
24
+ */
25
+ type TokenStorageMode = "localStorage" | "cookie";
26
+ /**
27
+ * refresh 요청/응답 타입
28
+ */
29
+ type RefreshRequestBody = {
30
+ refreshToken: string;
31
+ };
32
+ type RefreshResponseBody = {
33
+ accessToken: string;
34
+ refreshToken: string;
35
+ };
36
+ /**
37
+ * createAxiosClient 옵션
38
+ */
39
+ type CreateAxiosClientOptions = {
40
+ /**
41
+ * axios baseURL (선택)
42
+ */
43
+ baseURL?: string;
44
+ /**
45
+ * 요청 타임아웃(ms)
46
+ * @default 10000
47
+ */
48
+ timeout?: number;
49
+ /**
50
+ * 쿠키 전송 여부
51
+ * - cookie 모드에서는 거의 필수
52
+ * @default true
53
+ */
54
+ withCredentials?: boolean;
55
+ /**
56
+ * 토큰 재발급(Refresh) API URL
57
+ * @example "/api/authorize/token"
58
+ */
59
+ refreshUrl: string;
60
+ /**
61
+ * 토큰 저장 방식
62
+ * @default "localStorage"
63
+ */
64
+ storage?: TokenStorageMode;
65
+ /**
66
+ * localStorage 키 이름(선택)
67
+ * @default { accessTokenKey: "accessToken", refreshTokenKey: "refreshToken" }
68
+ */
69
+ storageKeys?: {
70
+ accessTokenKey?: string;
71
+ refreshTokenKey?: string;
72
+ };
73
+ /**
74
+ * 로그인 상태 변화 콜백(선택)
75
+ * - 프로젝트의 auth context/ref/store 업데이트에 사용
76
+ */
77
+ onAuthStateChange?: (status: "authenticated" | "unauthenticated") => void;
78
+ /**
79
+ * 일반 에러 표시 콜백(선택)
80
+ * - Swal/toast 등 프로젝트 스타일로 구현
81
+ */
82
+ onError?: (message: string, error: unknown) => void;
83
+ /**
84
+ * API 에러 메시지 추출 커스터마이즈(선택)
85
+ * - null 반환 시 내부 기본 로직 사용
86
+ */
87
+ getErrorMessage?: (error: unknown) => string | null;
88
+ };
89
+ /**
90
+ * createAxiosClient 반환값
91
+ */
92
+ type AxiosClient = {
93
+ /**
94
+ * 구성 완료된 axios instance
95
+ */
96
+ axios: AxiosInstance;
97
+ /**
98
+ * 토큰 설정(로그인 성공 등)
99
+ * - localStorage 모드: localStorage에 저장
100
+ * - cookie 모드: 기본 구현은 refreshToken만 저장(필요 시)
101
+ */
102
+ setToken: (data: Partial<RefreshResponseBody>) => void;
103
+ /**
104
+ * 토큰 조회
105
+ */
106
+ getToken: () => {
107
+ accessToken: string | null;
108
+ refreshToken: string | null;
109
+ };
110
+ /**
111
+ * 토큰 제거(로그아웃)
112
+ */
113
+ clearToken: () => void;
114
+ };
115
+ /**
116
+ * 공용 axios 클라이언트를 생성한다.
117
+ *
118
+ * 토큰 자동 첨부 + 401 발생 시 refresh 후 재요청을 처리한다.
119
+ *
120
+ * @example 기본 사용법 (Next.js Client Component)
121
+ * ```ts
122
+ * "use client";
123
+ *
124
+ * import Swal from "sweetalert2";
125
+ * import nl2br from "@/utils/common";
126
+ * import { authContextRef } from "@/components/providers/auth-provider";
127
+ * import { createAxiosClient } from "@your-scope/toolkit/client";
128
+ *
129
+ * export const { axios, setToken, clearToken, getToken } = createAxiosClient({
130
+ * refreshUrl: "/api/authorize/token",
131
+ * storage: "localStorage",
132
+ *
133
+ * onAuthStateChange: (status) => {
134
+ * authContextRef.setLoginStatus?.(status);
135
+ * },
136
+ *
137
+ * onError: (message) => {
138
+ * Swal.fire({
139
+ * title: "오류 발생",
140
+ * html: nl2br(message),
141
+ * backdrop: false,
142
+ * confirmButtonText: "확인",
143
+ * });
144
+ * },
145
+ * });
146
+ *
147
+ * export default axios;
148
+ * ```
149
+ *
150
+ * @example 로그인 성공 후 토큰 저장
151
+ * ```ts
152
+ * import { setToken } from "@/lib/axios";
153
+ *
154
+ * async function login(id: string, pw: string) {
155
+ * const res = await fetch("/api/login", {
156
+ * method: "POST",
157
+ * body: JSON.stringify({ id, pw })
158
+ * }).then(r => r.json());
159
+ *
160
+ * setToken({
161
+ * accessToken: res.accessToken,
162
+ * refreshToken: res.refreshToken
163
+ * });
164
+ * }
165
+ * ```
166
+ *
167
+ * @example 로그아웃 처리
168
+ * ```ts
169
+ * import { clearToken } from "@/lib/axios";
170
+ *
171
+ * function logout() {
172
+ * clearToken();
173
+ * location.href = "/login";
174
+ * }
175
+ * ```
176
+ */
177
+ declare function createAxiosClient(options: CreateAxiosClientOptions): AxiosClient;
178
+
179
+ export { type AxiosClient, type CreateAxiosClientOptions, type RefreshRequestBody, type RefreshResponseBody, type TokenStorageMode, createAxiosClient };
@@ -0,0 +1,179 @@
1
+ import { AxiosInstance } from 'axios';
2
+
3
+ /**
4
+ * @packageDocumentation
5
+ * Axios Client (Client-only)
6
+ *
7
+ * 공통 요구사항:
8
+ * - accessToken이 있으면 Authorization 헤더에 첨부
9
+ * - 401 발생 시 refreshToken으로 토큰 재발급을 시도하고, 원래 요청을 재시도
10
+ * - refresh 중복 호출 방지(큐 방식)
11
+ *
12
+ * 단순화 정책:
13
+ * - refresh URL만 옵션으로 받아 내부에서 fetch로 토큰 재발급을 수행한다.
14
+ * - 토큰 저장은 "localStorage" 또는 "cookie" 2가지 모드만 지원한다.
15
+ *
16
+ * ⚠️ 이 모듈은 브라우저 전용이다(window/localStorage 사용 가능).
17
+ * Next.js에서는 반드시 Client Component 또는 클라이언트 전용 모듈에서 import 해야 한다.
18
+ */
19
+
20
+ /**
21
+ * 토큰 저장 방식
22
+ * - localStorage: accessToken/refreshToken을 localStorage에 저장
23
+ * - cookie: 토큰을 쿠키 기반으로 운용(클라 저장 최소화)
24
+ */
25
+ type TokenStorageMode = "localStorage" | "cookie";
26
+ /**
27
+ * refresh 요청/응답 타입
28
+ */
29
+ type RefreshRequestBody = {
30
+ refreshToken: string;
31
+ };
32
+ type RefreshResponseBody = {
33
+ accessToken: string;
34
+ refreshToken: string;
35
+ };
36
+ /**
37
+ * createAxiosClient 옵션
38
+ */
39
+ type CreateAxiosClientOptions = {
40
+ /**
41
+ * axios baseURL (선택)
42
+ */
43
+ baseURL?: string;
44
+ /**
45
+ * 요청 타임아웃(ms)
46
+ * @default 10000
47
+ */
48
+ timeout?: number;
49
+ /**
50
+ * 쿠키 전송 여부
51
+ * - cookie 모드에서는 거의 필수
52
+ * @default true
53
+ */
54
+ withCredentials?: boolean;
55
+ /**
56
+ * 토큰 재발급(Refresh) API URL
57
+ * @example "/api/authorize/token"
58
+ */
59
+ refreshUrl: string;
60
+ /**
61
+ * 토큰 저장 방식
62
+ * @default "localStorage"
63
+ */
64
+ storage?: TokenStorageMode;
65
+ /**
66
+ * localStorage 키 이름(선택)
67
+ * @default { accessTokenKey: "accessToken", refreshTokenKey: "refreshToken" }
68
+ */
69
+ storageKeys?: {
70
+ accessTokenKey?: string;
71
+ refreshTokenKey?: string;
72
+ };
73
+ /**
74
+ * 로그인 상태 변화 콜백(선택)
75
+ * - 프로젝트의 auth context/ref/store 업데이트에 사용
76
+ */
77
+ onAuthStateChange?: (status: "authenticated" | "unauthenticated") => void;
78
+ /**
79
+ * 일반 에러 표시 콜백(선택)
80
+ * - Swal/toast 등 프로젝트 스타일로 구현
81
+ */
82
+ onError?: (message: string, error: unknown) => void;
83
+ /**
84
+ * API 에러 메시지 추출 커스터마이즈(선택)
85
+ * - null 반환 시 내부 기본 로직 사용
86
+ */
87
+ getErrorMessage?: (error: unknown) => string | null;
88
+ };
89
+ /**
90
+ * createAxiosClient 반환값
91
+ */
92
+ type AxiosClient = {
93
+ /**
94
+ * 구성 완료된 axios instance
95
+ */
96
+ axios: AxiosInstance;
97
+ /**
98
+ * 토큰 설정(로그인 성공 등)
99
+ * - localStorage 모드: localStorage에 저장
100
+ * - cookie 모드: 기본 구현은 refreshToken만 저장(필요 시)
101
+ */
102
+ setToken: (data: Partial<RefreshResponseBody>) => void;
103
+ /**
104
+ * 토큰 조회
105
+ */
106
+ getToken: () => {
107
+ accessToken: string | null;
108
+ refreshToken: string | null;
109
+ };
110
+ /**
111
+ * 토큰 제거(로그아웃)
112
+ */
113
+ clearToken: () => void;
114
+ };
115
+ /**
116
+ * 공용 axios 클라이언트를 생성한다.
117
+ *
118
+ * 토큰 자동 첨부 + 401 발생 시 refresh 후 재요청을 처리한다.
119
+ *
120
+ * @example 기본 사용법 (Next.js Client Component)
121
+ * ```ts
122
+ * "use client";
123
+ *
124
+ * import Swal from "sweetalert2";
125
+ * import nl2br from "@/utils/common";
126
+ * import { authContextRef } from "@/components/providers/auth-provider";
127
+ * import { createAxiosClient } from "@your-scope/toolkit/client";
128
+ *
129
+ * export const { axios, setToken, clearToken, getToken } = createAxiosClient({
130
+ * refreshUrl: "/api/authorize/token",
131
+ * storage: "localStorage",
132
+ *
133
+ * onAuthStateChange: (status) => {
134
+ * authContextRef.setLoginStatus?.(status);
135
+ * },
136
+ *
137
+ * onError: (message) => {
138
+ * Swal.fire({
139
+ * title: "오류 발생",
140
+ * html: nl2br(message),
141
+ * backdrop: false,
142
+ * confirmButtonText: "확인",
143
+ * });
144
+ * },
145
+ * });
146
+ *
147
+ * export default axios;
148
+ * ```
149
+ *
150
+ * @example 로그인 성공 후 토큰 저장
151
+ * ```ts
152
+ * import { setToken } from "@/lib/axios";
153
+ *
154
+ * async function login(id: string, pw: string) {
155
+ * const res = await fetch("/api/login", {
156
+ * method: "POST",
157
+ * body: JSON.stringify({ id, pw })
158
+ * }).then(r => r.json());
159
+ *
160
+ * setToken({
161
+ * accessToken: res.accessToken,
162
+ * refreshToken: res.refreshToken
163
+ * });
164
+ * }
165
+ * ```
166
+ *
167
+ * @example 로그아웃 처리
168
+ * ```ts
169
+ * import { clearToken } from "@/lib/axios";
170
+ *
171
+ * function logout() {
172
+ * clearToken();
173
+ * location.href = "/login";
174
+ * }
175
+ * ```
176
+ */
177
+ declare function createAxiosClient(options: CreateAxiosClientOptions): AxiosClient;
178
+
179
+ export { type AxiosClient, type CreateAxiosClientOptions, type RefreshRequestBody, type RefreshResponseBody, type TokenStorageMode, createAxiosClient };
@@ -0,0 +1,168 @@
1
+ // src/client/axios.ts
2
+ import axios from "axios";
3
+ function createLocalStorageTokenAccess(keys) {
4
+ const accessTokenKey = keys?.accessTokenKey ?? "accessToken";
5
+ const refreshTokenKey = keys?.refreshTokenKey ?? "refreshToken";
6
+ return {
7
+ get() {
8
+ return {
9
+ accessToken: window.localStorage.getItem(accessTokenKey),
10
+ refreshToken: window.localStorage.getItem(refreshTokenKey)
11
+ };
12
+ },
13
+ set(data) {
14
+ if (data.accessToken !== void 0) {
15
+ window.localStorage.setItem(accessTokenKey, data.accessToken ?? "");
16
+ }
17
+ if (data.refreshToken !== void 0) {
18
+ window.localStorage.setItem(refreshTokenKey, data.refreshToken ?? "");
19
+ }
20
+ },
21
+ clear() {
22
+ window.localStorage.removeItem(accessTokenKey);
23
+ window.localStorage.removeItem(refreshTokenKey);
24
+ }
25
+ };
26
+ }
27
+ function createCookieModeTokenAccess(keys) {
28
+ const refreshTokenKey = keys?.refreshTokenKey ?? "refreshToken";
29
+ return {
30
+ get() {
31
+ return {
32
+ accessToken: null,
33
+ // 쿠키 기반이면 JS에서 accessToken을 직접 읽지 않는 전제를 둔다.
34
+ refreshToken: window.localStorage.getItem(refreshTokenKey)
35
+ };
36
+ },
37
+ set(data) {
38
+ if (data.refreshToken !== void 0) {
39
+ window.localStorage.setItem(refreshTokenKey, data.refreshToken ?? "");
40
+ }
41
+ },
42
+ clear() {
43
+ window.localStorage.removeItem(refreshTokenKey);
44
+ }
45
+ };
46
+ }
47
+ function createAxiosClient(options) {
48
+ const {
49
+ baseURL,
50
+ timeout = 1e4,
51
+ withCredentials = true,
52
+ refreshUrl,
53
+ storage = "localStorage",
54
+ storageKeys,
55
+ onAuthStateChange,
56
+ onError,
57
+ getErrorMessage
58
+ } = options;
59
+ const instance = axios.create({ baseURL, timeout, withCredentials });
60
+ const tokenAccess = storage === "localStorage" ? createLocalStorageTokenAccess(storageKeys) : createCookieModeTokenAccess({ refreshTokenKey: storageKeys?.refreshTokenKey });
61
+ let isRefreshing = false;
62
+ let subscribers = [];
63
+ const notifySubscribers = (token) => {
64
+ subscribers.forEach((cb) => cb(token));
65
+ subscribers = [];
66
+ };
67
+ const clearAuth = () => {
68
+ tokenAccess.clear();
69
+ onAuthStateChange?.("unauthenticated");
70
+ };
71
+ const requestRefresh = async (refreshToken) => {
72
+ const res = await fetch(refreshUrl, {
73
+ method: "POST",
74
+ headers: { "Content-Type": "application/json" },
75
+ body: JSON.stringify({ refreshToken }),
76
+ credentials: withCredentials ? "include" : "same-origin"
77
+ });
78
+ if (!res.ok) {
79
+ throw new Error("Token refresh failed");
80
+ }
81
+ return await res.json();
82
+ };
83
+ instance.interceptors.request.use(
84
+ (config) => {
85
+ const { accessToken } = tokenAccess.get();
86
+ if (accessToken) {
87
+ config.headers = config.headers ?? {};
88
+ config.headers.Authorization = `Bearer ${accessToken}`;
89
+ }
90
+ return config;
91
+ },
92
+ (e) => Promise.reject(e)
93
+ );
94
+ instance.interceptors.response.use(
95
+ (res) => res,
96
+ async (error) => {
97
+ const response = error.response;
98
+ const status = response?.status;
99
+ const originalRequest = error.config ?? {};
100
+ if (status !== 401) {
101
+ let message = getErrorMessage?.(error) ?? response?.data?.error ?? "";
102
+ if (!message && typeof status === "number") {
103
+ switch (status) {
104
+ case 0:
105
+ message = "REST API \uC11C\uBC84\uC5D0 \uC811\uADFC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4\n\uC11C\uBC84 \uAD00\uB9AC\uC790\uC5D0\uAC8C \uBB38\uC758\uD558\uC138\uC694";
106
+ break;
107
+ case 400:
108
+ message = "\uC798\uBABB\uB41C \uC694\uCCAD\uC785\uB2C8\uB2E4.";
109
+ break;
110
+ case 404:
111
+ message = "[404] REST API \uC694\uCCAD\uC5D0 \uC2E4\uD328\uD558\uC600\uC2B5\uB2C8\uB2E4";
112
+ break;
113
+ case 500:
114
+ message = "\uC11C\uBC84\uC5D0\uC11C \uCC98\uB9AC \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD558\uC600\uC2B5\uB2C8\uB2E4.";
115
+ break;
116
+ }
117
+ }
118
+ if (message) onError?.(message, error);
119
+ return Promise.reject(error);
120
+ }
121
+ if (originalRequest._retry) {
122
+ clearAuth();
123
+ return Promise.reject(error);
124
+ }
125
+ originalRequest._retry = true;
126
+ const { refreshToken } = tokenAccess.get();
127
+ if (!refreshToken) {
128
+ clearAuth();
129
+ return Promise.reject(error);
130
+ }
131
+ if (isRefreshing) {
132
+ return new Promise((resolve, reject) => {
133
+ subscribers.push((newToken) => {
134
+ if (!newToken) return reject(error);
135
+ originalRequest.headers = originalRequest.headers ?? {};
136
+ originalRequest.headers.Authorization = `Bearer ${newToken}`;
137
+ resolve(instance(originalRequest));
138
+ });
139
+ });
140
+ }
141
+ isRefreshing = true;
142
+ try {
143
+ const newTokens = await requestRefresh(refreshToken);
144
+ tokenAccess.set(newTokens);
145
+ onAuthStateChange?.("authenticated");
146
+ isRefreshing = false;
147
+ notifySubscribers(newTokens.accessToken);
148
+ originalRequest.headers = originalRequest.headers ?? {};
149
+ originalRequest.headers.Authorization = `Bearer ${newTokens.accessToken}`;
150
+ return instance(originalRequest);
151
+ } catch (e) {
152
+ isRefreshing = false;
153
+ notifySubscribers(null);
154
+ clearAuth();
155
+ return Promise.reject(error);
156
+ }
157
+ }
158
+ );
159
+ return {
160
+ axios: instance,
161
+ setToken: (data) => tokenAccess.set(data),
162
+ getToken: () => tokenAccess.get(),
163
+ clearToken: () => clearAuth()
164
+ };
165
+ }
166
+ export {
167
+ createAxiosClient
168
+ };
package/dist/index.cjs ADDED
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ matchRoute: () => matchRoute,
24
+ routeResponse: () => routeResponse
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/route.ts
29
+ async function matchRoute(path, slug) {
30
+ const pathSegments = path.split("/").filter(Boolean);
31
+ const isDynamic = (segment) => segment.startsWith("[") && segment.endsWith("]");
32
+ if (pathSegments.length !== slug.length) return null;
33
+ const params = {};
34
+ for (let i = 0; i < pathSegments.length; i++) {
35
+ if (isDynamic(pathSegments[i])) {
36
+ const key = pathSegments[i].slice(1, -1);
37
+ params[key] = slug[i];
38
+ } else if (pathSegments[i] !== slug[i]) {
39
+ return null;
40
+ }
41
+ }
42
+ return { params };
43
+ }
44
+ async function routeResponse(slug, routes, request) {
45
+ for (const route of routes) {
46
+ const matched = await matchRoute(route.path, slug);
47
+ if (matched) {
48
+ return route.handler(request, matched.params);
49
+ }
50
+ }
51
+ return Response.json({ error: "Not Found" }, { status: 404 });
52
+ }
53
+ // Annotate the CommonJS export names for ESM import in node:
54
+ 0 && (module.exports = {
55
+ matchRoute,
56
+ routeResponse
57
+ });
@@ -0,0 +1,31 @@
1
+ type ApiParams = Record<string, string>;
2
+ type RouteHandler = (request: Request, params: ApiParams) => Promise<Response>;
3
+ type Routes = {
4
+ path: string;
5
+ handler: RouteHandler;
6
+ };
7
+ type ApiProps = {
8
+ params: Promise<{
9
+ slug?: string[];
10
+ }>;
11
+ };
12
+ type ListResponse<T> = {
13
+ result: T[];
14
+ totalCount: number;
15
+ };
16
+ type RowResponse<T> = {
17
+ result: T;
18
+ };
19
+ type ApiResult<T> = {
20
+ result: T;
21
+ };
22
+ type ApiErrorResponse = {
23
+ error: string;
24
+ };
25
+
26
+ declare function matchRoute(path: string, slug: string[]): Promise<{
27
+ params: Record<string, string>;
28
+ } | null>;
29
+ declare function routeResponse(slug: string[], routes: Routes[], request: Request): Promise<Response>;
30
+
31
+ export { type ApiErrorResponse, type ApiParams, type ApiProps, type ApiResult, type ListResponse, type RouteHandler, type Routes, type RowResponse, matchRoute, routeResponse };
@@ -0,0 +1,31 @@
1
+ type ApiParams = Record<string, string>;
2
+ type RouteHandler = (request: Request, params: ApiParams) => Promise<Response>;
3
+ type Routes = {
4
+ path: string;
5
+ handler: RouteHandler;
6
+ };
7
+ type ApiProps = {
8
+ params: Promise<{
9
+ slug?: string[];
10
+ }>;
11
+ };
12
+ type ListResponse<T> = {
13
+ result: T[];
14
+ totalCount: number;
15
+ };
16
+ type RowResponse<T> = {
17
+ result: T;
18
+ };
19
+ type ApiResult<T> = {
20
+ result: T;
21
+ };
22
+ type ApiErrorResponse = {
23
+ error: string;
24
+ };
25
+
26
+ declare function matchRoute(path: string, slug: string[]): Promise<{
27
+ params: Record<string, string>;
28
+ } | null>;
29
+ declare function routeResponse(slug: string[], routes: Routes[], request: Request): Promise<Response>;
30
+
31
+ export { type ApiErrorResponse, type ApiParams, type ApiProps, type ApiResult, type ListResponse, type RouteHandler, type Routes, type RowResponse, matchRoute, routeResponse };
package/dist/index.js ADDED
@@ -0,0 +1,29 @@
1
+ // src/route.ts
2
+ async function matchRoute(path, slug) {
3
+ const pathSegments = path.split("/").filter(Boolean);
4
+ const isDynamic = (segment) => segment.startsWith("[") && segment.endsWith("]");
5
+ if (pathSegments.length !== slug.length) return null;
6
+ const params = {};
7
+ for (let i = 0; i < pathSegments.length; i++) {
8
+ if (isDynamic(pathSegments[i])) {
9
+ const key = pathSegments[i].slice(1, -1);
10
+ params[key] = slug[i];
11
+ } else if (pathSegments[i] !== slug[i]) {
12
+ return null;
13
+ }
14
+ }
15
+ return { params };
16
+ }
17
+ async function routeResponse(slug, routes, request) {
18
+ for (const route of routes) {
19
+ const matched = await matchRoute(route.path, slug);
20
+ if (matched) {
21
+ return route.handler(request, matched.params);
22
+ }
23
+ }
24
+ return Response.json({ error: "Not Found" }, { status: 404 });
25
+ }
26
+ export {
27
+ matchRoute,
28
+ routeResponse
29
+ };