@stacksjs/api 0.70.88 → 0.70.91
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/fetcher.d.ts +42 -0
- package/dist/generate-openapi.d.ts +31 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/resource.d.ts +144 -0
- package/dist/types.d.ts +34 -0
- package/package.json +2 -2
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { BodyData, FetcherResponse, QueryParams } from './types';
|
|
2
|
+
export declare const fetcher: Fetcher;
|
|
3
|
+
declare class FetcherResponseImpl<T> implements FetcherResponse<T> {
|
|
4
|
+
public data: T;
|
|
5
|
+
public status: number;
|
|
6
|
+
public headers: Headers;
|
|
7
|
+
public isOk: boolean;
|
|
8
|
+
constructor(data: T, status: number, headers: Headers, isOk: boolean);
|
|
9
|
+
ok(): boolean;
|
|
10
|
+
created(): boolean;
|
|
11
|
+
accepted(): boolean;
|
|
12
|
+
noContent(): boolean;
|
|
13
|
+
movedPermanently(): boolean;
|
|
14
|
+
found(): boolean;
|
|
15
|
+
badRequest(): boolean;
|
|
16
|
+
unauthorized(): boolean;
|
|
17
|
+
paymentRequired(): boolean;
|
|
18
|
+
forbidden(): boolean;
|
|
19
|
+
notFound(): boolean;
|
|
20
|
+
requestTimeout(): boolean;
|
|
21
|
+
conflict(): boolean;
|
|
22
|
+
unprocessableEntity(): boolean;
|
|
23
|
+
tooManyRequests(): boolean;
|
|
24
|
+
serverError(): boolean;
|
|
25
|
+
}
|
|
26
|
+
declare class Fetcher {
|
|
27
|
+
withQueryParams(params: QueryParams): this;
|
|
28
|
+
withHeaders(headers: Record<string, string>): this;
|
|
29
|
+
accept(contentType: string): this;
|
|
30
|
+
acceptJson(): this;
|
|
31
|
+
withToken(token: string): this;
|
|
32
|
+
withBasicAuth(username: string, password: string): this;
|
|
33
|
+
withDigestAuth(username: string, password: string): this;
|
|
34
|
+
withBody<D extends BodyData>(data: D): this;
|
|
35
|
+
asForm(): this;
|
|
36
|
+
attach(name: string, content: string | Blob | File, filename?: string, headers?: Record<string, string>): this;
|
|
37
|
+
get<T = string>(url: string): Promise<FetcherResponse<T>>;
|
|
38
|
+
post<T = string, D extends BodyData = BodyData>(url: string, body?: D): Promise<FetcherResponse<T>>;
|
|
39
|
+
put<T = string, D extends BodyData = BodyData>(url: string, body?: D): Promise<FetcherResponse<T>>;
|
|
40
|
+
patch<T = string, D extends BodyData = BodyData>(url: string, body?: D): Promise<FetcherResponse<T>>;
|
|
41
|
+
delete<T = string>(url: string): Promise<FetcherResponse<T>>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build an OpenAPI 3.0 spec from the live route registry.
|
|
3
|
+
*
|
|
4
|
+
* The previous shape called `route.routes()` (which doesn't exist —
|
|
5
|
+
* `routes` is a getter, not a callable) and read fields like
|
|
6
|
+
* `route.url`, `route.statusCode`, `route.responseSchema` that the
|
|
7
|
+
* underlying bun-router doesn't actually expose. This rewrite uses
|
|
8
|
+
* `listRegisteredRoutes()` (the canonical introspection helper) and
|
|
9
|
+
* derives request schemas from each action's `validations` block when
|
|
10
|
+
* available.
|
|
11
|
+
*
|
|
12
|
+
* Without `Action.validations`, the spec lists the path + parameters
|
|
13
|
+
* but omits a request body schema — that's still useful for clients
|
|
14
|
+
* that just want to know what endpoints exist.
|
|
15
|
+
*/
|
|
16
|
+
export declare function generateOpenApi(): Promise<OpenApiSpec>;
|
|
17
|
+
declare interface OpenApiPathItem {
|
|
18
|
+
[method: string]: {
|
|
19
|
+
summary?: string
|
|
20
|
+
operationId?: string
|
|
21
|
+
parameters?: Array<{ name: string, in: string, required: boolean, schema: { type: string } }>
|
|
22
|
+
responses: Record<string, { description: string, content?: Record<string, unknown> }>
|
|
23
|
+
requestBody?: { content: Record<string, unknown>, required?: boolean }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
declare interface OpenApiSpec {
|
|
27
|
+
openapi: string
|
|
28
|
+
info: { title: string, version: string }
|
|
29
|
+
paths: Record<string, OpenApiPathItem>
|
|
30
|
+
components: { schemas: Record<string, unknown> }
|
|
31
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var rZ=Object.defineProperty;var aZ=(Z)=>Z;function eZ(Z,$){this[Z]=aZ.bind(null,$)}var NZ=(Z,$)=>{for(var z in $)rZ(Z,z,{get:$[z],enumerable:!0,configurable:!0,set:eZ.bind($,z)})};var zZ=(Z,$)=>()=>(Z&&($=Z(Z=0)),$);var p=import.meta.require;var IZ;var WZ=zZ(()=>{IZ={api:"api",v1:{path:"v1",prefix:"v1"}}});var DZ={};NZ(DZ,{default:()=>IZ});var SZ=zZ(()=>{WZ();WZ()});function Z0(Z){let $=Z;return $?.code==="SQLITE_CONSTRAINT_UNIQUE"||$?.code==="SQLITE_CONSTRAINT"||$?.code==="23505"||$?.errno===1062||/unique|duplicate/i.test($?.message??"")}function XZ(Z,$,z){let G=Z;if(Z instanceof Error&&typeof G.status==="number"&&Number.isInteger(G.status)&&G.status>=400&&G.status<600){let J={error:Z.message};if(G.details!==void 0)J.details=G.details;return{status:G.status,body:J}}if(Z0(Z))return{status:409,body:{error:`${$} already exists`}};return{status:500,body:{error:`Failed to ${z} ${$}`,detail:String(Z)}}}function M(Z){return Z.replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/([A-Z])([A-Z][a-z])/g,"$1_$2").toLowerCase()}function YZ(Z){let $={};for(let[z,G]of Object.entries(Z))$[M(z)]=G;return $}function TZ(Z,$){if(!Z||$.length===0)return{};let z={};for(let G of $){if(G in Z){z[G]=Z[G];continue}let J=M(G);if(J!==G&&J in Z)z[G]=Z[J]}return z}function BZ(Z,$){if(!$.length)return Z;let z={...Z};for(let G of $)delete z[G],delete z[M(G)];return z}function o(Z,$){if(!Z||$.length===0)return Z;let z={...Z};for(let G of $)delete z[G],delete z[M(G)];return z}function VZ(Z,$){let z=new Map;for(let G of[...Object.keys(Z??{}),...$0]){let J=M(G);if(!/^\w+$/.test(J))continue;z.set(G,J),z.set(J,J)}for(let G of $)z.delete(G),z.delete(M(G));return z}function FZ(Z,$,z){if(!$)return Z;let G=String($).split(",").map((L)=>L.trim()).filter(Boolean),J=Z;for(let L of G){let K=L.startsWith("-"),X=K?L.slice(1):L;if(!/^\w+$/.test(X))continue;let B=z.get(X);if(!B)continue;J=J.orderBy(B,K?"desc":"asc")}return J}function G0(Z){try{return JSON.parse(Z)}catch{return Z}}function J0(Z){try{return JSON.parse(Z)}catch{return[]}}function xZ(Z,$,z){if(!Z||typeof Z!=="object"||!$||Object.keys($).length===0)return Z;let G={...Z};for(let[J,L]of Object.entries($)){let K=typeof L==="string"?z0[L]:L;if(!K||typeof K[z]!=="function")continue;if(Object.prototype.hasOwnProperty.call(G,J))G[J]=K[z](G[J]);let X=M(J);if(X!==J&&Object.prototype.hasOwnProperty.call(G,X))G[X]=K[z](G[X])}return G}function MZ(Z){let $=typeof Z==="object"&&Z!==null&&"middleware"in Z,z=Z?.middleware,G=Array.isArray(z)?z.filter((J)=>typeof J==="string"&&J.length>0):typeof z==="string"&&z?[z]:[];return{read:G,write:$?G:["auth"],declared:$}}function CZ(Z){let $=Number.parseInt(Z.get("page")||String(1),10),z=Number.isFinite($)?Math.max(1,$):1,G=Number.parseInt(Z.get("per_page")||String(jZ),10),J=Math.min(Number.isFinite(G)?Math.max(1,G):jZ,L0);return{page:z,perPage:J,offset:(z-1)*J}}function GZ(Z,$){let z=new URL(Z.toString());return z.searchParams.set("page",String($)),`${z.pathname}${z.search}`}function JZ(Z,$,z,G,J,L){let K=($-1)*z,X=G===0,B={page:$,per_page:z,from:X?null:K+1,to:X?null:K+G,has_more_pages:J,prev_page_url:$>1?GZ(Z,$-1):null,next_page_url:J?GZ(Z,$+1):null};if(L!==void 0&&!Number.isNaN(L)){let W=Math.max(1,Math.ceil(L/z));B.total=L,B.last_page=W,B.first_page_url=GZ(Z,1),B.last_page_url=GZ(Z,W)}return B}function HZ(Z,$,z,G,J,L){let{page:K,...X}=JZ(Z,$,z,G,J,L);return{current_page:K,...X}}var $0,z0,jZ=15,L0=100;var kZ=zZ(()=>{$0=["id","uuid","created_at","updated_at","deleted_at"];z0={string:{get:(Z)=>Z!=null?String(Z):null,set:(Z)=>Z!=null?String(Z):null},number:{get:(Z)=>Z!=null?Number(Z):null,set:(Z)=>Z!=null?Number(Z):null},integer:{get:(Z)=>Z!=null?Math.trunc(Number(Z)):null,set:(Z)=>Z!=null?Math.trunc(Number(Z)):null},float:{get:(Z)=>Z!=null?Number.parseFloat(String(Z)):null,set:(Z)=>Z!=null?Number.parseFloat(String(Z)):null},boolean:{get:(Z)=>Z===1||Z==="1"||Z===!0||Z==="true",set:(Z)=>Z===!0||Z===1||Z==="1"||Z==="true"?1:0},json:{get:(Z)=>Z==null?null:typeof Z==="string"?G0(Z):Z,set:(Z)=>Z==null?null:typeof Z==="string"?Z:JSON.stringify(Z)},datetime:{get:(Z)=>Z?new Date(Z):null,set:(Z)=>Z instanceof Date?Z.toISOString():Z},date:{get:(Z)=>Z?new Date(Z):null,set:(Z)=>Z instanceof Date?Z.toISOString().split("T")[0]:Z},array:{get:(Z)=>Z==null?[]:Array.isArray(Z)?Z:typeof Z==="string"?J0(Z):[],set:(Z)=>Z==null?null:Array.isArray(Z)?JSON.stringify(Z):Z}}});var uZ={};NZ(uZ,{default:()=>T0});import{route as c}from"@stacksjs/router";import{env as n}from"@stacksjs/env";import{projectPath as hZ}from"@stacksjs/path";import{createQueryBuilder as K0,defaultConfig as PZ,setConfig as RZ}from"@stacksjs/query-builder";import{HttpError as i}from"@stacksjs/error-handling";import{log as cZ}from"@stacksjs/logging";function l(Z,$){return c.routes.some((z)=>z.method===Z&&z.path===$)}function W0(Z){if(!Z.attributes)return[];return Object.entries(Z.attributes).filter(([$,z])=>z.fillable===!0).map(([$])=>$)}function mZ(Z){if(!Z.attributes)return[];return Object.entries(Z.attributes).filter(([$,z])=>z.hidden===!0).map(([$])=>$)}function fZ(Z,$,z){let G=Z,J=$,L=z,K=J?.attributes??{},X={};for(let[B,W]of Object.entries(K)){let I=W?.validation?.rule;if(!I||typeof I.validate!=="function")continue;let N=Object.prototype.hasOwnProperty.call(G,B);if(!N&&L==="updating")continue;let R=N?G[B]:void 0,D=I.validate(R);if(!D?.valid&&Array.isArray(D?.errors)&&D.errors.length>0)X[B]=D.errors.map((S)=>W?.validation?.message?.[S?.code]??S?.message??"invalid")}return Object.keys(X).length===0?{valid:!0}:{valid:!1,errors:X}}function qZ(Z){if(Z.endsWith("y")&&!/[aeiou]y$/i.test(Z))return`${Z.slice(0,-1)}ies`;if(/(?:s|x|z|ch|sh)$/i.test(Z))return`${Z}es`;return`${Z}s`}async function yZ(Z,$,z,G){if(!Z.length)return Z;let J=z.split(",").map((W)=>W.trim()).filter(Boolean);if(!J.length)return Z;let L=Array.isArray($.belongsTo)?$.belongsTo:[],K=Array.isArray($.hasMany)?$.hasMany:[],X=Array.isArray($.hasOne)?$.hasOne:[],B=new Map;for(let W of L)B.set(M(W),{kind:"belongsTo",modelName:W});for(let W of X)B.set(M(W),{kind:"hasOne",modelName:W});for(let W of K)B.set(qZ(M(W)),{kind:"hasMany",modelName:W});for(let W of J){let I=B.get(W);if(!I)continue;let N=G[I.modelName];if(!N)continue;let R=N.table||qZ(M(I.modelName)),D=mZ(N),S=(b)=>o(e(b,N),D);if(I.kind==="belongsTo"||I.kind==="hasOne"){let b=`${M(I.modelName)}_id`,q=[...new Set(Z.map((T)=>T[b]).filter((T)=>T!=null))];if(!q.length){for(let T of Z)T[W]=null;continue}let f=await P.selectFrom(R).whereIn("id",q).get(),w=new Map;for(let T of f??[])w.set(String(T.id),S(T));for(let T of Z)T[W]=w.get(String(T[b]))??null}else{let b=String($.name??""),q=`${M(b)}_id`,f=[...new Set(Z.map((Q)=>Q.id).filter((Q)=>Q!=null))];if(!f.length){for(let Q of Z)Q[W]=[];continue}let w=await P.selectFrom(R).whereIn(q,f).get(),T=new Map;for(let Q of w??[]){let Y=String(Q[q]),x=T.get(Y)??[];x.push(S(Q)),T.set(Y,x)}for(let Q of Z)Q[W]=T.get(String(Q.id))??[]}}return Z}async function gZ(Z,$){let z=$?.set;if(!z||typeof z!=="object")return Z;let G={...Z};for(let[J,L]of Object.entries(z)){if(typeof L!=="function"||!(J in G))continue;try{G[J]=await L(G)}catch{}}return G}function wZ(Z,$){return xZ(Z,$?.casts,"set")}function e(Z,$){return xZ(Z,$?.casts,"get")}function H(Z,$=200){return new Response(JSON.stringify(Z),{status:$,headers:{"Content-Type":"application/json"}})}async function OZ(Z){if(Number(Z.headers?.get?.("content-length")??0)>LZ)throw new i(413,`Request body exceeds ${LZ}-byte limit`);if(Z.jsonBody&&typeof Z.jsonBody==="object")return Z.jsonBody;if(Z.formBody&&typeof Z.formBody==="object")return Z.formBody;try{let z=await Z.clone().text();if(z.length>LZ)throw new i(413,`Request body exceeds ${LZ}-byte limit`);if(!z)return{};let G=JSON.parse(z);return G&&typeof G==="object"&&!Array.isArray(G)?G:{}}catch(z){if(z instanceof i)throw z;throw new i(400,`Invalid JSON body: ${z.message}`)}}function KZ(Z){if(Z==null)return null;if(typeof Z==="number")return Number.isFinite(Z)&&Z>0?Z:null;if(typeof Z==="string"){let $=Z.trim();if(!$)return null;if(/^\d+$/.test($)){let z=Number($);return Number.isFinite(z)&&z>0?z:null}return $}return null}function pZ(Z){let $=Z.bearerToken;if(typeof $==="function"){let G=$.call(Z);if(G)return G}let z=Z.headers?.get?.("authorization")||Z.headers?.get?.("Authorization")||"";if(typeof z==="string"&&z.startsWith("Bearer "))return z.substring(7);return null}async function s(Z){let $=Z._authenticatedUser;if($)return $;let z=pZ(Z);if(!z)return null;try{let{Auth:G}=await import("@stacksjs/auth"),J=await G.getUserFromToken(z);if(J)Z._authenticatedUser=J;return J||null}catch{return null}}function r(Z,$){if(Z==null||$==null)return!1;if(Array.isArray($))return $.some((z)=>String(z)===String(Z));return String(Z)===String($)}function X0(Z){let $=Z?.attributes||{},z=(G)=>Object.prototype.hasOwnProperty.call($,G);return z("teamId")||z("team_id")?"team_id":null}function Y0(Z){let $=pZ(Z),z=Z.headers?.get?.("cookie")||"";return{bearerToken:()=>$,cookies:{get:(G)=>{for(let J of z.split(";")){let L=J.indexOf("=");if(L===-1)continue;if(J.slice(0,L).trim()===G)return decodeURIComponent(J.slice(L+1).trim())}return null}}}}function iZ(Z){if(Z?.ownership)return Z.ownership;let $=X0(Z);if(!$)return null;return{field:$,resolve:async(z,G)=>{let{resolveAuthenticatedTeamId:J}=await import("@stacksjs/auth");return J(Y0(G))}}}async function a(Z,$,z){let G=iZ(Z);if(!G||!G.field||typeof G.resolve!=="function")return{enforced:!1,value:null,field:"",bypass:!1};let J=typeof G.bypass==="function"?!!G.bypass($,z):!1,L=null;try{L=await G.resolve($,z)}catch{L=null}return{enforced:!0,value:L,field:String(G.field),bypass:J}}async function vZ(Z,$,z,G){let J=Z?.authedFill?.[$];if(!J)return{};if(typeof J==="function")try{let L=await J(z,G);return L&&typeof L==="object"?L:{}}catch{return{}}if(typeof J==="object"){let L={};for(let[K,X]of Object.entries(J))try{L[K]=typeof X==="function"?await X(z,G):X}catch{}return L}return{}}var Q0,bZ,QZ,P,LZ=1048576,T0;var nZ=zZ(async()=>{kZ();Q0=hZ("config/qb.ts");try{let Z=(await import(Q0)).default;RZ(Z??PZ)}catch{cZ.debug("[orm] No config/qb.ts override found \u2014 deriving config from DB_CONNECTION");let Z=n.DB_CONNECTION||"sqlite";RZ({...PZ,dialect:Z,database:Z==="sqlite"?{database:n.DB_DATABASE_PATH||"database/stacks.sqlite"}:{database:n.DB_DATABASE||"stacks",host:n.DB_HOST||"127.0.0.1",port:n.DB_PORT||(Z==="postgres"?5432:3306),username:n.DB_USERNAME||(Z==="postgres"?"postgres":"root"),password:n.DB_PASSWORD||""}})}bZ=hZ("app/Models"),QZ={};try{let{readdirSync:Z,statSync:$}=await import("fs"),{extname:z,basename:G}=await import("path"),J=Z(bZ);for(let L of J){let K=`${bZ}/${L}`;if($(K).isDirectory())continue;let B=z(K);if(![".ts",".js"].includes(B))continue;try{let W=await import(`${K}?t=${Date.now()}`),I=W.default??W,N=I.name??G(L,B);QZ[N]={...I,name:N}}catch{}}}catch{}P=K0();for(let[Z,$]of Object.entries(QZ)){let z=$.traits?.useApi;if(!z)continue;let G=typeof z==="object"?z:{},J=G.uri||$.table||Z.toLowerCase()+"s",L=G.routes||["index","show","store","update","destroy"],K=$.table||J,X=W0($),B=mZ($),W=`/api/${J}`,I=VZ($.attributes,B),{read:N,write:R,declared:D}=MZ(z);if(["store","update","destroy"].some((T)=>L.includes(T))&&D&&R.length===0)cZ.warn(`[orm] ${Z}: registering UNAUTHENTICATED mutating routes at ${W} (explicit \`middleware: []\` opt-out)`);let b=!!iZ($),q=b&&!N.includes("auth")?["auth",...N]:N,f=(T,Q=q)=>{let Y=T;for(let x of Q)if(Y&&typeof Y.middleware==="function")Y=Y.middleware(x);return Y};if(L.includes("index")&&!l("GET",W))f(c.get(W,async(T)=>{try{let Q=new URL(T.url),{page:Y,perPage:x,offset:A}=CZ(Q.searchParams),U=Q.searchParams.get("sort"),O=P.selectFrom(K);O=FZ(O,U,I);let y=new Set(["page","per_page","sort","fields","search","include","limit","offset","with_count","withTrashed","onlyTrashed"]);for(let[C,k]of Q.searchParams.entries()){if(y.has(C)||!k)continue;if(!/^[a-z_][a-z0-9_]*$/i.test(C))continue;let u=I.get(C);if(u)O=O.where(u,"=",k)}let E=Q.searchParams.get("search");if(E&&X.length>0){let C=X.slice(0,5);O=O.where((k)=>{for(let u of C)k=k.orWhere(M(u),"like",`%${E}%`);return k})}let j=Q.searchParams.get("fields");if(j){let C=j.split(",").map((k)=>k.trim()).filter((k)=>/^[a-z_][a-z0-9_]*$/i.test(k)).map((k)=>M(k));if(C.length>0)O=O.select(C)}let V=!!$.traits?.useSoftDeletes,F=Q.searchParams.get("withTrashed")==="true",m=Q.searchParams.get("onlyTrashed")==="true";if(V&&!F)O=m?O.whereNotNull("deleted_at"):O.whereNull("deleted_at");let _=b?await a($,await s(T),T):{enforced:!1,value:null,field:"",bypass:!1};if(_.enforced&&!_.bypass){if(_.value==null||Array.isArray(_.value)&&_.value.length===0){let C=HZ(Q,Y,x,0,!1,void 0);return new Response(JSON.stringify({data:[],...C,meta:JZ(Q,Y,x,0,!1,void 0)}),{status:200,headers:{"Content-Type":"application/json","Cache-Control":"private, no-store",Vary:"Authorization"}})}O=Array.isArray(_.value)?O.where(_.field,"in",_.value):O.where(_.field,"=",_.value)}let v=await O.limit(x+1).offset(A).get()||[],h=v.length>x,t=(h?v.slice(0,x):v).map((C)=>o(e(C,$),B)),_Z=Q.searchParams.get("include");if(_Z)t=await yZ(t,$,_Z,QZ);let sZ=Q.searchParams.get("with_count")==="true",d;if(sZ)try{let C=P.selectFrom(K);if(_.enforced&&!_.bypass&&_.value!=null)C=Array.isArray(_.value)?C.where(_.field,"in",_.value):C.where(_.field,"=",_.value);let k=await C.count(),u=typeof k==="number"?k:Number(k?.count??k);if(Number.isFinite(u))d=u}catch{}let $Z={"Content-Type":"application/json"};if(d!==void 0&&!Number.isNaN(d))$Z["X-Total-Count"]=String(d);$Z["Cache-Control"]=_.enforced&&!_.bypass?"private, no-store":"public, max-age=15, must-revalidate",$Z.Vary="Authorization";let tZ=HZ(Q,Y,x,t.length,h,d);return new Response(JSON.stringify({data:t,...tZ,meta:JZ(Q,Y,x,t.length,h,d)}),{status:200,headers:$Z})}catch(Q){if(Q instanceof i){let Y={error:Q.message};if(Q.details!==void 0)Y.details=Q.details;return H(Y,Q.status||400)}return H({error:`Failed to fetch ${J}`,detail:String(Q)},500)}}));if(L.includes("show")&&!l("GET",`${W}/{id}`))f(c.get(`${W}/{id}`,async(T)=>{try{let Q=KZ(T.params?.id);if(Q==null)return H({error:"Invalid ID parameter"},400);let Y=await P.selectFrom(K).where({id:Q}).executeTakeFirst();if(!Y)return H({error:`${Z} not found`},404);let x=new URL(T.url);if($.traits?.useSoftDeletes&&Y.deleted_at&&x.searchParams.get("withTrashed")!=="true")return H({error:`${Z} not found`},404);let A=b?await a($,await s(T),T):{enforced:!1,value:null,field:"",bypass:!1};if(A.enforced&&!A.bypass){if(A.value==null||!r(Y[A.field],A.value))return H({error:`${Z} not found`},404)}let U=o(e(Y,$),B),O=x.searchParams.get("include");if(O){let[F]=await yZ([U],$,O,QZ);U=F}let y=U?.updated_at||U?.created_at,E=y?`W/"${U.id}-${String(y)}"`:void 0,j=T.headers?.get?.("if-none-match");if(E&&j&&j===E)return new Response(null,{status:304,headers:{ETag:E}});let V={"Content-Type":"application/json"};if(E)V.ETag=E;return V["Cache-Control"]=A.enforced&&!A.bypass?"private, no-store":"public, max-age=30, must-revalidate",V.Vary="Authorization",new Response(JSON.stringify({data:U}),{status:200,headers:V})}catch(Q){if(Q instanceof i){let Y={error:Q.message};if(Q.details!==void 0)Y.details=Q.details;return H(Y,Q.status||400)}return H({error:`Failed to fetch ${Z}`,detail:String(Q)},500)}}));if(L.includes("store")&&!l("POST",W))f(c.post(W,async(T)=>{try{let Q=await OZ(T),Y=BZ(Q,B),x=TZ(Y,X),A=await s(T);if(A){let V=await vZ($,"creating",A,T);for(let[F,m]of Object.entries(V))if(m!==void 0&&m!==null&&x[F]===void 0)x[F]=m}if(Object.keys(x).length===0)return H({error:"No fillable fields provided"},422);let U=fZ(x,$,"creating");if(!U.valid)return H({error:"Validation failed",errors:U.errors},422);if($.traits?.useTimestamps!==!1){let V=new Date().toISOString();x.created_at=V,x.updated_at=V}let O=await gZ(x,$),y=YZ(wZ(O,$)),E=await P.insertInto(K).values(y).execute(),j=y;try{let V=E?.lastInsertRowid??E?.insertId??E;if(V){let F=await P.selectFrom(K).where({id:V}).executeTakeFirst();if(F)j=F}}catch{}return H({data:o(e(j,$),B)},201)}catch(Q){let{status:Y,body:x}=XZ(Q,Z,"create");return H(x,Y)}}),R);if(L.includes("update")){let T=async(Q)=>{try{let Y=KZ(Q.params?.id);if(Y==null)return H({error:"Invalid ID parameter"},400);let x=await OZ(Q),A=BZ(x,B),U=TZ(A,X),O=await s(Q);if(O){let _=await vZ($,"updating",O,Q);for(let[v,h]of Object.entries(_))if(h!==void 0&&h!==null&&U[v]===void 0)U[v]=h}if(Object.keys(U).length===0)return H({error:"No fillable fields provided"},422);let y=fZ(U,$,"updating");if(!y.valid)return H({error:"Validation failed",errors:y.errors},422);let E=await P.selectFrom(K).where({id:Y}).executeTakeFirst();if(!E)return H({error:`${Z} not found`},404);let j=await a($,O,Q);if(j.enforced&&!j.bypass){if(!O)return H({error:"Auth required"},401);let _=E[j.field];if(!r(_,j.value))return H({error:`Not your ${Z}`},403);let v=Object.keys(U).find((h)=>M(h)===M(j.field));if(v!==void 0&&!r(U[v],j.value))return H({error:`Cannot reassign ${Z} ownership`},403)}if($.traits?.useTimestamps!==!1)U.updated_at=new Date().toISOString();let V=await gZ(U,$),F=YZ(wZ(V,$));await P.updateTable(K).set(F).where({id:Y}).execute();let m={...E,...F};try{let _=await P.selectFrom(K).where({id:Y}).executeTakeFirst();if(_)m=_}catch{}return H({data:o(e(m,$),B)})}catch(Y){let{status:x,body:A}=XZ(Y,Z,"update");return H(A,x)}};if(!l("PUT",`${W}/{id}`))f(c.put(`${W}/{id}`,T),R);if(!l("PATCH",`${W}/{id}`))f(c.patch(`${W}/{id}`,T),R)}let w=!!$.traits?.useSoftDeletes;if(L.includes("destroy")&&!l("DELETE",`${W}/{id}`))f(c.delete(`${W}/{id}`,async(T)=>{try{let Q=KZ(T.params?.id);if(Q==null)return H({error:"Invalid ID parameter"},400);let Y=await P.selectFrom(K).where({id:Q}).executeTakeFirst();if(!Y)return H({error:`${Z} not found`},404);let x=await s(T),A=await a($,x,T);if(A.enforced&&!A.bypass){if(!x)return H({error:"Auth required"},401);let U=Y[A.field];if(!r(U,A.value))return H({error:`Not your ${Z}`},403)}if(w){let U=new Date().toISOString();await P.updateTable(K).set({deleted_at:U,updated_at:U}).where({id:Q}).execute()}else await P.deleteFrom(K).where({id:Q}).execute();return new Response(null,{status:204})}catch(Q){if(Q instanceof i){let Y={error:Q.message};if(Q.details!==void 0)Y.details=Q.details;return H(Y,Q.status||400)}return H({error:`Failed to delete ${Z}`,detail:String(Q)},500)}}),R);if(L.includes("destroy")&&!l("POST",`${W}/bulk-delete`))f(c.post(`${W}/bulk-delete`,async(T)=>{try{let Y=(await OZ(T))?.ids;if(!Array.isArray(Y)||Y.length===0)return H({error:"An array of IDs is required"},422);if(Y.length>100)return H({error:"Cannot delete more than 100 records at once"},422);let x=[],A=[];for(let E of Y){let j=KZ(E);if(j!=null)x.push(j);else A.push(E)}if(A.length>0)return H({error:"Invalid IDs in batch",invalid:A},422);let U=await s(T),O=await a($,U,T);if(O.enforced&&!O.bypass){if(!U)return H({error:"Auth required"},401);if(O.value==null)return H({error:"Caller has no ownership identity"},403);let E=await P.selectFrom(K).select(["id",O.field]).execute(),j=new Set(E.filter((F)=>r(F[O.field],O.value)).map((F)=>String(F.id))),V=x.filter((F)=>!j.has(String(F)));if(V.length>0)return H({error:`Cannot delete ${Z} you don't own`,ids:V},403)}let y=new Date().toISOString();for(let E of x)if(w)await P.updateTable(K).set({deleted_at:y,updated_at:y}).where({id:E}).execute();else await P.deleteFrom(K).where({id:E}).execute();return H({message:`Successfully deleted ${x.length} ${J}`})}catch(Q){if(Q instanceof i){let Y={error:Q.message};if(Q.details!==void 0)Y.details=Q.details;return H(Y,Q.status||400)}return H({error:`Failed to bulk delete ${J}`,detail:String(Q)},500)}}),R)}T0=c});class AZ{data;status;headers;isOk;constructor(Z,$,z,G){this.data=Z;this.status=$;this.headers=z;this.isOk=G}ok(){return this.status===200}created(){return this.status===201}accepted(){return this.status===202}noContent(){return this.status===204}movedPermanently(){return this.status===301}found(){return this.status===302}badRequest(){return this.status===400}unauthorized(){return this.status===401}paymentRequired(){return this.status===402}forbidden(){return this.status===403}notFound(){return this.status===404}requestTimeout(){return this.status===408}conflict(){return this.status===409}unprocessableEntity(){return this.status===422}tooManyRequests(){return this.status===429}serverError(){return this.status===500}}class EZ{defaultHeaders={Accept:"application/json","Content-Type":"application/json"};queryParams;bodyData;isFormData=!1;customHeaders={};attachments=[];isMultipart=!1;digestAuth;withQueryParams(Z){return this.queryParams=Z,this}withHeaders(Z){return this.customHeaders={...this.customHeaders,...Z},this}accept(Z){return this.defaultHeaders.Accept=Z,this}acceptJson(){return this.accept("application/json")}withToken(Z){return this.customHeaders.Authorization=`Bearer ${Z}`,this}withBasicAuth(Z,$){let z=btoa(`${Z}:${$}`);return this.customHeaders.Authorization=`Basic ${z}`,this}withDigestAuth(Z,$){return this.digestAuth={username:Z,password:$},this}withBody(Z){return this.bodyData=Z,this}asForm(){return this.isFormData=!0,this.defaultHeaders["Content-Type"]="application/x-www-form-urlencoded",this}attach(Z,$,z,G){let J=typeof $==="string"?new Blob([$],{type:G?.["Content-Type"]||"text/plain"}):$;return this.attachments.push({name:Z,content:J,filename:z,headers:G}),this.isMultipart=!0,delete this.defaultHeaders["Content-Type"],this}getHeaders(){return{...this.defaultHeaders,...this.customHeaders}}addQueryParams(Z){if(!this.queryParams)return Z;let $=new URLSearchParams;Object.entries(this.queryParams).forEach(([J,L])=>{if(L!=null)$.append(J,String(L))});let z=$.toString();if(!z)return Z;let G=Z.includes("?");return Z+(G?"&":"?")+z}async formatBody(Z){let $=Z||this.bodyData;if(this.isMultipart){let z=new FormData;if($)Object.entries($).forEach(([G,J])=>{z.append(G,String(J))});for(let G of this.attachments)if(G.headers){let J=G.content instanceof Blob?new Blob([G.content],{type:G.headers["Content-Type"]}):G.content;z.append(G.name,J,G.filename)}else z.append(G.name,G.content,G.filename);return z}if(this.isFormData&&$){let z=new URLSearchParams;return Object.entries($).forEach(([G,J])=>{if(J!=null)z.append(G,String(J))}),z.toString()}return $?JSON.stringify($):void 0}resetState(){this.queryParams=void 0,this.bodyData=void 0,this.isFormData=!1,this.isMultipart=!1,this.customHeaders={},this.attachments=[],this.digestAuth=void 0,this.defaultHeaders["Content-Type"]="application/json"}async handleDigestAuth(Z,$,z,G){if(Z.status!==401||!this.digestAuth)return Z;let J=Z.headers.get("WWW-Authenticate");if(!J?.startsWith("Digest "))return Z;let L=J.substring(7).split(",").reduce((D,S)=>{let[b,q]=S.trim().split("=");if(b)D[b]=q?.replace(/["']/g,"")??"";return D},{}),K=await this.generateMD5(`${this.digestAuth.username}:${L.realm}:${this.digestAuth.password}`),X=await this.generateMD5(`${z}:${$}`),B="00000001",W=await this.generateMD5(Date.now().toString()).then((D)=>D.substring(0,8)),I=await this.generateMD5(`${K}:${L.nonce}:${B}:${W}:${L.qop}:${X}`),N={username:`"${this.digestAuth.username}"`,realm:`"${L.realm}"`,nonce:`"${L.nonce}"`,uri:`"${$}"`,algorithm:L.algorithm?`"${L.algorithm}"`:void 0,qop:`"${L.qop}"`,nc:B,cnonce:`"${W}"`,response:`"${I}"`,opaque:L.opaque?`"${L.opaque}"`:void 0},R=Object.entries(N).filter(([D,S])=>S!==void 0).map(([D,S])=>`${D}=${S}`).join(", ");return this.customHeaders.Authorization=`Digest ${R}`,fetch($,{method:z,headers:this.getHeaders(),body:G?this.formatBody(G):void 0})}async generateMD5(Z){let z=new TextEncoder().encode(Z),G=await crypto.subtle.digest("SHA-256",z);return Array.from(new Uint8Array(G)).map((L)=>L.toString(16).padStart(2,"0")).join("")}async request(Z,$,z){let G=this.addQueryParams(Z),J=await this.formatBody(z),L=await fetch(G,{method:$,headers:this.getHeaders(),body:J});if(this.digestAuth)L=await this.handleDigestAuth(L,G,$,z);let K=await L.json();return this.resetState(),new AZ(K,L.status,L.headers,L.ok)}async get(Z){return this.request(Z,"GET")}async post(Z,$){return this.request(Z,"POST",$)}async put(Z,$){return this.request(Z,"PUT",$)}async patch(Z,$){return this.request(Z,"PATCH",$)}async delete(Z){return this.request(Z,"DELETE")}}var _0=new EZ;import{path as B0}from"@stacksjs/path";function x0(Z){if(!Z||typeof Z!=="object")return{type:"string"};let $=Z,z=$._type??"string",G={type:z==="array"?"array":z};if(typeof $._min==="number")G[z==="string"?"minLength":"minimum"]=$._min;if(typeof $._max==="number")G[z==="string"?"maxLength":"maximum"]=$._max;if($._email)G.format="email";if($._url)G.format="uri";if($._uuid)G.format="uuid";if(Array.isArray($._enum))G.enum=$._enum;return G}async function H0(Z){if(typeof Z!=="string"||!Z.includes("Actions"))return null;try{let{path:$}=await import("@stacksjs/path"),z=[$.appPath(`${Z}.ts`),$.storagePath(`framework/defaults/app/${Z}.ts`)];for(let G of z)try{if(!await Bun.file(G).exists())continue;let K=(await import(G)).default;if(K&&typeof K==="object"&&"validations"in K){let X=K.validations;return X&&typeof X==="object"?X:null}return null}catch{}}catch{}return null}async function R0(){let{listRegisteredRoutes:Z}=await import("@stacksjs/router");try{let{loadRoutes:K}=await import("@stacksjs/router"),{default:X}=await Promise.resolve().then(() => (SZ(),DZ));await K(X)}catch(K){let X=K instanceof Error?K.message:String(K);console.warn(`[generateOpenApi] Failed to load routes/api.ts + framework routes: ${X}`)}try{await nZ().then(() => uZ)}catch(K){let X=K instanceof Error?K.message:String(K);console.warn(`[generateOpenApi] Failed to load useApi-generated ORM routes: ${X}`)}let $=Z(),z="Stacks API";try{let{config:K}=await import("@stacksjs/config");if(K.app?.name)z=`${K.app.name} API`}catch{}let G={openapi:"3.0.0",info:{title:z,version:"1.0.0"},paths:{},components:{schemas:{}}};for(let K of $){let X=K.path.replace(/(^|\/):(\w+)(?=$|\/)/g,"$1{$2}");if(!G.paths[X])G.paths[X]={};let B=[];for(let N of K.path.matchAll(/\{(\w+)\}/g))if(N[1])B.push(N[1]);for(let N of K.path.matchAll(/(?:^|\/):(\w+)(?=$|\/)/g))if(N[1])B.push(N[1]);let W=K.method.toLowerCase(),I={summary:K.name??K.path,operationId:K.name??`${W}_${X.replace(/[^a-z0-9]/gi,"_")}`,parameters:B.map((N)=>({name:N,in:"path",required:!0,schema:{type:"string"}})),responses:{200:{description:"Successful response",content:{"application/json":{schema:{type:"object"}}}},422:{description:"Validation failed"},500:{description:"Server error"}}};if(W!=="get"&&W!=="head"&&K.name){let R=`Actions/${K.name.split(".").map((S)=>S.charAt(0).toUpperCase()+S.slice(1)).join("")}Action`,D=await H0(R);if(D&&Object.keys(D).length>0){let S={type:"object",properties:{},required:[]},b=S.properties,q=S.required;for(let[f,w]of Object.entries(D)){let T=w?.rule;if(b[f]=x0(T),T?._required)q.push(f)}if(q.length===0)delete S.required;I.requestBody={required:q.length>0,content:{"application/json":{schema:S}}}}}G.paths[X][W]=I}let L=Bun.file(B0.frameworkPath("api/openapi.json")).writer();return L.write(JSON.stringify(G,null,2)),await L.end(),G}class g{static instance=new g;isMissing(){return!0}}class ZZ{data;constructor(Z){this.data=Z}}class lZ{condition;value;defaultValue;constructor(Z,$,z=new g){this.condition=Z;this.value=$;this.defaultValue=z}resolve(){return(typeof this.condition==="function"?this.condition():this.condition)?this.value:this.defaultValue}}class dZ{resource;additional={};request;static wrap="data";static additionalMeta={};constructor(Z){this.resource=Z}withRequest(Z){return this.request=Z,this}withAdditional(Z){return this.additional={...this.additional,...Z},this}resolve(Z){let $=this.toArray(Z||this.request);return this.filterAndResolve($)}toResponse(Z){let $=this.resolve(Z),z=this.constructor.wrap;if(z)return{[z]:$,...this.additional};return{...$,...this.additional}}toJson(Z){return JSON.stringify(this.toResponse(Z))}toJSON(){return this.toResponse()}static collection(Z){return new UZ(Z,this)}when(Z,$,z){if(typeof Z==="function"?Z():Z)return typeof $==="function"?$():$;return z!==void 0?z:g.instance}whenNotNull(Z,$){if(Z===null||Z===void 0)return g.instance;return $?$(Z):Z}whenLoaded(Z,$,z){let G=this.resource;if(!((Z in G)&&G[Z]!==void 0))return z!==void 0?z:g.instance;if($!==void 0)return typeof $==="function"?$():$;return G[Z]}whenCounted(Z,$){let z=this.resource,G=`${Z}_count`;if(G in z)return z[G];return $!==void 0?$:g.instance}merge(Z){return new ZZ(Z)}mergeWhen(Z,$){if(!(typeof Z==="function"?Z():Z))return g.instance;let G=typeof $==="function"?$():$;return new ZZ(G)}static MAX_RESOLVE_DEPTH=20;filterAndResolve(Z,$=0){if($>=dZ.MAX_RESOLVE_DEPTH)return Z;let z={};for(let[G,J]of Object.entries(Z)){if(J instanceof g)continue;if(J instanceof ZZ){Object.assign(z,this.filterAndResolve(J.data,$+1));continue}if(J instanceof lZ){let L=J.resolve();if(!(L instanceof g))z[G]=L;continue}if(J!==null&&typeof J==="object"&&!Array.isArray(J)&&!(J instanceof Date)){z[G]=this.filterAndResolve(J,$+1);continue}if(Array.isArray(J)){z[G]=J.filter((L)=>!(L instanceof g)).map((L)=>{if(L instanceof ZZ)return L.data;if(L!==null&&typeof L==="object"&&!(L instanceof Date))return this.filterAndResolve(L,$+1);return L});continue}z[G]=J}return z}static withoutWrapping(){this.wrap=null}static wrapWith(Z){this.wrap=Z}}class UZ{resources;ResourceClass;additional={};request;static wrap="data";constructor(Z,$){this.resources=Z,this.ResourceClass=$}withRequest(Z){return this.request=Z,this}withAdditional(Z){return this.additional={...this.additional,...Z},this}resolve(Z){let $=Z||this.request;return this.resources.map((z)=>{let G=new this.ResourceClass(z);if($)G.withRequest($);return G.resolve($)})}toResponse(Z){let $=this.resolve(Z),z=this.constructor.wrap;if(z)return{[z]:$,...this.additional};return{data:$,...this.additional}}toJson(Z){return JSON.stringify(this.toResponse(Z))}count(){return this.resources.length}isEmpty(){return this.resources.length===0}isNotEmpty(){return this.resources.length>0}}class oZ extends UZ{meta;links;constructor(Z,$){super(Z.data,$);this.meta=Z.meta,this.links=Z.links}static fromPagination(Z,$,z){let{currentPage:G,perPage:J,total:L,baseUrl:K=""}=z,X=Math.ceil(L/J),B=L>0?(G-1)*J+1:null,W=L>0?Math.min(G*J,L):null;return new oZ({data:Z,meta:{current_page:G,from:B,last_page:X,per_page:J,to:W,total:L},links:{first:`${K}?page=1`,last:`${K}?page=${X}`,prev:G>1?`${K}?page=${G-1}`:null,next:G<X?`${K}?page=${G+1}`:null}},$)}toResponse(Z){return{data:this.resolve(Z),meta:this.meta,links:this.links,...this.additional}}}function f0(Z,$){let z=$(Z);return{toResponse:()=>({data:z}),toJson:()=>JSON.stringify({data:z})}}function q0(Z,$){let z=Z.map($);return{toResponse:()=>({data:z}),toJson:()=>JSON.stringify({data:z})}}export{f0 as resource,R0 as generateOpenApi,_0 as fetcher,q0 as collection,UZ as ResourceCollection,oZ as PaginatedResourceCollection,g as MissingValue,ZZ as MergeValue,dZ as JsonResource,lZ as ConditionalValue};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anonymous Resource
|
|
3
|
+
*
|
|
4
|
+
* Create a resource inline without a class.
|
|
5
|
+
*/
|
|
6
|
+
export declare function resource<T>(data: T, transform: (resource: T) => Record<string, any>): { toResponse: () => Record<string, any>, toJson: () => string };
|
|
7
|
+
/**
|
|
8
|
+
* Anonymous Collection
|
|
9
|
+
*
|
|
10
|
+
* Create a collection inline without a class.
|
|
11
|
+
*/
|
|
12
|
+
export declare function collection<T>(data: T[], transform: (resource: T) => Record<string, any>): { toResponse: () => Record<string, any>, toJson: () => string };
|
|
13
|
+
/**
|
|
14
|
+
* Pagination metadata structure
|
|
15
|
+
*/
|
|
16
|
+
export declare interface PaginationMeta {
|
|
17
|
+
current_page: number
|
|
18
|
+
from: number | null
|
|
19
|
+
last_page: number
|
|
20
|
+
per_page: number
|
|
21
|
+
to: number | null
|
|
22
|
+
total: number
|
|
23
|
+
}
|
|
24
|
+
export declare interface PaginationLinks {
|
|
25
|
+
first: string | null
|
|
26
|
+
last: string | null
|
|
27
|
+
prev: string | null
|
|
28
|
+
next: string | null
|
|
29
|
+
}
|
|
30
|
+
export declare interface PaginatedData<T> {
|
|
31
|
+
data: T[]
|
|
32
|
+
meta: PaginationMeta
|
|
33
|
+
links: PaginationLinks
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* API Resources
|
|
37
|
+
*
|
|
38
|
+
* Laravel-like API resources for transforming models and collections
|
|
39
|
+
* into JSON responses with fine-grained control.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* class UserResource extends JsonResource<User> {
|
|
43
|
+
* toArray() {
|
|
44
|
+
* return {
|
|
45
|
+
* id: this.resource.id,
|
|
46
|
+
* name: this.resource.name,
|
|
47
|
+
* email: this.resource.email,
|
|
48
|
+
* posts: PostResource.collection(this.whenLoaded('posts')),
|
|
49
|
+
* created_at: this.resource.created_at,
|
|
50
|
+
* }
|
|
51
|
+
* }
|
|
52
|
+
* }
|
|
53
|
+
*
|
|
54
|
+
* // Usage
|
|
55
|
+
* return new UserResource(user).toResponse()
|
|
56
|
+
* return UserResource.collection(users).toResponse()
|
|
57
|
+
*/
|
|
58
|
+
declare type Request = Record<string, unknown>;
|
|
59
|
+
/**
|
|
60
|
+
* Wrapper value types for conditional inclusion
|
|
61
|
+
*/
|
|
62
|
+
export declare class MissingValue {
|
|
63
|
+
static instance: unknown;
|
|
64
|
+
isMissing(): boolean;
|
|
65
|
+
}
|
|
66
|
+
export declare class MergeValue {
|
|
67
|
+
public data: Record<string, any>;
|
|
68
|
+
constructor(data: Record<string, any>);
|
|
69
|
+
}
|
|
70
|
+
export declare class ConditionalValue {
|
|
71
|
+
public condition: boolean | (() => boolean);
|
|
72
|
+
public value: any;
|
|
73
|
+
public defaultValue?: any;
|
|
74
|
+
constructor(condition: boolean | (() => boolean), value: any, defaultValue?: any);
|
|
75
|
+
resolve(): any;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Base JSON Resource class
|
|
79
|
+
*
|
|
80
|
+
* Extend this class to create custom API resources that transform
|
|
81
|
+
* your models into JSON-serializable objects.
|
|
82
|
+
*/
|
|
83
|
+
export declare abstract class JsonResource<T = unknown> {
|
|
84
|
+
resource: T;
|
|
85
|
+
additional: Record<string, any>;
|
|
86
|
+
protected request?: Request;
|
|
87
|
+
static wrap: string | null;
|
|
88
|
+
protected static additionalMeta: Record<string, any>;
|
|
89
|
+
constructor(resource: T);
|
|
90
|
+
abstract toArray(request?: Request): Record<string, any>;
|
|
91
|
+
withRequest(request: Request): this;
|
|
92
|
+
withAdditional(data: Record<string, any>): this;
|
|
93
|
+
resolve(request?: Request): Record<string, any>;
|
|
94
|
+
toResponse(request?: Request): Record<string, any>;
|
|
95
|
+
toJson(request?: Request): string;
|
|
96
|
+
toJSON(): Record<string, unknown>;
|
|
97
|
+
static collection<T, R extends JsonResource<T>>(this: new (resource: T) => R, resources: T[]): ResourceCollection<T, R>;
|
|
98
|
+
protected when<V>(condition: boolean | (() => boolean), value: V | (() => V), defaultValue?: any): V | MissingValue;
|
|
99
|
+
protected whenNotNull<V>(value: V | null | undefined, transform?: (v: V) => any): any | MissingValue;
|
|
100
|
+
protected whenLoaded<V>(relationship: string, value?: V | (() => V), defaultValue?: any): V | any[] | MissingValue;
|
|
101
|
+
protected whenCounted(relationship: string, defaultValue?: number): number | MissingValue;
|
|
102
|
+
protected merge(data: Record<string, any>): MergeValue;
|
|
103
|
+
protected mergeWhen(condition: boolean | (() => boolean), data: Record<string, any> | (() => Record<string, any>)): MergeValue | MissingValue;
|
|
104
|
+
static withoutWrapping(): void;
|
|
105
|
+
static wrapWith(key: string): void;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Resource Collection
|
|
109
|
+
*
|
|
110
|
+
* Wraps an array of resources with collection-specific functionality.
|
|
111
|
+
*/
|
|
112
|
+
export declare class ResourceCollection<T = unknown, R extends JsonResource<T> = JsonResource<T>> {
|
|
113
|
+
resources: T[];
|
|
114
|
+
ResourceClass: new (resource: T) => R;
|
|
115
|
+
additional: Record<string, any>;
|
|
116
|
+
protected request?: Request;
|
|
117
|
+
static wrap: string | null;
|
|
118
|
+
constructor(resources: T[], ResourceClass: new (resource: T) => R);
|
|
119
|
+
withRequest(request: Request): this;
|
|
120
|
+
withAdditional(data: Record<string, any>): this;
|
|
121
|
+
resolve(request?: Request): Record<string, any>[];
|
|
122
|
+
toResponse(request?: Request): Record<string, any>;
|
|
123
|
+
toJson(request?: Request): string;
|
|
124
|
+
count(): number;
|
|
125
|
+
isEmpty(): boolean;
|
|
126
|
+
isNotEmpty(): boolean;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Paginated Resource Collection
|
|
130
|
+
*
|
|
131
|
+
* Handles paginated data with meta and links.
|
|
132
|
+
*/
|
|
133
|
+
export declare class PaginatedResourceCollection<T = unknown, R extends JsonResource<T> = JsonResource<T>> extends ResourceCollection<T, R> {
|
|
134
|
+
meta: PaginationMeta;
|
|
135
|
+
links: PaginationLinks;
|
|
136
|
+
constructor(paginatedData: PaginatedData<T>, ResourceClass: new (resource: T) => R);
|
|
137
|
+
static fromPagination<T, R extends JsonResource<T>>(data: T[], ResourceClass: new (resource: T) => R, options: {
|
|
138
|
+
currentPage: number
|
|
139
|
+
perPage: number
|
|
140
|
+
total: number
|
|
141
|
+
baseUrl?: string
|
|
142
|
+
}): PaginatedResourceCollection<T, R>;
|
|
143
|
+
toResponse(request?: Request): Record<string, any>;
|
|
144
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The response shape from any Fetcher request
|
|
3
|
+
* @template T The type of data expected in the response
|
|
4
|
+
*/
|
|
5
|
+
export declare interface FetcherResponse<T = any> {
|
|
6
|
+
data: T
|
|
7
|
+
status: number
|
|
8
|
+
headers: Headers
|
|
9
|
+
isOk: boolean
|
|
10
|
+
ok: () => boolean
|
|
11
|
+
created: () => boolean
|
|
12
|
+
accepted: () => boolean
|
|
13
|
+
noContent: () => boolean
|
|
14
|
+
movedPermanently: () => boolean
|
|
15
|
+
found: () => boolean
|
|
16
|
+
badRequest: () => boolean
|
|
17
|
+
unauthorized: () => boolean
|
|
18
|
+
paymentRequired: () => boolean
|
|
19
|
+
forbidden: () => boolean
|
|
20
|
+
notFound: () => boolean
|
|
21
|
+
requestTimeout: () => boolean
|
|
22
|
+
conflict: () => boolean
|
|
23
|
+
unprocessableEntity: () => boolean
|
|
24
|
+
tooManyRequests: () => boolean
|
|
25
|
+
serverError: () => boolean
|
|
26
|
+
}
|
|
27
|
+
export declare interface FileAttachment {
|
|
28
|
+
name: string
|
|
29
|
+
content: Blob
|
|
30
|
+
filename?: string
|
|
31
|
+
headers?: Record<string, string>
|
|
32
|
+
}
|
|
33
|
+
export type QueryParams = Record<string, string | number | boolean | null | undefined>;
|
|
34
|
+
export type BodyData = Record<string, any>;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/api",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.91",
|
|
6
6
|
"description": "The Stacks array utilities.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -56,6 +56,6 @@
|
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"better-dx": "^0.2.16",
|
|
59
|
-
"@stacksjs/utils": "0.70.
|
|
59
|
+
"@stacksjs/utils": "0.70.91"
|
|
60
60
|
}
|
|
61
61
|
}
|