@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/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
- export { HttpError, badRequest, conflict, created, forbidden, handler, noContent, notFound, ok, safely, serverError, unauthorized };
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
- export { HttpError, badRequest, conflict, created, forbidden, handler, noContent, notFound, ok, safely, serverError, unauthorized };
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 u=Object.defineProperty;var q=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var P=Object.prototype.hasOwnProperty;var y=(e,r)=>{for(var o in r)u(e,o,{get:r[o],enumerable:!0})},T=(e,r,o,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of w(r))!P.call(e,s)&&s!==o&&u(e,s,{get:()=>r[s],enumerable:!(n=q(r,s))||n.enumerable});return e};var E=e=>T(u({},"__esModule",{value:!0}),e);var b={};y(b,{HttpError:()=>t,badRequest:()=>c,conflict:()=>x,created:()=>i,forbidden:()=>f,handler:()=>m,noContent:()=>p,notFound:()=>R,ok:()=>a,safely:()=>h,serverError:()=>l,unauthorized:()=>d});module.exports=E(b);var t=class extends Error{constructor(o,n){super(`HTTP ${o}`);this.status=o;this.body=n;this.name="HttpError"}};function a(e,r=200){return Response.json(e,{status:r})}function i(e){return Response.json(e,{status:201})}function p(){return new Response(null,{status:204})}function c(e="Bad Request",r){throw new t(400,{error:e,...r?{errors:r}:{}})}function d(e="Unauthorized"){throw new t(401,{error:e})}function f(e="Forbidden"){throw new t(403,{error:e})}function R(e="Not Found"){throw new t(404,{error:e})}function x(e="Conflict"){throw new t(409,{error:e})}function l(e="Internal Server Error"){throw new t(500,{error:e})}function m(e){return async(r,o)=>{try{return await e(r,o)}catch(n){return n instanceof t?Response.json(n.body,{status:n.status}):(console.error("[handler] Unhandled error:",n),Response.json({error:"Internal Server Error"},{status:500}))}}}async function h(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))}}}0&&(module.exports={HttpError,badRequest,conflict,created,forbidden,handler,noContent,notFound,ok,safely,serverError,unauthorized});
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(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&#0?39;/g,"'").replace(/&#x27;/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 t=class extends Error{constructor(n,o){super(`HTTP ${n}`);this.status=n;this.body=o;this.name="HttpError"}};function s(e,r=200){return Response.json(e,{status:r})}function u(e){return Response.json(e,{status:201})}function a(){return new Response(null,{status:204})}function i(e="Bad Request",r){throw new t(400,{error:e,...r?{errors:r}:{}})}function p(e="Unauthorized"){throw new t(401,{error:e})}function c(e="Forbidden"){throw new t(403,{error:e})}function d(e="Not Found"){throw new t(404,{error:e})}function f(e="Conflict"){throw new t(409,{error:e})}function R(e="Internal Server Error"){throw new t(500,{error:e})}function x(e){return async(r,n)=>{try{return await e(r,n)}catch(o){return o instanceof t?Response.json(o.body,{status:o.status}):(console.error("[handler] Unhandled error:",o),Response.json({error:"Internal Server Error"},{status:500}))}}}async function l(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))}}}export{t as HttpError,i as badRequest,f as conflict,u as created,c as forbidden,x as handler,a as noContent,d as notFound,s as ok,l as safely,R as serverError,p as unauthorized};
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(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&#0?39;/g,"'").replace(/&#x27;/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.26",
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",