@m1kapp/kit 0.0.26 → 0.0.27
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/README.md +36 -0
- package/bin/stats.mjs +4 -5
- package/dist/index.d.mts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +4 -4
- package/dist/index.mjs +3 -3
- package/dist/meta.json +337 -133
- package/dist/server.d.mts +118 -1
- package/dist/server.d.ts +118 -1
- package/dist/server.js +1 -1
- package/dist/server.mjs +1 -1
- package/package.json +2 -1
package/dist/server.d.mts
CHANGED
|
@@ -87,4 +87,121 @@ type SafeResult<T> = SafeOk<T> | SafeErr;
|
|
|
87
87
|
*/
|
|
88
88
|
declare function safely<T>(fn: () => Promise<T>): Promise<SafeResult<T>>;
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Validate that required environment variables are present and non-empty.
|
|
92
|
+
* Returns them as a typed object; throws a 500 (via `serverError`) listing every
|
|
93
|
+
* missing key — so inside `handler()` it surfaces as a clean error response.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* const { XAI_API_KEY, DATABASE_URL } = requireEnv(["XAI_API_KEY", "DATABASE_URL"]);
|
|
97
|
+
* // → typed: { XAI_API_KEY: string; DATABASE_URL: string }
|
|
98
|
+
*/
|
|
99
|
+
declare function requireEnv<K extends string>(keys: readonly K[]): Record<K, string>;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Calendar date as "YYYY-MM-DD" for a given IANA time zone (default UTC).
|
|
103
|
+
* Uses Intl (zero deps), so it's correct across DST and offsets.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* dateInTz(new Date(), "Asia/Seoul") // "2026-06-04"
|
|
107
|
+
* dateInTz(someEpochMs, "America/New_York")
|
|
108
|
+
*/
|
|
109
|
+
declare function dateInTz(date?: Date | number | string, timeZone?: string): string;
|
|
110
|
+
/** Today's calendar date "YYYY-MM-DD" in KST (Asia/Seoul). */
|
|
111
|
+
declare function todayKST(date?: Date | number | string): string;
|
|
112
|
+
|
|
113
|
+
interface RetryOptions {
|
|
114
|
+
/** Extra attempts after the first (default 2 → up to 3 total) */
|
|
115
|
+
retries?: number;
|
|
116
|
+
/** Base backoff in ms (default 200) */
|
|
117
|
+
delayMs?: number;
|
|
118
|
+
/** Backoff multiplier per attempt (default 2) */
|
|
119
|
+
factor?: number;
|
|
120
|
+
/** Retry only when this returns true for the thrown error (default: always) */
|
|
121
|
+
shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Retry an async function with exponential backoff. Handy for serverless DB
|
|
125
|
+
* cold starts, flaky upstreams, etc.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* const rows = await withRetry(() => db.query.users.findMany(), {
|
|
129
|
+
* shouldRetry: (e) => String(e).includes("fetch failed"),
|
|
130
|
+
* });
|
|
131
|
+
*/
|
|
132
|
+
declare function withRetry<T>(fn: () => Promise<T>, opts?: RetryOptions): Promise<T>;
|
|
133
|
+
interface FetchRetryOptions extends RequestInit {
|
|
134
|
+
/** Extra attempts after the first (default 2) */
|
|
135
|
+
retries?: number;
|
|
136
|
+
/** Per-attempt timeout in ms (default 10000) */
|
|
137
|
+
timeoutMs?: number;
|
|
138
|
+
/** Base backoff in ms (default 300) */
|
|
139
|
+
retryDelayMs?: number;
|
|
140
|
+
/** Status codes that trigger a retry (default 429, 500, 502, 503, 504) */
|
|
141
|
+
retryStatuses?: number[];
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* `fetch` with a per-attempt timeout and automatic retry on network errors and
|
|
145
|
+
* retryable status codes (429/5xx). Returns the last Response even if retries
|
|
146
|
+
* are exhausted, so you can still inspect a final 429/503.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* const res = await fetchWithRetry("https://api.example.com/x", {
|
|
150
|
+
* headers: { authorization: `Bearer ${key}` }, retries: 3, timeoutMs: 8000,
|
|
151
|
+
* });
|
|
152
|
+
*/
|
|
153
|
+
declare function fetchWithRetry(url: string | URL, opts?: FetchRetryOptions): Promise<Response>;
|
|
154
|
+
interface OgData {
|
|
155
|
+
title?: string;
|
|
156
|
+
description?: string;
|
|
157
|
+
image?: string;
|
|
158
|
+
siteName?: string;
|
|
159
|
+
/** The URL that was actually fetched */
|
|
160
|
+
url: string;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Fetch a URL and extract Open Graph metadata (og:title/description/image, with
|
|
164
|
+
* twitter:* and plain-tag fallbacks). Resolves relative image URLs. Zero deps —
|
|
165
|
+
* lightweight regex parse, not a full DOM.
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* const og = await scrapeOg("example.com");
|
|
169
|
+
* // { title, description, image, siteName, url }
|
|
170
|
+
*/
|
|
171
|
+
declare function scrapeOg(target: string, opts?: {
|
|
172
|
+
timeoutMs?: number;
|
|
173
|
+
}): Promise<OgData>;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Best-effort parse of a JSON object/array embedded in noisy text — e.g. an LLM
|
|
177
|
+
* reply wrapped in prose or ```json fences. Strips code fences, slices to the
|
|
178
|
+
* outermost bracket pair, removes trailing commas and control chars. Throws if
|
|
179
|
+
* nothing parses.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* const data = recoverJsonFromText<{ items: string[] }>(llmReply);
|
|
183
|
+
*/
|
|
184
|
+
declare function recoverJsonFromText<T = unknown>(text: string): T;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Encode a non-negative integer id into a short base62 slug. `offset` lets you
|
|
188
|
+
* push tiny ids past short/ugly slugs (e.g. offset 1000 → first slug is "g8").
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* idToSlug(42) // "G"
|
|
192
|
+
* idToSlug(1, 1000) // "g9" (decode with the same offset)
|
|
193
|
+
*/
|
|
194
|
+
declare function idToSlug(id: number, offset?: number): string;
|
|
195
|
+
/** Inverse of `idToSlug` (use the same `offset`). */
|
|
196
|
+
declare function slugToId(slug: string, offset?: number): number;
|
|
197
|
+
/**
|
|
198
|
+
* Resolve the app host from env (default key `NEXT_PUBLIC_HOST`) with a fallback.
|
|
199
|
+
* Useful for building absolute URLs in API routes / OG images.
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* const base = `https://${appHost()}`; // NEXT_PUBLIC_HOST or localhost:3000
|
|
203
|
+
* const base = `https://${appHost("m1k.app")}`; // custom fallback
|
|
204
|
+
*/
|
|
205
|
+
declare function appHost(fallback?: string, envKey?: string): string;
|
|
206
|
+
|
|
207
|
+
export { type FetchRetryOptions, HttpError, type OgData, type RetryOptions, appHost, badRequest, conflict, created, dateInTz, fetchWithRetry, forbidden, handler, idToSlug, noContent, notFound, ok, recoverJsonFromText, requireEnv, safely, scrapeOg, serverError, slugToId, todayKST, unauthorized, withRetry };
|
package/dist/server.d.ts
CHANGED
|
@@ -87,4 +87,121 @@ type SafeResult<T> = SafeOk<T> | SafeErr;
|
|
|
87
87
|
*/
|
|
88
88
|
declare function safely<T>(fn: () => Promise<T>): Promise<SafeResult<T>>;
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Validate that required environment variables are present and non-empty.
|
|
92
|
+
* Returns them as a typed object; throws a 500 (via `serverError`) listing every
|
|
93
|
+
* missing key — so inside `handler()` it surfaces as a clean error response.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* const { XAI_API_KEY, DATABASE_URL } = requireEnv(["XAI_API_KEY", "DATABASE_URL"]);
|
|
97
|
+
* // → typed: { XAI_API_KEY: string; DATABASE_URL: string }
|
|
98
|
+
*/
|
|
99
|
+
declare function requireEnv<K extends string>(keys: readonly K[]): Record<K, string>;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Calendar date as "YYYY-MM-DD" for a given IANA time zone (default UTC).
|
|
103
|
+
* Uses Intl (zero deps), so it's correct across DST and offsets.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* dateInTz(new Date(), "Asia/Seoul") // "2026-06-04"
|
|
107
|
+
* dateInTz(someEpochMs, "America/New_York")
|
|
108
|
+
*/
|
|
109
|
+
declare function dateInTz(date?: Date | number | string, timeZone?: string): string;
|
|
110
|
+
/** Today's calendar date "YYYY-MM-DD" in KST (Asia/Seoul). */
|
|
111
|
+
declare function todayKST(date?: Date | number | string): string;
|
|
112
|
+
|
|
113
|
+
interface RetryOptions {
|
|
114
|
+
/** Extra attempts after the first (default 2 → up to 3 total) */
|
|
115
|
+
retries?: number;
|
|
116
|
+
/** Base backoff in ms (default 200) */
|
|
117
|
+
delayMs?: number;
|
|
118
|
+
/** Backoff multiplier per attempt (default 2) */
|
|
119
|
+
factor?: number;
|
|
120
|
+
/** Retry only when this returns true for the thrown error (default: always) */
|
|
121
|
+
shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Retry an async function with exponential backoff. Handy for serverless DB
|
|
125
|
+
* cold starts, flaky upstreams, etc.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* const rows = await withRetry(() => db.query.users.findMany(), {
|
|
129
|
+
* shouldRetry: (e) => String(e).includes("fetch failed"),
|
|
130
|
+
* });
|
|
131
|
+
*/
|
|
132
|
+
declare function withRetry<T>(fn: () => Promise<T>, opts?: RetryOptions): Promise<T>;
|
|
133
|
+
interface FetchRetryOptions extends RequestInit {
|
|
134
|
+
/** Extra attempts after the first (default 2) */
|
|
135
|
+
retries?: number;
|
|
136
|
+
/** Per-attempt timeout in ms (default 10000) */
|
|
137
|
+
timeoutMs?: number;
|
|
138
|
+
/** Base backoff in ms (default 300) */
|
|
139
|
+
retryDelayMs?: number;
|
|
140
|
+
/** Status codes that trigger a retry (default 429, 500, 502, 503, 504) */
|
|
141
|
+
retryStatuses?: number[];
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* `fetch` with a per-attempt timeout and automatic retry on network errors and
|
|
145
|
+
* retryable status codes (429/5xx). Returns the last Response even if retries
|
|
146
|
+
* are exhausted, so you can still inspect a final 429/503.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* const res = await fetchWithRetry("https://api.example.com/x", {
|
|
150
|
+
* headers: { authorization: `Bearer ${key}` }, retries: 3, timeoutMs: 8000,
|
|
151
|
+
* });
|
|
152
|
+
*/
|
|
153
|
+
declare function fetchWithRetry(url: string | URL, opts?: FetchRetryOptions): Promise<Response>;
|
|
154
|
+
interface OgData {
|
|
155
|
+
title?: string;
|
|
156
|
+
description?: string;
|
|
157
|
+
image?: string;
|
|
158
|
+
siteName?: string;
|
|
159
|
+
/** The URL that was actually fetched */
|
|
160
|
+
url: string;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Fetch a URL and extract Open Graph metadata (og:title/description/image, with
|
|
164
|
+
* twitter:* and plain-tag fallbacks). Resolves relative image URLs. Zero deps —
|
|
165
|
+
* lightweight regex parse, not a full DOM.
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* const og = await scrapeOg("example.com");
|
|
169
|
+
* // { title, description, image, siteName, url }
|
|
170
|
+
*/
|
|
171
|
+
declare function scrapeOg(target: string, opts?: {
|
|
172
|
+
timeoutMs?: number;
|
|
173
|
+
}): Promise<OgData>;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Best-effort parse of a JSON object/array embedded in noisy text — e.g. an LLM
|
|
177
|
+
* reply wrapped in prose or ```json fences. Strips code fences, slices to the
|
|
178
|
+
* outermost bracket pair, removes trailing commas and control chars. Throws if
|
|
179
|
+
* nothing parses.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* const data = recoverJsonFromText<{ items: string[] }>(llmReply);
|
|
183
|
+
*/
|
|
184
|
+
declare function recoverJsonFromText<T = unknown>(text: string): T;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Encode a non-negative integer id into a short base62 slug. `offset` lets you
|
|
188
|
+
* push tiny ids past short/ugly slugs (e.g. offset 1000 → first slug is "g8").
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* idToSlug(42) // "G"
|
|
192
|
+
* idToSlug(1, 1000) // "g9" (decode with the same offset)
|
|
193
|
+
*/
|
|
194
|
+
declare function idToSlug(id: number, offset?: number): string;
|
|
195
|
+
/** Inverse of `idToSlug` (use the same `offset`). */
|
|
196
|
+
declare function slugToId(slug: string, offset?: number): number;
|
|
197
|
+
/**
|
|
198
|
+
* Resolve the app host from env (default key `NEXT_PUBLIC_HOST`) with a fallback.
|
|
199
|
+
* Useful for building absolute URLs in API routes / OG images.
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* const base = `https://${appHost()}`; // NEXT_PUBLIC_HOST or localhost:3000
|
|
203
|
+
* const base = `https://${appHost("m1k.app")}`; // custom fallback
|
|
204
|
+
*/
|
|
205
|
+
declare function appHost(fallback?: string, envKey?: string): string;
|
|
206
|
+
|
|
207
|
+
export { type FetchRetryOptions, HttpError, type OgData, type RetryOptions, appHost, badRequest, conflict, created, dateInTz, fetchWithRetry, forbidden, handler, idToSlug, noContent, notFound, ok, recoverJsonFromText, requireEnv, safely, scrapeOg, serverError, slugToId, todayKST, unauthorized, withRetry };
|
package/dist/server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var m=Object.defineProperty;var H=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var j=(e,t)=>{for(var r in t)m(e,r,{get:t[r],enumerable:!0})},z=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of L(t))!U.call(e,s)&&s!==r&&m(e,s,{get:()=>t[s],enumerable:!(n=H(t,s))||n.enumerable});return e};var K=e=>z(m({},"__esModule",{value:!0}),e);var B={};j(B,{HttpError:()=>l,appHost:()=>N,badRequest:()=>b,conflict:()=>P,created:()=>y,dateInTz:()=>g,fetchWithRetry:()=>I,forbidden:()=>O,handler:()=>q,idToSlug:()=>A,noContent:()=>T,notFound:()=>v,ok:()=>R,recoverJsonFromText:()=>D,requireEnv:()=>k,safely:()=>S,scrapeOg:()=>M,serverError:()=>d,slugToId:()=>F,todayKST:()=>C,unauthorized:()=>E,withRetry:()=>h});module.exports=K(B);var l=class extends Error{constructor(r,n){super(`HTTP ${r}`);this.status=r;this.body=n;this.name="HttpError"}};function R(e,t=200){return Response.json(e,{status:t})}function y(e){return Response.json(e,{status:201})}function T(){return new Response(null,{status:204})}function b(e="Bad Request",t){throw new l(400,{error:e,...t?{errors:t}:{}})}function E(e="Unauthorized"){throw new l(401,{error:e})}function O(e="Forbidden"){throw new l(403,{error:e})}function v(e="Not Found"){throw new l(404,{error:e})}function P(e="Conflict"){throw new l(409,{error:e})}function d(e="Internal Server Error"){throw new l(500,{error:e})}function q(e){return async(t,r)=>{try{return await e(t,r)}catch(n){return n instanceof l?Response.json(n.body,{status:n.status}):(console.error("[handler] Unhandled error:",n),Response.json({error:"Internal Server Error"},{status:500}))}}}async function S(e){try{return{ok:!0,data:await e(),error:null}}catch(t){return{ok:!1,data:null,error:t instanceof Error?t:new Error(String(t))}}}function k(e){let t={},r=[];for(let n of e){let s=process.env[n];s==null||s===""?r.push(n):t[n]=s}return r.length&&d(`Missing required env: ${r.join(", ")}`),t}function g(e=new Date,t="UTC"){let r=e instanceof Date?e:new Date(e);return new Intl.DateTimeFormat("en-CA",{timeZone:t,year:"numeric",month:"2-digit",day:"2-digit"}).format(r)}function C(e=new Date){return g(e,"Asia/Seoul")}async function h(e,t={}){let{retries:r=2,delayMs:n=200,factor:s=2,shouldRetry:u}=t,i;for(let o=0;o<=r;o++)try{return await e()}catch(a){if(i=a,o===r||u&&!u(a,o))throw a;await new Promise(c=>setTimeout(c,n*Math.pow(s,o)))}throw i}var f=class extends Error{constructor(r){super(`HTTP ${r.status}`);this.response=r}};function J(e){return e instanceof Error?e.name==="AbortError"||/fetch failed|network|ECONN|ETIMEDOUT|EAI_AGAIN/i.test(e.message):!1}async function I(e,t={}){let{retries:r=2,timeoutMs:n=1e4,retryDelayMs:s=300,retryStatuses:u=[429,500,502,503,504],...i}=t;try{return await h(async()=>{let o=new AbortController,a=setTimeout(()=>o.abort(),n),c=()=>o.abort();i.signal&&(i.signal.aborted?o.abort():i.signal.addEventListener("abort",c,{once:!0}));try{let p=await fetch(e,{...i,signal:o.signal});if(u.includes(p.status))throw new f(p);return p}finally{clearTimeout(a),i.signal?.removeEventListener("abort",c)}},{retries:r,delayMs:s,shouldRetry:o=>o instanceof f||J(o)})}catch(o){if(o instanceof f)return o.response;throw o}}function x(e){return e&&e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/�?39;/g,"'").replace(/'/gi,"'")}async function M(e,t={}){let r=/^https?:\/\//i.test(e)?e:`https://${e}`,n=new AbortController,s=setTimeout(()=>n.abort(),t.timeoutMs??8e3),u="";try{let c=await fetch(r,{signal:n.signal,headers:{"user-agent":"Mozilla/5.0 (compatible; m1kapp-kit/scrapeOg)"}});if(!c.ok)throw c.body?.cancel().catch(()=>{}),new Error(`scrapeOg: ${c.status} ${c.statusText} for ${r}`);u=await c.text()}finally{clearTimeout(s)}let i=c=>{let p=new RegExp(`<meta[^>]+(?:property|name)=["']${c}["'][^>]*content=["']([^"']*)["']`,"i"),$=new RegExp(`<meta[^>]+content=["']([^"']*)["'][^>]*(?:property|name)=["']${c}["']`,"i");return(u.match(p)?.[1]??u.match($)?.[1])||void 0},o=u.match(/<title[^>]*>([^<]*)<\/title>/i)?.[1]?.trim(),a=i("og:image")??i("twitter:image");if(a&&!/^https?:\/\//i.test(a))try{a=new URL(a,r).href}catch{}return{title:x(i("og:title")??i("twitter:title")??o),description:x(i("og:description")??i("twitter:description")??i("description")),image:a,siteName:x(i("og:site_name")),url:r}}var _=/[\x00-\x08\x0B\x0C\x0E-\x1F]/g;function D(e){let t=a=>{try{return JSON.parse(a)}catch{return}},r=t(e.trim());if(r!==void 0)return r;let n=e.replace(/```(?:json)?/gi,"").trim(),s=[n.indexOf("{"),n.indexOf("[")].filter(a=>a>=0),u=s.length?Math.min(...s):-1,i=Math.max(n.lastIndexOf("}"),n.lastIndexOf("]"));u>=0&&i>u&&(n=n.slice(u,i+1)),n=n.replace(/,\s*([}\]])/g,"$1").replace(_,"");let o=t(n);if(o!==void 0)return o;throw new Error("recoverJsonFromText: no valid JSON found in text")}var w="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";function A(e,t=0){let r=e+t;if(!Number.isInteger(r)||r<0)throw new Error("idToSlug: id must be a non-negative integer");if(r===0)return w[0];let n="";for(;r>0;)n=w[r%62]+n,r=Math.floor(r/62);return n}function F(e,t=0){let r=0;for(let n of e){let s=w.indexOf(n);if(s<0)throw new Error(`slugToId: invalid character '${n}'`);r=r*62+s}return r-t}function N(e="localhost:3000",t="NEXT_PUBLIC_HOST"){return process.env[t]||e}0&&(module.exports={HttpError,appHost,badRequest,conflict,created,dateInTz,fetchWithRetry,forbidden,handler,idToSlug,noContent,notFound,ok,recoverJsonFromText,requireEnv,safely,scrapeOg,serverError,slugToId,todayKST,unauthorized,withRetry});
|
package/dist/server.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var l=class extends Error{constructor(t,n){super(`HTTP ${t}`);this.status=t;this.body=n;this.name="HttpError"}};function R(e,r=200){return Response.json(e,{status:r})}function y(e){return Response.json(e,{status:201})}function T(){return new Response(null,{status:204})}function b(e="Bad Request",r){throw new l(400,{error:e,...r?{errors:r}:{}})}function E(e="Unauthorized"){throw new l(401,{error:e})}function O(e="Forbidden"){throw new l(403,{error:e})}function v(e="Not Found"){throw new l(404,{error:e})}function P(e="Conflict"){throw new l(409,{error:e})}function d(e="Internal Server Error"){throw new l(500,{error:e})}function q(e){return async(r,t)=>{try{return await e(r,t)}catch(n){return n instanceof l?Response.json(n.body,{status:n.status}):(console.error("[handler] Unhandled error:",n),Response.json({error:"Internal Server Error"},{status:500}))}}}async function S(e){try{return{ok:!0,data:await e(),error:null}}catch(r){return{ok:!1,data:null,error:r instanceof Error?r:new Error(String(r))}}}function k(e){let r={},t=[];for(let n of e){let i=process.env[n];i==null||i===""?t.push(n):r[n]=i}return t.length&&d(`Missing required env: ${t.join(", ")}`),r}function x(e=new Date,r="UTC"){let t=e instanceof Date?e:new Date(e);return new Intl.DateTimeFormat("en-CA",{timeZone:r,year:"numeric",month:"2-digit",day:"2-digit"}).format(t)}function C(e=new Date){return x(e,"Asia/Seoul")}async function h(e,r={}){let{retries:t=2,delayMs:n=200,factor:i=2,shouldRetry:u}=r,s;for(let o=0;o<=t;o++)try{return await e()}catch(a){if(s=a,o===t||u&&!u(a,o))throw a;await new Promise(c=>setTimeout(c,n*Math.pow(i,o)))}throw s}var f=class extends Error{constructor(t){super(`HTTP ${t.status}`);this.response=t}};function I(e){return e instanceof Error?e.name==="AbortError"||/fetch failed|network|ECONN|ETIMEDOUT|EAI_AGAIN/i.test(e.message):!1}async function M(e,r={}){let{retries:t=2,timeoutMs:n=1e4,retryDelayMs:i=300,retryStatuses:u=[429,500,502,503,504],...s}=r;try{return await h(async()=>{let o=new AbortController,a=setTimeout(()=>o.abort(),n),c=()=>o.abort();s.signal&&(s.signal.aborted?o.abort():s.signal.addEventListener("abort",c,{once:!0}));try{let p=await fetch(e,{...s,signal:o.signal});if(u.includes(p.status))throw new f(p);return p}finally{clearTimeout(a),s.signal?.removeEventListener("abort",c)}},{retries:t,delayMs:i,shouldRetry:o=>o instanceof f||I(o)})}catch(o){if(o instanceof f)return o.response;throw o}}function m(e){return e&&e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/�?39;/g,"'").replace(/'/gi,"'")}async function D(e,r={}){let t=/^https?:\/\//i.test(e)?e:`https://${e}`,n=new AbortController,i=setTimeout(()=>n.abort(),r.timeoutMs??8e3),u="";try{let c=await fetch(t,{signal:n.signal,headers:{"user-agent":"Mozilla/5.0 (compatible; m1kapp-kit/scrapeOg)"}});if(!c.ok)throw c.body?.cancel().catch(()=>{}),new Error(`scrapeOg: ${c.status} ${c.statusText} for ${t}`);u=await c.text()}finally{clearTimeout(i)}let s=c=>{let p=new RegExp(`<meta[^>]+(?:property|name)=["']${c}["'][^>]*content=["']([^"']*)["']`,"i"),w=new RegExp(`<meta[^>]+content=["']([^"']*)["'][^>]*(?:property|name)=["']${c}["']`,"i");return(u.match(p)?.[1]??u.match(w)?.[1])||void 0},o=u.match(/<title[^>]*>([^<]*)<\/title>/i)?.[1]?.trim(),a=s("og:image")??s("twitter:image");if(a&&!/^https?:\/\//i.test(a))try{a=new URL(a,t).href}catch{}return{title:m(s("og:title")??s("twitter:title")??o),description:m(s("og:description")??s("twitter:description")??s("description")),image:a,siteName:m(s("og:site_name")),url:t}}var A=/[\x00-\x08\x0B\x0C\x0E-\x1F]/g;function F(e){let r=a=>{try{return JSON.parse(a)}catch{return}},t=r(e.trim());if(t!==void 0)return t;let n=e.replace(/```(?:json)?/gi,"").trim(),i=[n.indexOf("{"),n.indexOf("[")].filter(a=>a>=0),u=i.length?Math.min(...i):-1,s=Math.max(n.lastIndexOf("}"),n.lastIndexOf("]"));u>=0&&s>u&&(n=n.slice(u,s+1)),n=n.replace(/,\s*([}\]])/g,"$1").replace(A,"");let o=r(n);if(o!==void 0)return o;throw new Error("recoverJsonFromText: no valid JSON found in text")}var g="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";function N(e,r=0){let t=e+r;if(!Number.isInteger(t)||t<0)throw new Error("idToSlug: id must be a non-negative integer");if(t===0)return g[0];let n="";for(;t>0;)n=g[t%62]+n,t=Math.floor(t/62);return n}function $(e,r=0){let t=0;for(let n of e){let i=g.indexOf(n);if(i<0)throw new Error(`slugToId: invalid character '${n}'`);t=t*62+i}return t-r}function H(e="localhost:3000",r="NEXT_PUBLIC_HOST"){return process.env[r]||e}export{l as HttpError,H as appHost,b as badRequest,P as conflict,y as created,x as dateInTz,M as fetchWithRetry,O as forbidden,q as handler,N as idToSlug,T as noContent,v as notFound,R as ok,F as recoverJsonFromText,k as requireEnv,S as safely,D as scrapeOg,d as serverError,$ as slugToId,C as todayKST,E as unauthorized,h as withRetry};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@m1kapp/kit",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.27",
|
|
4
4
|
"description": "UI, SEO, and PWA utilities for side projects",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"postinstall": "node bin/postinstall.mjs",
|
|
56
|
+
"prepublishOnly": "npm run build",
|
|
56
57
|
"build": "rm -rf dist && mkdir -p .tmp && npx @tailwindcss/cli -i src/ui/styles.css -o .tmp/styles.css --minify && node scripts/embed-css.mjs && tsup && node scripts/gen-meta.mjs",
|
|
57
58
|
"dev": "tsup --watch",
|
|
58
59
|
"test": "vitest run",
|