resora 0.2.17 → 0.2.18
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/index.cjs +2533 -5
- package/dist/index.mjs +2440 -5
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,1138 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`fs`),l=require(`path`);l=s(l);let u=require(`module`),d=require(`url`),f=require(`@h3ravel/musket`);function ee(e){return e}let p,m,h=[`meta`,`links`],g={first:`first`,last:`last`,prev:`prev`,next:`next`},te=``,ne=`page`,_={to:`to`,from:`from`,links:`links`,path:`path`,total:`total`,per_page:`per_page`,last_page:`last_page`,current_page:`current_page`},v={previous:`previous`,next:`next`};const re=e=>{p=e},ie=()=>p,y=e=>{m=e},b=()=>m,ae=e=>{m={...m||{},rootKey:e}},oe=e=>{m={...m||{},wrap:e}},se=()=>m?.wrap,ce=()=>m?.rootKey,le=e=>{m={...m||{},factory:e}},ue=()=>m?.factory,x=e=>{h=e},S=()=>h,de=e=>{g={...g,...e}},fe=()=>g,pe=e=>{te=e},me=()=>te,he=e=>{ne=e},C=()=>ne,ge=e=>{_=e},_e=()=>_,ve=e=>{v={...v,...e}},ye=()=>v;let be;const w=e=>{be=e},T=()=>be,E=e=>{if(!e||typeof e!=`object`)return typeof e==`string`?e:void 0;let t=e;return t.req&&typeof t.req==`object`?D(t.req):D(t)},D=e=>{if(typeof e.originalUrl==`string`)return e.originalUrl;if(typeof e.url==`string`)try{let t=new URL(e.url);return t.pathname+t.search}catch{return e.url}},O=e=>{if(!e||typeof e!=`object`)return;let t=e;return t.res&&typeof t.res==`object`?t.res:e},k=e=>{w(E(e))},A=e=>{if(typeof e!=`object`||!e||Array.isArray(e)||e instanceof Date||e instanceof RegExp)return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null},j=(e,t,n=`data`)=>!t||Object.keys(t).length===0?e:Array.isArray(e)?{[n]:e,...t}:A(e)?{...e,...t}:{[n]:e,...t},M=(e,t)=>{if(!t)return e;if(!e)return t;let n={...e};for(let[e,r]of Object.entries(t)){let t=n[e];A(t)&&A(r)?n[e]=M(t,r):n[e]=r}return n},N=e=>{if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.toObject==`function`&&typeof t.getRawAttributes==`function`},P=e=>!e||typeof e!=`object`?!1:typeof e.all==`function`,F=e=>{if(Array.isArray(e))return e.map(e=>F(e));if(N(e))return F(e.toObject());if(P(e)){let t=e.all();return Array.isArray(t)?t.map(e=>F(e)):F(t)}return A(e)?Object.entries(e).reduce((e,[t,n])=>(e[t]=F(n),e),{}):e},I=()=>{let e=S();return Array.isArray(e)?{metaKey:e.includes(`meta`)?`meta`:void 0,linksKey:e.includes(`links`)?`links`:void 0,cursorKey:e.includes(`cursor`)?`cursor`:void 0}:{metaKey:e.meta,linksKey:e.links,cursorKey:e.cursor}},xe=e=>{if(e===null)return;let t=Number(e);if(!(!Number.isInteger(t)||t<1))return t},Se=(e,t)=>{try{return new URL(e,`http://resora.local`).searchParams.get(t)}catch{let n=e.indexOf(`?`);return n<0?null:new URLSearchParams(e.slice(n+1)).get(t)}},L=(e,t=C()||`page`)=>{let n=e===void 0?T():E(e);if(n)return xe(Se(n,t))},Ce=e=>t=>L(e,t),R=(e,t)=>{if(e===void 0)return;let n=t||T()||``,r=me()||``,i=C()||`page`,a=n.indexOf(`?`),o=a>=0?n.slice(0,a):n,s=a>=0?n.slice(a+1):``;if(/^https?:\/\//i.test(o)){let t=new URL(n);return t.searchParams.set(i,String(e)),t.toString()}let c=r.replace(/\/$/,``),l=o.replace(/^\//,``);if(/^https?:\/\//i.test(c)){let t=l?`${c}/${l}`:c,n=new URL(t);if(s)for(let[e,t]of new URLSearchParams(s))n.searchParams.set(e,t);return n.searchParams.set(i,String(e)),n.toString()}let u=[c,l].filter(Boolean).join(`/`),d=u?`/${u}`:`/`,f=new URLSearchParams(s);return f.set(i,String(e)),`${d}?${f.toString()}`},we=e=>{let t=e.currentPage,n=e.lastPage,r=e.hasMorePages;return{firstPage:typeof n==`number`?1:void 0,lastPage:n,nextPage:typeof n==`number`?typeof t==`number`&&t<n?t+1:void 0:r&&typeof t==`number`?t+1:void 0,prevPage:typeof t==`number`&&t>1?t-1:void 0}},z=(e,t)=>e.links&&Object.prototype.hasOwnProperty.call(e.links,t),B=e=>{let{metaKey:t,linksKey:n,cursorKey:r}=I(),i={},a=!!e&&typeof e==`object`&&!!e.meta&&typeof e.meta==`object`&&(Array.isArray(e.data)||P(e.data)),o=a?we(e.meta):void 0,s=a?{first:typeof e.firstPageUrl==`function`?e.firstPageUrl():void 0,last:typeof e.lastPageUrl==`function`?e.lastPageUrl():void 0,prev:typeof e.previousPageUrl==`function`?e.previousPageUrl():void 0,next:typeof e.nextPageUrl==`function`?e.nextPageUrl():void 0}:void 0,c=e?.pagination||(a?{...e.meta,...o||{},path:e.urlDriver?.path}:void 0),l=e?.cursor,u={},d={};if(c){let e=T()||c.path,t=e?R(c.currentPage,e)??e:void 0,n={first:z(c,`first`)?c.links.first:R(c.firstPage,e)??s?.first,last:z(c,`last`)?c.links.last:R(c.lastPage,e)??s?.last,prev:z(c,`prev`)?c.links.prev:R(c.prevPage,e)??s?.prev,next:z(c,`next`)?c.links.next:R(c.nextPage,e)??s?.next},r=Object.fromEntries(Object.entries(n).filter(([,e])=>e!==void 0)),i={to:c.to,from:c.from,links:c.links||(a&&Object.keys(r).length?r:void 0),path:t,total:c.total,per_page:c.perPage,last_page:c.lastPage,current_page:c.currentPage};for(let[e,t]of Object.entries(_e())){if(!t)continue;let n=i[e];n!==void 0&&(u[t]=n)}for(let[e,t]of Object.entries(fe())){if(!t)continue;let r=n[e];r!==void 0&&(d[t]=r)}}if(l){let e={},t={previous:l.previous,next:l.next};for(let[n,r]of Object.entries(ye())){if(!r)continue;let i=t[n];i!==void 0&&(e[r]=i)}r&&Object.keys(e).length>0?i[r]=e:Object.keys(e).length>0&&(u.cursor=e)}return t&&Object.keys(u).length>0&&(i[t]=u),n&&Object.keys(d).length>0&&(i[n]=d),i},V=({payload:e,meta:t,metaKey:n=`meta`,wrap:r=!0,rootKey:i=`data`,factory:a,context:o})=>{if(a)return a(e,{...o,rootKey:i,meta:t});if(!r)return t===void 0?e:A(e)?{...e,[n]:t}:{[i]:e,[n]:t};let s={[i]:e};return t!==void 0&&(s[n]=t),s},Te=(e,t)=>{let n=e?.with;if(typeof n!=`function`||n===t||n.length>0)return;let r=n.call(e);return A(r)?r:void 0},H=Symbol(`resora.conditional.missing`),Ee=(e,t)=>e?typeof t==`function`?t():t:H,De=e=>e??H,Oe=(e,t)=>{if(!e)return{};let n=typeof t==`function`?t():t;return A(n)?n:{}},U=e=>{if(e===H)return H;if(Array.isArray(e))return e.map(e=>U(e)).filter(e=>e!==H);if(A(e)){let t={};for(let[n,r]of Object.entries(e)){let e=U(r);e!==H&&(t[n]=e)}return t}return e},W=e=>e.replace(/([a-z0-9])([A-Z])/g,`$1 $2`).replace(/([A-Z]+)([A-Z][a-z])/g,`$1 $2`).replace(/[-_\s]+/g,` `).trim().toLowerCase().split(` `).filter(Boolean),ke=e=>W(e).map((e,t)=>t===0?e:e.charAt(0).toUpperCase()+e.slice(1)).join(``),Ae=e=>W(e).join(`_`),je=e=>W(e).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(``),Me=e=>W(e).join(`-`),G=e=>{if(typeof e==`function`)return e;switch(e){case`camel`:return ke;case`snake`:return Ae;case`pascal`:return je;case`kebab`:return Me}},K=(e,t)=>e==null?e:Array.isArray(e)?e.map(e=>K(e,t)):e instanceof Date||e instanceof RegExp?e:typeof e==`object`?Object.fromEntries(Object.entries(e).map(([e,n])=>[t(e),K(n,t)])):e;let q=l.default.resolve(process.cwd(),`node_modules/resora/stubs`);(0,c.existsSync)(q)||(q=l.default.resolve(process.cwd(),`stubs`));const Ne=()=>({stubsDir:q,preferredCase:`camel`,responseStructure:{wrap:!0,rootKey:`data`},paginatedExtras:[`meta`,`links`],baseUrl:``,pageName:`page`,paginatedLinks:{first:`first`,last:`last`,prev:`prev`,next:`next`},paginatedMeta:{to:`to`,from:`from`,links:`links`,path:`path`,total:`total`,per_page:`per_page`,last_page:`last_page`,current_page:`current_page`},cursorMeta:{previous:`previous`,next:`next`},resourcesDir:`src/resources`,stubs:{config:`resora.config.stub`,resource:`resource.stub`,collection:`resource.collection.stub`}}),J=e=>{let t=Ne();return Object.assign(t,e,{stubs:Object.assign(t.stubs,e.stubs||{})},{cursorMeta:Object.assign(t.cursorMeta,e.cursorMeta||{})},{paginatedMeta:e.paginatedMeta||t.paginatedMeta},{paginatedLinks:Object.assign(t.paginatedLinks,e.paginatedLinks||{})},{responseStructure:Object.assign(t.responseStructure,e.responseStructure||{})})};let Y=!1,X;const Pe=()=>{Y=!1,X=void 0},Fe=e=>{e.preferredCase!==`camel`&&re(e.preferredCase),y(e.responseStructure),x(e.paginatedExtras),de(e.paginatedLinks),ge(e.paginatedMeta),ve(e.cursorMeta),pe(e.baseUrl),he(e.pageName)},Ie=async e=>await import(`${(0,d.pathToFileURL)(e).href}?resora_runtime=${Date.now()}`),Le=e=>{Fe(J((e?.default??e)||{})),Y=!0},Re=()=>{let e=(0,u.createRequire)(e(`url`).pathToFileURL(__filename).href),t=[l.default.join(process.cwd(),`resora.config.cjs`)];for(let n of t)if((0,c.existsSync)(n))try{return Le(e(n)),!0}catch{continue}return!1},Z=async()=>{if(!Y){if(X)return await X;Re()||(X=(async()=>{let e=[l.default.join(process.cwd(),`resora.config.js`),l.default.join(process.cwd(),`resora.config.ts`)];for(let t of e)if((0,c.existsSync)(t))try{Le(await Ie(t));return}catch{continue}Y=!0})(),await X)}};Z();var ze=class{command;config={};constructor(e={}){this.config=J(e)}async loadConfig(e={}){this.config=J(e);let t=[(0,l.join)(process.cwd(),`resora.config.ts`),(0,l.join)(process.cwd(),`resora.config.js`),(0,l.join)(process.cwd(),`resora.config.cjs`)];for(let e of t)if((0,c.existsSync)(e))try{let{default:t}=await import(e);Object.assign(this.config,t);break}catch(t){console.error(`Error loading config file at ${e}:`,t)}return this}getConfig(){return this.config}init(){let e=(0,l.join)(process.cwd(),`resora.config.js`),t=(0,l.join)(this.config.stubsDir,this.config.stubs.config);return(0,c.existsSync)(e)&&!this.command.option(`force`)&&(this.command.error(`Error: ${e} already exists.`),process.exit(1)),this.ensureDirectory(e),(0,c.existsSync)(e)&&this.command.option(`force`)&&(0,c.copyFileSync)(e,e.replace(/\.js$/,`.backup.${Date.now()}.js`)),(0,c.writeFileSync)(e,(0,c.readFileSync)(t,`utf-8`)),{path:e}}ensureDirectory(e){let t=(0,l.dirname)(e);(0,c.existsSync)(t)||(0,c.mkdirSync)(t,{recursive:!0})}generateFile(e,t,n,r){(0,c.existsSync)(t)&&!r?.force?(this.command.error(`Error: ${t} already exists.`),process.exit(1)):(0,c.existsSync)(t)&&r?.force&&(0,c.rmSync)(t);let i=(0,c.readFileSync)(e,`utf-8`);for(let[e,t]of Object.entries(n))i=i.replace(RegExp(`{{${e}}}`,`g`),t);return this.ensureDirectory(t),(0,c.writeFileSync)(t,i),t}makeResource(e,t){let n=e;t?.collection&&!e.endsWith(`Collection`)&&!e.endsWith(`Resource`)?n+=`Collection`:!t?.collection&&!e.endsWith(`Resource`)&&!e.endsWith(`Collection`)&&(n+=`Resource`);let r=`${n}.ts`,i=(0,l.join)(this.config.resourcesDir,r),a=(0,l.join)(this.config.stubsDir,t?.collection||e.endsWith(`Collection`)?this.config.stubs.collection:this.config.stubs.resource);(0,c.existsSync)(a)||(this.command.error(`Error: Stub file ${a} not found.`),process.exit(1)),n=n.split(`/`).pop()?.split(`.`).shift();let o=n.replace(/(Resource|Collection)$/,``)+`Resource`,s=[`/**`,` * The resource that this collection collects.`,` */`,`collects = ${o}`].join(`
|
|
2
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
14
|
+
__defProp(to, key, {
|
|
15
|
+
get: ((k) => from[k]).bind(null, key),
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
let fs = require("fs");
|
|
30
|
+
let path = require("path");
|
|
31
|
+
path = __toESM(path);
|
|
32
|
+
let module$1 = require("module");
|
|
33
|
+
let url = require("url");
|
|
34
|
+
let _h3ravel_musket = require("@h3ravel/musket");
|
|
35
|
+
|
|
36
|
+
//#region src/ApiResource.ts
|
|
37
|
+
/**
|
|
38
|
+
* ApiResource function to return the Resource instance
|
|
39
|
+
*
|
|
40
|
+
* @param instance Resource instance
|
|
41
|
+
* @returns Resource instance
|
|
42
|
+
*/
|
|
43
|
+
function ApiResource(instance) {
|
|
44
|
+
return instance;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/utilities/state.ts
|
|
49
|
+
let globalPreferredCase;
|
|
50
|
+
let globalResponseStructure;
|
|
51
|
+
let globalPaginatedExtras = ["meta", "links"];
|
|
52
|
+
let globalPaginatedLinks = {
|
|
53
|
+
first: "first",
|
|
54
|
+
last: "last",
|
|
55
|
+
prev: "prev",
|
|
56
|
+
next: "next"
|
|
57
|
+
};
|
|
58
|
+
let globalBaseUrl = "";
|
|
59
|
+
let globalPageName = "page";
|
|
60
|
+
let globalPaginatedMeta = {
|
|
61
|
+
to: "to",
|
|
62
|
+
from: "from",
|
|
63
|
+
links: "links",
|
|
64
|
+
path: "path",
|
|
65
|
+
total: "total",
|
|
66
|
+
per_page: "per_page",
|
|
67
|
+
last_page: "last_page",
|
|
68
|
+
current_page: "current_page"
|
|
69
|
+
};
|
|
70
|
+
let globalCursorMeta = {
|
|
71
|
+
previous: "previous",
|
|
72
|
+
next: "next"
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Sets the global case style for response keys, which will be applied
|
|
76
|
+
* to all responses unless overridden by individual resource configurations.
|
|
77
|
+
*
|
|
78
|
+
* @param style The case style to set as the global default for response keys.
|
|
79
|
+
*/
|
|
80
|
+
const setGlobalCase = (style) => {
|
|
81
|
+
globalPreferredCase = style;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Retrieves the global case style for response keys, which is used
|
|
85
|
+
* to determine how keys in responses should be formatted.
|
|
86
|
+
*
|
|
87
|
+
* @returns
|
|
88
|
+
*/
|
|
89
|
+
const getGlobalCase = () => {
|
|
90
|
+
return globalPreferredCase;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Sets the global response structure configuration, which defines how
|
|
94
|
+
* responses should be structured across the application.
|
|
95
|
+
*
|
|
96
|
+
* @param config The response structure configuration object.
|
|
97
|
+
*/
|
|
98
|
+
const setGlobalResponseStructure = (config) => {
|
|
99
|
+
globalResponseStructure = config;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Retrieves the global response structure configuration, which
|
|
103
|
+
* defines how responses should be structured across the application.
|
|
104
|
+
*
|
|
105
|
+
* @returns
|
|
106
|
+
*/
|
|
107
|
+
const getGlobalResponseStructure = () => {
|
|
108
|
+
return globalResponseStructure;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Sets the global response root key, which is the key under which
|
|
112
|
+
* the main data will be nested in responses if wrapping is enabled.
|
|
113
|
+
*
|
|
114
|
+
* @param rootKey The root key to set for response data.
|
|
115
|
+
*/
|
|
116
|
+
const setGlobalResponseRootKey = (rootKey) => {
|
|
117
|
+
globalResponseStructure = {
|
|
118
|
+
...globalResponseStructure || {},
|
|
119
|
+
rootKey
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Sets the global response wrap option, which determines whether responses
|
|
124
|
+
* should be wrapped in a root key or returned unwrapped when possible.
|
|
125
|
+
*
|
|
126
|
+
* @param wrap The wrap option to set for responses.
|
|
127
|
+
*/
|
|
128
|
+
const setGlobalResponseWrap = (wrap) => {
|
|
129
|
+
globalResponseStructure = {
|
|
130
|
+
...globalResponseStructure || {},
|
|
131
|
+
wrap
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Retrieves the global response wrap option, which indicates whether responses
|
|
136
|
+
* should be wrapped in a root key or returned unwrapped when possible.
|
|
137
|
+
*
|
|
138
|
+
* @returns
|
|
139
|
+
*/
|
|
140
|
+
const getGlobalResponseWrap = () => {
|
|
141
|
+
return globalResponseStructure?.wrap;
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Retrieves the global response root key, which is the key under which the main
|
|
145
|
+
* data will be nested in responses if wrapping is enabled.
|
|
146
|
+
*
|
|
147
|
+
* @returns
|
|
148
|
+
*/
|
|
149
|
+
const getGlobalResponseRootKey = () => {
|
|
150
|
+
return globalResponseStructure?.rootKey;
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* Sets the global response factory, which is a custom function that can be used
|
|
154
|
+
* to produce a completely custom response structure based on the provided
|
|
155
|
+
* payload and context.
|
|
156
|
+
*
|
|
157
|
+
* @param factory The response factory function to set as the global default for response construction.
|
|
158
|
+
*/
|
|
159
|
+
const setGlobalResponseFactory = (factory) => {
|
|
160
|
+
globalResponseStructure = {
|
|
161
|
+
...globalResponseStructure || {},
|
|
162
|
+
factory
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Retrieves the global response factory, which is a custom function that
|
|
167
|
+
* can be used to produce a completely custom response structure based on
|
|
168
|
+
* the provided payload and context.
|
|
169
|
+
*
|
|
170
|
+
* @returns
|
|
171
|
+
*/
|
|
172
|
+
const getGlobalResponseFactory = () => {
|
|
173
|
+
return globalResponseStructure?.factory;
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Sets the global paginated extras configuration, which defines the keys
|
|
177
|
+
* to use for pagination metadata, links, and cursor information in paginated responses.
|
|
178
|
+
*
|
|
179
|
+
* @param extras The paginated extras configuration object.
|
|
180
|
+
*/
|
|
181
|
+
const setGlobalPaginatedExtras = (extras) => {
|
|
182
|
+
globalPaginatedExtras = extras;
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* Retrieves the global paginated extras configuration, which defines the keys to use for pagination metadata, links, and cursor information in paginated responses.
|
|
186
|
+
*
|
|
187
|
+
* @returns
|
|
188
|
+
*/
|
|
189
|
+
const getGlobalPaginatedExtras = () => {
|
|
190
|
+
return globalPaginatedExtras;
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* Sets the global paginated links configuration, which defines the keys to
|
|
194
|
+
* use for pagination links (first, last, prev, next) in paginated responses.
|
|
195
|
+
*
|
|
196
|
+
* @param links The paginated links configuration object.
|
|
197
|
+
*/
|
|
198
|
+
const setGlobalPaginatedLinks = (links) => {
|
|
199
|
+
globalPaginatedLinks = {
|
|
200
|
+
...globalPaginatedLinks,
|
|
201
|
+
...links
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
/**
|
|
205
|
+
* Retrieves the global paginated links configuration, which defines the keys to use for pagination links (first, last, prev, next) in paginated responses.
|
|
206
|
+
*
|
|
207
|
+
* @returns
|
|
208
|
+
*/
|
|
209
|
+
const getGlobalPaginatedLinks = () => {
|
|
210
|
+
return globalPaginatedLinks;
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* Sets the global base URL, which is used for generating pagination links in responses.
|
|
214
|
+
*
|
|
215
|
+
* @param baseUrl The base URL to set for pagination link generation.
|
|
216
|
+
*/
|
|
217
|
+
const setGlobalBaseUrl = (baseUrl) => {
|
|
218
|
+
globalBaseUrl = baseUrl;
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Retrieves the global base URL, which is used for generating pagination links in responses.
|
|
222
|
+
*
|
|
223
|
+
* @returns
|
|
224
|
+
*/
|
|
225
|
+
const getGlobalBaseUrl = () => {
|
|
226
|
+
return globalBaseUrl;
|
|
227
|
+
};
|
|
228
|
+
/**
|
|
229
|
+
* Sets the global page name, which is the query parameter name used for the page number in paginated requests and link generation.
|
|
230
|
+
*
|
|
231
|
+
* @param pageName
|
|
232
|
+
*/
|
|
233
|
+
const setGlobalPageName = (pageName) => {
|
|
234
|
+
globalPageName = pageName;
|
|
235
|
+
};
|
|
236
|
+
/**
|
|
237
|
+
* Retrieves the global page name, which is the query parameter name
|
|
238
|
+
* used for the page number in paginated requests and link generation.
|
|
239
|
+
*
|
|
240
|
+
* @returns
|
|
241
|
+
*/
|
|
242
|
+
const getGlobalPageName = () => {
|
|
243
|
+
return globalPageName;
|
|
244
|
+
};
|
|
245
|
+
/**
|
|
246
|
+
* Retrieves the keys to use for pagination extras (meta, links, cursor) based
|
|
247
|
+
* on the global configuration.
|
|
248
|
+
*
|
|
249
|
+
* @param meta Whether to include pagination metadata in the response.
|
|
250
|
+
*/
|
|
251
|
+
const setGlobalPaginatedMeta = (meta) => {
|
|
252
|
+
globalPaginatedMeta = meta;
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* Retrieves the keys to use for pagination extras (meta, links, cursor) based
|
|
256
|
+
* on the global configuration.
|
|
257
|
+
*
|
|
258
|
+
* @returns The global pagination metadata configuration.
|
|
259
|
+
*/
|
|
260
|
+
const getGlobalPaginatedMeta = () => {
|
|
261
|
+
return globalPaginatedMeta;
|
|
262
|
+
};
|
|
263
|
+
/**
|
|
264
|
+
* Sets the global cursor meta configuration, which defines the keys to use
|
|
265
|
+
* for cursor pagination metadata (previous, next) in responses.
|
|
266
|
+
*
|
|
267
|
+
* @param meta The cursor meta configuration object.
|
|
268
|
+
*/
|
|
269
|
+
const setGlobalCursorMeta = (meta) => {
|
|
270
|
+
globalCursorMeta = {
|
|
271
|
+
...globalCursorMeta,
|
|
272
|
+
...meta
|
|
273
|
+
};
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* Retrieves the keys to use for cursor pagination metadata (previous, next) in
|
|
277
|
+
* responses based on the global configuration.
|
|
278
|
+
*
|
|
279
|
+
* @returns The global cursor pagination metadata configuration.
|
|
280
|
+
*/
|
|
281
|
+
const getGlobalCursorMeta = () => {
|
|
282
|
+
return globalCursorMeta;
|
|
283
|
+
};
|
|
284
|
+
let globalRequestUrl;
|
|
285
|
+
/**
|
|
286
|
+
* Sets the current request URL, used as a fallback for pagination link generation
|
|
287
|
+
* when no explicit path is provided in the pagination data.
|
|
288
|
+
*
|
|
289
|
+
* @param url The request URL or pathname to set.
|
|
290
|
+
*/
|
|
291
|
+
const setRequestUrl = (url) => {
|
|
292
|
+
globalRequestUrl = url;
|
|
293
|
+
};
|
|
294
|
+
/**
|
|
295
|
+
* Retrieves the current request URL, used as a fallback path for pagination links.
|
|
296
|
+
*
|
|
297
|
+
* @returns The current request URL, or undefined if not set.
|
|
298
|
+
*/
|
|
299
|
+
const getRequestUrl = () => {
|
|
300
|
+
return globalRequestUrl;
|
|
301
|
+
};
|
|
302
|
+
/**
|
|
303
|
+
* Extracts the request URL pathname (with query string) from an HTTP context.
|
|
304
|
+
*
|
|
305
|
+
* Both Express middleware and H3 HTTPEvent expose `{ req, res }`. The `req`
|
|
306
|
+
* object carries URL information:
|
|
307
|
+
* - H3 / Web standard `Request`: `req.url` is a full URL string
|
|
308
|
+
* - Express `Request`: `req.originalUrl` is the pathname + query string
|
|
309
|
+
*
|
|
310
|
+
* The function also accepts a plain `req` object directly or a string URL.
|
|
311
|
+
*
|
|
312
|
+
* @param ctx An HTTP context `{ req, res }`, a bare `Request` object, or a string URL.
|
|
313
|
+
* @returns The pathname with query string, or undefined.
|
|
314
|
+
*/
|
|
315
|
+
const extractRequestUrl = (ctx) => {
|
|
316
|
+
if (!ctx || typeof ctx !== "object") return typeof ctx === "string" ? ctx : void 0;
|
|
317
|
+
const obj = ctx;
|
|
318
|
+
if (obj.req && typeof obj.req === "object") return extractUrlFromRequest(obj.req);
|
|
319
|
+
return extractUrlFromRequest(obj);
|
|
320
|
+
};
|
|
321
|
+
/**
|
|
322
|
+
* Extracts the URL pathname + query string from a request object.
|
|
323
|
+
*
|
|
324
|
+
* Handles both Web standard `Request` (H3) where `url` is a full URL string,
|
|
325
|
+
* and Express `Request` where `originalUrl` or `url` provide the path.
|
|
326
|
+
*/
|
|
327
|
+
const extractUrlFromRequest = (req) => {
|
|
328
|
+
if (typeof req.originalUrl === "string") return req.originalUrl;
|
|
329
|
+
if (typeof req.url === "string") try {
|
|
330
|
+
const parsed = new URL(req.url);
|
|
331
|
+
return parsed.pathname + parsed.search;
|
|
332
|
+
} catch {
|
|
333
|
+
return req.url;
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
/**
|
|
337
|
+
* Extracts the response object from an HTTP context.
|
|
338
|
+
*
|
|
339
|
+
* If the context has a `res` property (Express middleware `{ req, res }` or
|
|
340
|
+
* H3 HTTPEvent), returns `ctx.res`. Otherwise assumes the value is already
|
|
341
|
+
* a bare response object and returns it directly.
|
|
342
|
+
*
|
|
343
|
+
* @param ctx The HTTP context or bare response.
|
|
344
|
+
* @returns The response object, or undefined.
|
|
345
|
+
*/
|
|
346
|
+
const extractResponseFromCtx = (ctx) => {
|
|
347
|
+
if (!ctx || typeof ctx !== "object") return;
|
|
348
|
+
const obj = ctx;
|
|
349
|
+
if (obj.res && typeof obj.res === "object") return obj.res;
|
|
350
|
+
return ctx;
|
|
351
|
+
};
|
|
352
|
+
/**
|
|
353
|
+
* Sets the current request context. Extracts the request URL from the provided
|
|
354
|
+
* context and stores it for use in pagination link generation.
|
|
355
|
+
*
|
|
356
|
+
* Can be called from middleware to make the request URL available to all
|
|
357
|
+
* resources created during the request lifecycle.
|
|
358
|
+
*
|
|
359
|
+
* @param ctx An HTTP context `{ req, res }`, Express Request, H3 HTTPEvent, or bare request.
|
|
360
|
+
*/
|
|
361
|
+
const setCtx = (ctx) => {
|
|
362
|
+
setRequestUrl(extractRequestUrl(ctx));
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
//#endregion
|
|
366
|
+
//#region src/utilities/objects.ts
|
|
367
|
+
/**
|
|
368
|
+
* Utility functions for working with objects, including type checking, merging, and property manipulation.
|
|
369
|
+
*
|
|
370
|
+
* @param value The value to check.
|
|
371
|
+
* @returns `true` if the value is a plain object, `false` otherwise.
|
|
372
|
+
*/
|
|
373
|
+
const isPlainObject = (value) => {
|
|
374
|
+
if (typeof value !== "object" || value === null) return false;
|
|
375
|
+
if (Array.isArray(value) || value instanceof Date || value instanceof RegExp) return false;
|
|
376
|
+
const proto = Object.getPrototypeOf(value);
|
|
377
|
+
return proto === Object.prototype || proto === null;
|
|
378
|
+
};
|
|
379
|
+
/**
|
|
380
|
+
* Appends extra properties to a response body, ensuring that the main data is wrapped under a specified root key if necessary.
|
|
381
|
+
*
|
|
382
|
+
* @param body The original response body, which can be an object, array, or primitive value.
|
|
383
|
+
* @param extra Extra properties to append to the response body.
|
|
384
|
+
* @param rootKey The root key under which to wrap the main data if necessary.
|
|
385
|
+
* @returns The response body with the extra properties appended.
|
|
386
|
+
*/
|
|
387
|
+
const appendRootProperties = (body, extra, rootKey = "data") => {
|
|
388
|
+
if (!extra || Object.keys(extra).length === 0) return body;
|
|
389
|
+
if (Array.isArray(body)) return {
|
|
390
|
+
[rootKey]: body,
|
|
391
|
+
...extra
|
|
392
|
+
};
|
|
393
|
+
if (isPlainObject(body)) return {
|
|
394
|
+
...body,
|
|
395
|
+
...extra
|
|
396
|
+
};
|
|
397
|
+
return {
|
|
398
|
+
[rootKey]: body,
|
|
399
|
+
...extra
|
|
400
|
+
};
|
|
401
|
+
};
|
|
402
|
+
/**
|
|
403
|
+
* Deeply merges two metadata objects, combining nested objects recursively.
|
|
404
|
+
*
|
|
405
|
+
* @param base The base metadata object to merge into.
|
|
406
|
+
* @param incoming The incoming metadata object to merge from.
|
|
407
|
+
* @returns
|
|
408
|
+
*/
|
|
409
|
+
const mergeMetadata = (base, incoming) => {
|
|
410
|
+
if (!incoming) return base;
|
|
411
|
+
if (!base) return incoming;
|
|
412
|
+
const merged = { ...base };
|
|
413
|
+
for (const [key, value] of Object.entries(incoming)) {
|
|
414
|
+
const existing = merged[key];
|
|
415
|
+
if (isPlainObject(existing) && isPlainObject(value)) merged[key] = mergeMetadata(existing, value);
|
|
416
|
+
else merged[key] = value;
|
|
417
|
+
}
|
|
418
|
+
return merged;
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
//#endregion
|
|
422
|
+
//#region src/utilities/arkorm.ts
|
|
423
|
+
/**
|
|
424
|
+
* Type guard to check if a value is an Arkorm-like model, which is defined as an object
|
|
425
|
+
* that has a toObject method and optionally getRawAttributes, getAttribute, and
|
|
426
|
+
* setAttribute methods.
|
|
427
|
+
*
|
|
428
|
+
* @param value The value to check
|
|
429
|
+
* @returns True if the value is an Arkorm-like model, false otherwise
|
|
430
|
+
*/
|
|
431
|
+
const isArkormLikeModel = (value) => {
|
|
432
|
+
if (!value || typeof value !== "object") return false;
|
|
433
|
+
const candidate = value;
|
|
434
|
+
return typeof candidate.toObject === "function" && typeof candidate.getRawAttributes === "function";
|
|
435
|
+
};
|
|
436
|
+
/**
|
|
437
|
+
* Type guard to check if a value is an Arkorm-like collection, which is defined as an object
|
|
438
|
+
*
|
|
439
|
+
* @param value
|
|
440
|
+
* @returns
|
|
441
|
+
*/
|
|
442
|
+
const isArkormLikeCollection = (value) => {
|
|
443
|
+
if (!value || typeof value !== "object") return false;
|
|
444
|
+
return typeof value.all === "function";
|
|
445
|
+
};
|
|
446
|
+
/**
|
|
447
|
+
* Normalize a value for serialization by recursively converting Arkorm-like models and
|
|
448
|
+
* collections to plain objects, while preserving the structure of arrays and plain objects.
|
|
449
|
+
*
|
|
450
|
+
* @param value The value to normalize
|
|
451
|
+
* @returns The normalized value, ready for serialization
|
|
452
|
+
*/
|
|
453
|
+
const normalizeSerializableData = (value) => {
|
|
454
|
+
if (Array.isArray(value)) return value.map((item) => normalizeSerializableData(item));
|
|
455
|
+
if (isArkormLikeModel(value)) return normalizeSerializableData(value.toObject());
|
|
456
|
+
if (isArkormLikeCollection(value)) {
|
|
457
|
+
const collectionData = value.all();
|
|
458
|
+
if (Array.isArray(collectionData)) return collectionData.map((item) => normalizeSerializableData(item));
|
|
459
|
+
return normalizeSerializableData(collectionData);
|
|
460
|
+
}
|
|
461
|
+
if (isPlainObject(value)) return Object.entries(value).reduce((accumulator, [key, nestedValue]) => {
|
|
462
|
+
accumulator[key] = normalizeSerializableData(nestedValue);
|
|
463
|
+
return accumulator;
|
|
464
|
+
}, {});
|
|
465
|
+
return value;
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
//#endregion
|
|
469
|
+
//#region src/utilities/pagination.ts
|
|
470
|
+
/**
|
|
471
|
+
* Retrieves the configured keys for pagination extras (meta, links, cursor) based on the application's configuration.
|
|
472
|
+
*
|
|
473
|
+
* @returns An object containing the keys for meta, links, and cursor extras, or `undefined` if not configured.
|
|
474
|
+
*/
|
|
475
|
+
const getPaginationExtraKeys = () => {
|
|
476
|
+
const extras = getGlobalPaginatedExtras();
|
|
477
|
+
if (Array.isArray(extras)) return {
|
|
478
|
+
metaKey: extras.includes("meta") ? "meta" : void 0,
|
|
479
|
+
linksKey: extras.includes("links") ? "links" : void 0,
|
|
480
|
+
cursorKey: extras.includes("cursor") ? "cursor" : void 0
|
|
481
|
+
};
|
|
482
|
+
return {
|
|
483
|
+
metaKey: extras.meta,
|
|
484
|
+
linksKey: extras.links,
|
|
485
|
+
cursorKey: extras.cursor
|
|
486
|
+
};
|
|
487
|
+
};
|
|
488
|
+
const parsePageValue = (value) => {
|
|
489
|
+
if (value === null) return;
|
|
490
|
+
const page = Number(value);
|
|
491
|
+
if (!Number.isInteger(page) || page < 1) return;
|
|
492
|
+
return page;
|
|
493
|
+
};
|
|
494
|
+
const getPageValueFromUrl = (requestUrl, pageName) => {
|
|
495
|
+
try {
|
|
496
|
+
return new URL(requestUrl, "http://resora.local").searchParams.get(pageName);
|
|
497
|
+
} catch {
|
|
498
|
+
const qIndex = requestUrl.indexOf("?");
|
|
499
|
+
if (qIndex < 0) return null;
|
|
500
|
+
return new URLSearchParams(requestUrl.slice(qIndex + 1)).get(pageName);
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
/**
|
|
504
|
+
* Resolves the current page number from a request context or the globally
|
|
505
|
+
* stored request URL, using the configured page query name by default.
|
|
506
|
+
*
|
|
507
|
+
* @param ctx Optional request context or URL.
|
|
508
|
+
* @param pageName Optional page query parameter name.
|
|
509
|
+
* @returns
|
|
510
|
+
*/
|
|
511
|
+
const resolveCurrentPage = (ctx, pageName = getGlobalPageName() || "page") => {
|
|
512
|
+
const requestUrl = typeof ctx === "undefined" ? getRequestUrl() : extractRequestUrl(ctx);
|
|
513
|
+
if (!requestUrl) return;
|
|
514
|
+
return parsePageValue(getPageValueFromUrl(requestUrl, pageName));
|
|
515
|
+
};
|
|
516
|
+
/**
|
|
517
|
+
* Creates an Arkorm-compatible current-page resolver backed by Resora's
|
|
518
|
+
* request-context URL parsing.
|
|
519
|
+
*
|
|
520
|
+
* @param ctx Optional request context or URL.
|
|
521
|
+
* @returns
|
|
522
|
+
*/
|
|
523
|
+
const createArkormCurrentPageResolver = (ctx) => {
|
|
524
|
+
return (pageName) => resolveCurrentPage(ctx, pageName);
|
|
525
|
+
};
|
|
526
|
+
/**
|
|
527
|
+
* Builds a pagination URL for a given page number and path, using the global base URL and page name configuration.
|
|
528
|
+
*
|
|
529
|
+
* URL resolution follows a three-tier priority:
|
|
530
|
+
* 1. Full URL – when `pathName` is absolute or `baseUrl` is set alongside a path
|
|
531
|
+
* 2. Path-relative – when only a path is available (e.g. `/users?page=2`)
|
|
532
|
+
* 3. Bare fallback – `/?page=X` when neither base URL nor path is available
|
|
533
|
+
*
|
|
534
|
+
* @param page The page number for which to build the URL. If `undefined`, the function will return `undefined`.
|
|
535
|
+
* @param pathName The path to use for the URL. If not provided, it will default to an empty string.
|
|
536
|
+
* @returns
|
|
537
|
+
*/
|
|
538
|
+
const buildPageUrl = (page, pathName) => {
|
|
539
|
+
if (typeof page === "undefined") return;
|
|
540
|
+
const rawPath = pathName || getRequestUrl() || "";
|
|
541
|
+
const base = getGlobalBaseUrl() || "";
|
|
542
|
+
const pageName = getGlobalPageName() || "page";
|
|
543
|
+
const qIndex = rawPath.indexOf("?");
|
|
544
|
+
const pathOnly = qIndex >= 0 ? rawPath.slice(0, qIndex) : rawPath;
|
|
545
|
+
const existingSearch = qIndex >= 0 ? rawPath.slice(qIndex + 1) : "";
|
|
546
|
+
if (/^https?:\/\//i.test(pathOnly)) {
|
|
547
|
+
const url = new URL(rawPath);
|
|
548
|
+
url.searchParams.set(pageName, String(page));
|
|
549
|
+
return url.toString();
|
|
550
|
+
}
|
|
551
|
+
const normalizedBase = base.replace(/\/$/, "");
|
|
552
|
+
const normalizedPath = pathOnly.replace(/^\//, "");
|
|
553
|
+
if (/^https?:\/\//i.test(normalizedBase)) {
|
|
554
|
+
const root = normalizedPath ? `${normalizedBase}/${normalizedPath}` : normalizedBase;
|
|
555
|
+
const url = new URL(root);
|
|
556
|
+
if (existingSearch) for (const [k, v] of new URLSearchParams(existingSearch)) url.searchParams.set(k, v);
|
|
557
|
+
url.searchParams.set(pageName, String(page));
|
|
558
|
+
return url.toString();
|
|
559
|
+
}
|
|
560
|
+
const segments = [normalizedBase, normalizedPath].filter(Boolean).join("/");
|
|
561
|
+
const pathBase = segments ? `/${segments}` : "/";
|
|
562
|
+
const params = new URLSearchParams(existingSearch);
|
|
563
|
+
params.set(pageName, String(page));
|
|
564
|
+
return `${pathBase}?${params.toString()}`;
|
|
565
|
+
};
|
|
566
|
+
/**
|
|
567
|
+
* Derives page numbers (firstPage, lastPage, nextPage, prevPage) from an
|
|
568
|
+
* Arkorm-style pagination meta object so that buildPageUrl() can generate
|
|
569
|
+
* links that respect the auto-detected request URL and query string.
|
|
570
|
+
*/
|
|
571
|
+
const derivePageNumbers = (meta) => {
|
|
572
|
+
const currentPage = meta.currentPage;
|
|
573
|
+
const lastPage = meta.lastPage;
|
|
574
|
+
const hasMorePages = meta.hasMorePages;
|
|
575
|
+
return {
|
|
576
|
+
firstPage: typeof lastPage === "number" ? 1 : void 0,
|
|
577
|
+
lastPage,
|
|
578
|
+
nextPage: typeof lastPage === "number" ? typeof currentPage === "number" && currentPage < lastPage ? currentPage + 1 : void 0 : hasMorePages && typeof currentPage === "number" ? currentPage + 1 : void 0,
|
|
579
|
+
prevPage: typeof currentPage === "number" && currentPage > 1 ? currentPage - 1 : void 0
|
|
580
|
+
};
|
|
581
|
+
};
|
|
582
|
+
/**
|
|
583
|
+
* Extracts a pagination link (e.g. 'first', 'last', 'prev', 'next') from a
|
|
584
|
+
* pagination object, if it exists.
|
|
585
|
+
*
|
|
586
|
+
* @param pagination
|
|
587
|
+
* @param rel
|
|
588
|
+
* @returns
|
|
589
|
+
*/
|
|
590
|
+
const hasPaginationLink = (pagination, rel) => {
|
|
591
|
+
return pagination.links && Object.prototype.hasOwnProperty.call(pagination.links, rel);
|
|
592
|
+
};
|
|
593
|
+
/**
|
|
594
|
+
* Builds pagination extras (meta, links, cursor) for a given resource based on
|
|
595
|
+
* its pagination and cursor properties, using the configured keys for each type of extra.
|
|
596
|
+
*
|
|
597
|
+
* @param resource The resource for which to build pagination extras.
|
|
598
|
+
* @returns An object containing the pagination extras (meta, links, cursor) for the resource.
|
|
599
|
+
*/
|
|
600
|
+
const buildPaginationExtras = (resource) => {
|
|
601
|
+
const { metaKey, linksKey, cursorKey } = getPaginationExtraKeys();
|
|
602
|
+
const extra = {};
|
|
603
|
+
const isArkormPaginatorLike = !!resource && typeof resource === "object" && !!resource.meta && typeof resource.meta === "object" && (Array.isArray(resource.data) || isArkormLikeCollection(resource.data));
|
|
604
|
+
const arkormPageNumbers = isArkormPaginatorLike ? derivePageNumbers(resource.meta) : void 0;
|
|
605
|
+
const arkormFallbackLinks = isArkormPaginatorLike ? {
|
|
606
|
+
first: typeof resource.firstPageUrl === "function" ? resource.firstPageUrl() : void 0,
|
|
607
|
+
last: typeof resource.lastPageUrl === "function" ? resource.lastPageUrl() : void 0,
|
|
608
|
+
prev: typeof resource.previousPageUrl === "function" ? resource.previousPageUrl() : void 0,
|
|
609
|
+
next: typeof resource.nextPageUrl === "function" ? resource.nextPageUrl() : void 0
|
|
610
|
+
} : void 0;
|
|
611
|
+
const pagination = resource?.pagination || (isArkormPaginatorLike ? {
|
|
612
|
+
...resource.meta,
|
|
613
|
+
...arkormPageNumbers || {},
|
|
614
|
+
path: resource.urlDriver?.path
|
|
615
|
+
} : void 0);
|
|
616
|
+
const cursor = resource?.cursor;
|
|
617
|
+
const metaBlock = {};
|
|
618
|
+
const linksBlock = {};
|
|
619
|
+
if (pagination) {
|
|
620
|
+
const effectivePath = getRequestUrl() || pagination.path;
|
|
621
|
+
const currentPagePath = effectivePath ? buildPageUrl(pagination.currentPage, effectivePath) ?? effectivePath : void 0;
|
|
622
|
+
const linksSource = {
|
|
623
|
+
first: hasPaginationLink(pagination, "first") ? pagination.links.first : buildPageUrl(pagination.firstPage, effectivePath) ?? arkormFallbackLinks?.first,
|
|
624
|
+
last: hasPaginationLink(pagination, "last") ? pagination.links.last : buildPageUrl(pagination.lastPage, effectivePath) ?? arkormFallbackLinks?.last,
|
|
625
|
+
prev: hasPaginationLink(pagination, "prev") ? pagination.links.prev : buildPageUrl(pagination.prevPage, effectivePath) ?? arkormFallbackLinks?.prev,
|
|
626
|
+
next: hasPaginationLink(pagination, "next") ? pagination.links.next : buildPageUrl(pagination.nextPage, effectivePath) ?? arkormFallbackLinks?.next
|
|
627
|
+
};
|
|
628
|
+
const resolvedLinks = Object.fromEntries(Object.entries(linksSource).filter(([, v]) => v !== void 0));
|
|
629
|
+
const metaSource = {
|
|
630
|
+
to: pagination.to,
|
|
631
|
+
from: pagination.from,
|
|
632
|
+
links: pagination.links || (isArkormPaginatorLike && Object.keys(resolvedLinks).length ? resolvedLinks : void 0),
|
|
633
|
+
path: currentPagePath,
|
|
634
|
+
total: pagination.total,
|
|
635
|
+
per_page: pagination.perPage,
|
|
636
|
+
last_page: pagination.lastPage,
|
|
637
|
+
current_page: pagination.currentPage
|
|
638
|
+
};
|
|
639
|
+
for (const [sourceKey, outputKey] of Object.entries(getGlobalPaginatedMeta())) {
|
|
640
|
+
if (!outputKey) continue;
|
|
641
|
+
const value = metaSource[sourceKey];
|
|
642
|
+
if (typeof value !== "undefined") metaBlock[outputKey] = value;
|
|
643
|
+
}
|
|
644
|
+
for (const [sourceKey, outputKey] of Object.entries(getGlobalPaginatedLinks())) {
|
|
645
|
+
if (!outputKey) continue;
|
|
646
|
+
const value = linksSource[sourceKey];
|
|
647
|
+
if (typeof value !== "undefined") linksBlock[outputKey] = value;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (cursor) {
|
|
651
|
+
const cursorBlock = {};
|
|
652
|
+
const cursorSource = {
|
|
653
|
+
previous: cursor.previous,
|
|
654
|
+
next: cursor.next
|
|
655
|
+
};
|
|
656
|
+
for (const [sourceKey, outputKey] of Object.entries(getGlobalCursorMeta())) {
|
|
657
|
+
if (!outputKey) continue;
|
|
658
|
+
const value = cursorSource[sourceKey];
|
|
659
|
+
if (typeof value !== "undefined") cursorBlock[outputKey] = value;
|
|
660
|
+
}
|
|
661
|
+
if (cursorKey && Object.keys(cursorBlock).length > 0) extra[cursorKey] = cursorBlock;
|
|
662
|
+
else if (Object.keys(cursorBlock).length > 0) metaBlock.cursor = cursorBlock;
|
|
663
|
+
}
|
|
664
|
+
if (metaKey && Object.keys(metaBlock).length > 0) extra[metaKey] = metaBlock;
|
|
665
|
+
if (linksKey && Object.keys(linksBlock).length > 0) extra[linksKey] = linksBlock;
|
|
666
|
+
return extra;
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
//#endregion
|
|
670
|
+
//#region src/utilities/response.ts
|
|
671
|
+
/**
|
|
672
|
+
* Builds a response envelope based on the provided payload,
|
|
673
|
+
* metadata, and configuration options.
|
|
674
|
+
*
|
|
675
|
+
* @param config The configuration object containing payload, metadata, and other options for building the response.
|
|
676
|
+
*/
|
|
677
|
+
const buildResponseEnvelope = ({ payload, meta, metaKey = "meta", wrap = true, rootKey = "data", factory, context }) => {
|
|
678
|
+
if (factory) return factory(payload, {
|
|
679
|
+
...context,
|
|
680
|
+
rootKey,
|
|
681
|
+
meta
|
|
682
|
+
});
|
|
683
|
+
if (!wrap) {
|
|
684
|
+
if (typeof meta === "undefined") return payload;
|
|
685
|
+
if (isPlainObject(payload)) return {
|
|
686
|
+
...payload,
|
|
687
|
+
[metaKey]: meta
|
|
688
|
+
};
|
|
689
|
+
return {
|
|
690
|
+
[rootKey]: payload,
|
|
691
|
+
[metaKey]: meta
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
const body = { [rootKey]: payload };
|
|
695
|
+
if (typeof meta !== "undefined") body[metaKey] = meta;
|
|
696
|
+
return body;
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
//#endregion
|
|
700
|
+
//#region src/utilities/metadata.ts
|
|
701
|
+
/**
|
|
702
|
+
* Resolves metadata from a resource instance by checking for a custom `with` method.
|
|
703
|
+
* If the method exists and is different from the base method, it calls it to retrieve metadata.
|
|
704
|
+
* This allows resources to provide additional metadata for response construction.
|
|
705
|
+
*
|
|
706
|
+
* @param instance The resource instance to check for a custom `with` method.
|
|
707
|
+
* @param baseWithMethod The base `with` method to compare against.
|
|
708
|
+
* @returns The resolved metadata or `undefined` if no custom metadata is provided.
|
|
709
|
+
*/
|
|
710
|
+
const resolveWithHookMetadata = (instance, baseWithMethod) => {
|
|
711
|
+
const candidate = instance?.with;
|
|
712
|
+
if (typeof candidate !== "function" || candidate === baseWithMethod) return;
|
|
713
|
+
if (candidate.length > 0) return;
|
|
714
|
+
const result = candidate.call(instance);
|
|
715
|
+
return isPlainObject(result) ? result : void 0;
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
//#endregion
|
|
719
|
+
//#region src/utilities/conditional.ts
|
|
720
|
+
const CONDITIONAL_ATTRIBUTE_MISSING = Symbol("resora.conditional.missing");
|
|
721
|
+
/**
|
|
722
|
+
* Resolves a value based on a condition. If the condition is falsy, it returns a special symbol indicating that the attribute is missing.
|
|
723
|
+
*
|
|
724
|
+
*
|
|
725
|
+
* @param condition The condition to evaluate.
|
|
726
|
+
* @param value The value or function to resolve if the condition is truthy.
|
|
727
|
+
* @returns The resolved value or a symbol indicating the attribute is missing.
|
|
728
|
+
*/
|
|
729
|
+
const resolveWhen = (condition, value) => {
|
|
730
|
+
if (!condition) return CONDITIONAL_ATTRIBUTE_MISSING;
|
|
731
|
+
return typeof value === "function" ? value() : value;
|
|
732
|
+
};
|
|
733
|
+
/**
|
|
734
|
+
* Resolves a value only if it is not null or undefined.
|
|
735
|
+
*
|
|
736
|
+
* @param value The value to resolve.
|
|
737
|
+
* @returns The resolved value or a symbol indicating the attribute is missing.
|
|
738
|
+
*/
|
|
739
|
+
const resolveWhenNotNull = (value) => {
|
|
740
|
+
return value === null || typeof value === "undefined" ? CONDITIONAL_ATTRIBUTE_MISSING : value;
|
|
741
|
+
};
|
|
742
|
+
/**
|
|
743
|
+
* Conditionally merges object attributes based on a condition.
|
|
744
|
+
*
|
|
745
|
+
* @param condition
|
|
746
|
+
* @param value
|
|
747
|
+
* @returns
|
|
748
|
+
*/
|
|
749
|
+
const resolveMergeWhen = (condition, value) => {
|
|
750
|
+
if (!condition) return {};
|
|
751
|
+
const resolved = typeof value === "function" ? value() : value;
|
|
752
|
+
return isPlainObject(resolved) ? resolved : {};
|
|
753
|
+
};
|
|
754
|
+
/**
|
|
755
|
+
* Recursively sanitizes an object or array by removing any attributes that are marked as missing using the special symbol.
|
|
756
|
+
*
|
|
757
|
+
* @param value The value to sanitize.
|
|
758
|
+
* @returns The sanitized value.
|
|
759
|
+
*/
|
|
760
|
+
const sanitizeConditionalAttributes = (value) => {
|
|
761
|
+
if (value === CONDITIONAL_ATTRIBUTE_MISSING) return CONDITIONAL_ATTRIBUTE_MISSING;
|
|
762
|
+
if (Array.isArray(value)) return value.map((item) => sanitizeConditionalAttributes(item)).filter((item) => item !== CONDITIONAL_ATTRIBUTE_MISSING);
|
|
763
|
+
if (isPlainObject(value)) {
|
|
764
|
+
const result = {};
|
|
765
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
766
|
+
const sanitizedValue = sanitizeConditionalAttributes(nestedValue);
|
|
767
|
+
if (sanitizedValue === CONDITIONAL_ATTRIBUTE_MISSING) continue;
|
|
768
|
+
result[key] = sanitizedValue;
|
|
769
|
+
}
|
|
770
|
+
return result;
|
|
771
|
+
}
|
|
772
|
+
return value;
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
//#endregion
|
|
776
|
+
//#region src/utilities/case.ts
|
|
777
|
+
/**
|
|
778
|
+
* Splits a string into words based on common delimiters and capitalization patterns.
|
|
779
|
+
* Handles camelCase, PascalCase, snake_case, kebab-case, and space-separated words.
|
|
780
|
+
*
|
|
781
|
+
* @param str
|
|
782
|
+
* @returns
|
|
783
|
+
*/
|
|
784
|
+
const splitWords = (str) => {
|
|
785
|
+
return str.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[-_\s]+/g, " ").trim().toLowerCase().split(" ").filter(Boolean);
|
|
786
|
+
};
|
|
787
|
+
/**
|
|
788
|
+
* Transforms a string to camelCase.
|
|
789
|
+
*
|
|
790
|
+
* @param str
|
|
791
|
+
* @returns
|
|
792
|
+
*/
|
|
793
|
+
const toCamelCase = (str) => {
|
|
794
|
+
return splitWords(str).map((w, i) => i === 0 ? w : w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
795
|
+
};
|
|
796
|
+
/**
|
|
797
|
+
* Transforms a string to snake_case.
|
|
798
|
+
*
|
|
799
|
+
* @param str
|
|
800
|
+
* @returns
|
|
801
|
+
*/
|
|
802
|
+
const toSnakeCase = (str) => {
|
|
803
|
+
return splitWords(str).join("_");
|
|
804
|
+
};
|
|
805
|
+
/**
|
|
806
|
+
* Transforms a string to PascalCase.
|
|
807
|
+
*
|
|
808
|
+
* @param str
|
|
809
|
+
* @returns
|
|
810
|
+
*/
|
|
811
|
+
const toPascalCase = (str) => {
|
|
812
|
+
return splitWords(str).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
813
|
+
};
|
|
814
|
+
/**
|
|
815
|
+
* Transforms a string to kebab-case.
|
|
816
|
+
*
|
|
817
|
+
* @param str
|
|
818
|
+
* @returns
|
|
819
|
+
*/
|
|
820
|
+
const toKebabCase = (str) => {
|
|
821
|
+
return splitWords(str).join("-");
|
|
822
|
+
};
|
|
823
|
+
/**
|
|
824
|
+
* Retrieves the appropriate case transformation function based on the provided style.
|
|
825
|
+
*
|
|
826
|
+
* @param style
|
|
827
|
+
* @returns
|
|
828
|
+
*/
|
|
829
|
+
const getCaseTransformer = (style) => {
|
|
830
|
+
if (typeof style === "function") return style;
|
|
831
|
+
switch (style) {
|
|
832
|
+
case "camel": return toCamelCase;
|
|
833
|
+
case "snake": return toSnakeCase;
|
|
834
|
+
case "pascal": return toPascalCase;
|
|
835
|
+
case "kebab": return toKebabCase;
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
/**
|
|
839
|
+
* Transforms the keys of an object (including nested objects and arrays) using
|
|
840
|
+
* the provided transformer function.
|
|
841
|
+
*
|
|
842
|
+
* @param obj
|
|
843
|
+
* @param transformer
|
|
844
|
+
* @returns
|
|
845
|
+
*/
|
|
846
|
+
const transformKeys = (obj, transformer) => {
|
|
847
|
+
if (obj === null || obj === void 0) return obj;
|
|
848
|
+
if (Array.isArray(obj)) return obj.map((item) => transformKeys(item, transformer));
|
|
849
|
+
if (obj instanceof Date || obj instanceof RegExp) return obj;
|
|
850
|
+
if (typeof obj === "object") return Object.fromEntries(Object.entries(obj).map(([key, value]) => [transformer(key), transformKeys(value, transformer)]));
|
|
851
|
+
return obj;
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
//#endregion
|
|
855
|
+
//#region src/utilities/config.ts
|
|
856
|
+
let stubsDir = path.default.resolve(process.cwd(), "node_modules/resora/stubs");
|
|
857
|
+
if (!(0, fs.existsSync)(stubsDir)) stubsDir = path.default.resolve(process.cwd(), "stubs");
|
|
858
|
+
/**
|
|
859
|
+
* Get the default configuration for the application
|
|
860
|
+
*
|
|
861
|
+
* @returns
|
|
862
|
+
*/
|
|
863
|
+
const getDefaultConfig = () => {
|
|
864
|
+
return {
|
|
865
|
+
stubsDir,
|
|
866
|
+
preferredCase: "camel",
|
|
867
|
+
responseStructure: {
|
|
868
|
+
wrap: true,
|
|
869
|
+
rootKey: "data"
|
|
870
|
+
},
|
|
871
|
+
paginatedExtras: ["meta", "links"],
|
|
872
|
+
baseUrl: "",
|
|
873
|
+
pageName: "page",
|
|
874
|
+
paginatedLinks: {
|
|
875
|
+
first: "first",
|
|
876
|
+
last: "last",
|
|
877
|
+
prev: "prev",
|
|
878
|
+
next: "next"
|
|
879
|
+
},
|
|
880
|
+
paginatedMeta: {
|
|
881
|
+
to: "to",
|
|
882
|
+
from: "from",
|
|
883
|
+
links: "links",
|
|
884
|
+
path: "path",
|
|
885
|
+
total: "total",
|
|
886
|
+
per_page: "per_page",
|
|
887
|
+
last_page: "last_page",
|
|
888
|
+
current_page: "current_page"
|
|
889
|
+
},
|
|
890
|
+
cursorMeta: {
|
|
891
|
+
previous: "previous",
|
|
892
|
+
next: "next"
|
|
893
|
+
},
|
|
894
|
+
resourcesDir: "src/resources",
|
|
895
|
+
stubs: {
|
|
896
|
+
config: "resora.config.stub",
|
|
897
|
+
resource: "resource.stub",
|
|
898
|
+
collection: "resource.collection.stub"
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
};
|
|
902
|
+
/**
|
|
903
|
+
* Defines the configuration for the application by merging the provided configuration with the default configuration. This function takes a partial configuration object as input and returns a complete configuration object that includes all required properties, using default values for any properties that are not specified in the input.
|
|
904
|
+
*
|
|
905
|
+
* @param config
|
|
906
|
+
* @returns
|
|
907
|
+
*/
|
|
908
|
+
const defineConfig = (config) => {
|
|
909
|
+
const defConf = getDefaultConfig();
|
|
910
|
+
return Object.assign(defConf, config, { stubs: Object.assign(defConf.stubs, config.stubs || {}) }, { cursorMeta: Object.assign(defConf.cursorMeta, config.cursorMeta || {}) }, { paginatedMeta: config.paginatedMeta || defConf.paginatedMeta }, { paginatedLinks: Object.assign(defConf.paginatedLinks, config.paginatedLinks || {}) }, { responseStructure: Object.assign(defConf.responseStructure, config.responseStructure || {}) });
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
//#endregion
|
|
914
|
+
//#region src/utilities/runtime-config.ts
|
|
915
|
+
let runtimeConfigLoaded = false;
|
|
916
|
+
let runtimeConfigLoadingPromise;
|
|
917
|
+
/**
|
|
918
|
+
* Resets the runtime configuration state for testing purposes.
|
|
919
|
+
*
|
|
920
|
+
* @returns
|
|
921
|
+
*/
|
|
922
|
+
const resetRuntimeConfigForTests = () => {
|
|
923
|
+
runtimeConfigLoaded = false;
|
|
924
|
+
runtimeConfigLoadingPromise = void 0;
|
|
925
|
+
};
|
|
926
|
+
/**
|
|
927
|
+
* Applies the provided configuration to the global state of the application.
|
|
928
|
+
*
|
|
929
|
+
* @param config The complete configuration object to apply.
|
|
930
|
+
*/
|
|
931
|
+
const applyRuntimeConfig = (config) => {
|
|
932
|
+
if (config.preferredCase !== "camel") setGlobalCase(config.preferredCase);
|
|
933
|
+
setGlobalResponseStructure(config.responseStructure);
|
|
934
|
+
setGlobalPaginatedExtras(config.paginatedExtras);
|
|
935
|
+
setGlobalPaginatedLinks(config.paginatedLinks);
|
|
936
|
+
setGlobalPaginatedMeta(config.paginatedMeta);
|
|
937
|
+
setGlobalCursorMeta(config.cursorMeta);
|
|
938
|
+
setGlobalBaseUrl(config.baseUrl);
|
|
939
|
+
setGlobalPageName(config.pageName);
|
|
940
|
+
};
|
|
941
|
+
/**
|
|
942
|
+
* Loads the runtime configuration by searching for configuration files in the current working directory.
|
|
943
|
+
* @param configPath The path to the configuration file to load.
|
|
944
|
+
* @returns
|
|
945
|
+
*/
|
|
946
|
+
const importConfigFile = async (configPath) => {
|
|
947
|
+
return await import(`${(0, url.pathToFileURL)(configPath).href}?resora_runtime=${Date.now()}`);
|
|
948
|
+
};
|
|
949
|
+
/**
|
|
950
|
+
* Resolves the imported configuration and applies it to the global state.
|
|
951
|
+
*
|
|
952
|
+
* @param imported
|
|
953
|
+
*/
|
|
954
|
+
const resolveAndApply = (imported) => {
|
|
955
|
+
applyRuntimeConfig(defineConfig((imported?.default ?? imported) || {}));
|
|
956
|
+
runtimeConfigLoaded = true;
|
|
957
|
+
};
|
|
958
|
+
/**
|
|
959
|
+
* Loads the runtime configuration synchronously by searching for CommonJS configuration files in the current working directory.
|
|
960
|
+
*
|
|
961
|
+
* @returns
|
|
962
|
+
*/
|
|
963
|
+
const loadRuntimeConfigSync = () => {
|
|
964
|
+
const require = (0, module$1.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
965
|
+
const syncConfigPaths = [path.default.join(process.cwd(), "resora.config.cjs")];
|
|
966
|
+
for (const configPath of syncConfigPaths) {
|
|
967
|
+
if (!(0, fs.existsSync)(configPath)) continue;
|
|
968
|
+
try {
|
|
969
|
+
resolveAndApply(require(configPath));
|
|
970
|
+
return true;
|
|
971
|
+
} catch {
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
return false;
|
|
976
|
+
};
|
|
977
|
+
/**
|
|
978
|
+
* Loads the runtime configuration by searching for configuration files in the current working directory.
|
|
979
|
+
*
|
|
980
|
+
* @returns
|
|
981
|
+
*/
|
|
982
|
+
const loadRuntimeConfig = async () => {
|
|
983
|
+
if (runtimeConfigLoaded) return;
|
|
984
|
+
if (runtimeConfigLoadingPromise) return await runtimeConfigLoadingPromise;
|
|
985
|
+
if (loadRuntimeConfigSync()) return;
|
|
986
|
+
runtimeConfigLoadingPromise = (async () => {
|
|
987
|
+
const possibleConfigPaths = [path.default.join(process.cwd(), "resora.config.js"), path.default.join(process.cwd(), "resora.config.ts")];
|
|
988
|
+
for (const configPath of possibleConfigPaths) {
|
|
989
|
+
if (!(0, fs.existsSync)(configPath)) continue;
|
|
990
|
+
try {
|
|
991
|
+
resolveAndApply(await importConfigFile(configPath));
|
|
992
|
+
return;
|
|
993
|
+
} catch {
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
runtimeConfigLoaded = true;
|
|
998
|
+
})();
|
|
999
|
+
await runtimeConfigLoadingPromise;
|
|
1000
|
+
};
|
|
1001
|
+
loadRuntimeConfig();
|
|
1002
|
+
|
|
1003
|
+
//#endregion
|
|
1004
|
+
//#region src/cli/CliApp.ts
|
|
1005
|
+
var CliApp = class {
|
|
1006
|
+
command;
|
|
1007
|
+
config = {};
|
|
1008
|
+
constructor(config = {}) {
|
|
1009
|
+
this.config = defineConfig(config);
|
|
1010
|
+
}
|
|
1011
|
+
async loadConfig(config = {}) {
|
|
1012
|
+
this.config = defineConfig(config);
|
|
1013
|
+
const possibleConfigPaths = [
|
|
1014
|
+
(0, path.join)(process.cwd(), "resora.config.ts"),
|
|
1015
|
+
(0, path.join)(process.cwd(), "resora.config.js"),
|
|
1016
|
+
(0, path.join)(process.cwd(), "resora.config.cjs")
|
|
1017
|
+
];
|
|
1018
|
+
for (const configPath of possibleConfigPaths) if ((0, fs.existsSync)(configPath)) try {
|
|
1019
|
+
const { default: userConfig } = await import(configPath);
|
|
1020
|
+
Object.assign(this.config, userConfig);
|
|
1021
|
+
break;
|
|
1022
|
+
} catch (e) {
|
|
1023
|
+
console.error(`Error loading config file at ${configPath}:`, e);
|
|
1024
|
+
}
|
|
1025
|
+
return this;
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Get the current configuration object
|
|
1029
|
+
* @returns
|
|
1030
|
+
*/
|
|
1031
|
+
getConfig() {
|
|
1032
|
+
return this.config;
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Initialize Resora by creating a default config file in the current directory
|
|
1036
|
+
*
|
|
1037
|
+
* @returns
|
|
1038
|
+
*/
|
|
1039
|
+
init() {
|
|
1040
|
+
const outputPath = (0, path.join)(process.cwd(), "resora.config.js");
|
|
1041
|
+
const stubPath = (0, path.join)(this.config.stubsDir, this.config.stubs.config);
|
|
1042
|
+
if ((0, fs.existsSync)(outputPath) && !this.command.option("force")) {
|
|
1043
|
+
this.command.error(`Error: ${outputPath} already exists.`);
|
|
1044
|
+
process.exit(1);
|
|
1045
|
+
}
|
|
1046
|
+
this.ensureDirectory(outputPath);
|
|
1047
|
+
if ((0, fs.existsSync)(outputPath) && this.command.option("force")) (0, fs.copyFileSync)(outputPath, outputPath.replace(/\.js$/, `.backup.${Date.now()}.js`));
|
|
1048
|
+
(0, fs.writeFileSync)(outputPath, (0, fs.readFileSync)(stubPath, "utf-8"));
|
|
1049
|
+
return { path: outputPath };
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Utility to ensure directory exists
|
|
1053
|
+
*
|
|
1054
|
+
* @param filePath
|
|
1055
|
+
*/
|
|
1056
|
+
ensureDirectory(filePath) {
|
|
1057
|
+
const dir = (0, path.dirname)(filePath);
|
|
1058
|
+
if (!(0, fs.existsSync)(dir)) (0, fs.mkdirSync)(dir, { recursive: true });
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Utility to generate file from stub
|
|
1062
|
+
*
|
|
1063
|
+
* @param stubPath
|
|
1064
|
+
* @param outputPath
|
|
1065
|
+
* @param replacements
|
|
1066
|
+
*/
|
|
1067
|
+
generateFile(stubPath, outputPath, replacements, options) {
|
|
1068
|
+
if ((0, fs.existsSync)(outputPath) && !options?.force) {
|
|
1069
|
+
this.command.error(`Error: ${outputPath} already exists.`);
|
|
1070
|
+
process.exit(1);
|
|
1071
|
+
} else if ((0, fs.existsSync)(outputPath) && options?.force) (0, fs.rmSync)(outputPath);
|
|
1072
|
+
let content = (0, fs.readFileSync)(stubPath, "utf-8");
|
|
1073
|
+
for (const [key, value] of Object.entries(replacements)) content = content.replace(new RegExp(`{{${key}}}`, "g"), value);
|
|
1074
|
+
this.ensureDirectory(outputPath);
|
|
1075
|
+
(0, fs.writeFileSync)(outputPath, content);
|
|
1076
|
+
return outputPath;
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Command to create a new resource or resource collection file
|
|
1080
|
+
*
|
|
1081
|
+
* @param name
|
|
1082
|
+
* @param options
|
|
1083
|
+
*/
|
|
1084
|
+
makeResource(name, options) {
|
|
1085
|
+
let resourceName = name;
|
|
1086
|
+
if (options?.collection && !name.endsWith("Collection") && !name.endsWith("Resource")) resourceName += "Collection";
|
|
1087
|
+
else if (!options?.collection && !name.endsWith("Resource") && !name.endsWith("Collection")) resourceName += "Resource";
|
|
1088
|
+
const fileName = `${resourceName}.ts`;
|
|
1089
|
+
const outputPath = (0, path.join)(this.config.resourcesDir, fileName);
|
|
1090
|
+
const stubPath = (0, path.join)(this.config.stubsDir, options?.collection || name.endsWith("Collection") ? this.config.stubs.collection : this.config.stubs.resource);
|
|
1091
|
+
if (!(0, fs.existsSync)(stubPath)) {
|
|
1092
|
+
this.command.error(`Error: Stub file ${stubPath} not found.`);
|
|
1093
|
+
process.exit(1);
|
|
1094
|
+
}
|
|
1095
|
+
resourceName = resourceName.split("/").pop()?.split(".").shift();
|
|
1096
|
+
const collectsName = resourceName.replace(/(Resource|Collection)$/, "") + "Resource";
|
|
1097
|
+
const collects = [
|
|
1098
|
+
"/**",
|
|
1099
|
+
" * The resource that this collection collects.",
|
|
1100
|
+
" */",
|
|
1101
|
+
`collects = ${collectsName}`
|
|
1102
|
+
].join("\n");
|
|
1103
|
+
const collectsImport = `import ${collectsName} from './${collectsName}'\n`;
|
|
1104
|
+
const hasCollects = (!!options?.collection || name.endsWith("Collection")) && (0, fs.existsSync)((0, path.join)(this.config.resourcesDir, `${collectsName}.ts`));
|
|
1105
|
+
const path$3 = this.generateFile(stubPath, outputPath, {
|
|
1106
|
+
ResourceName: resourceName,
|
|
1107
|
+
CollectionResourceName: resourceName.replace(/(Resource|Collection)$/, "") + "Resource",
|
|
1108
|
+
"collects = Resource": hasCollects ? collects : "",
|
|
1109
|
+
"import = Resource": hasCollects ? collectsImport : ""
|
|
1110
|
+
}, options);
|
|
1111
|
+
return {
|
|
1112
|
+
name: resourceName,
|
|
1113
|
+
path: path$3
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
1117
|
+
|
|
1118
|
+
//#endregion
|
|
1119
|
+
//#region src/cli/commands/InitCommand.ts
|
|
1120
|
+
var InitCommand = class extends _h3ravel_musket.Command {
|
|
1121
|
+
signature = `init
|
|
3
1122
|
{--force : Force overwrite if config file already exists (existing file will be backed up) }
|
|
4
|
-
`;
|
|
1123
|
+
`;
|
|
1124
|
+
description = "Initialize Resora";
|
|
1125
|
+
async handle() {
|
|
1126
|
+
this.app.command = this;
|
|
1127
|
+
this.app.init();
|
|
1128
|
+
this.success("Resora initialized");
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
|
|
1132
|
+
//#endregion
|
|
1133
|
+
//#region src/cli/commands/MakeResource.ts
|
|
1134
|
+
var MakeResource = class extends _h3ravel_musket.Command {
|
|
1135
|
+
signature = `#create:
|
|
5
1136
|
{resource : Generates a new resource file.
|
|
6
1137
|
| {name : Name of the resource to create}
|
|
7
1138
|
| {--c|collection : Make a resource collection}
|
|
@@ -15,11 +1146,1408 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.
|
|
|
15
1146
|
| {prefix : prefix of the resources to create, "Admin" will create AdminResource, AdminCollection}
|
|
16
1147
|
| {--force : Create the resource or collection file even if it already exists.}
|
|
17
1148
|
}
|
|
18
|
-
`;
|
|
1149
|
+
`;
|
|
1150
|
+
description = "Create a new resource or resource collection file";
|
|
1151
|
+
async handle() {
|
|
1152
|
+
this.app.command = this;
|
|
1153
|
+
let path = "";
|
|
1154
|
+
const name = this.argument("name") || this.argument("prefix");
|
|
1155
|
+
const action = this.dictionary.name || this.dictionary.baseCommand;
|
|
1156
|
+
if (["resource", "collection"].includes(action) && !name) return void this.error("Error: Name argument is required.");
|
|
1157
|
+
if (action === "all" && !name) return void this.error("Error: Prefix argument is required.");
|
|
1158
|
+
switch (action) {
|
|
1159
|
+
case "resource":
|
|
1160
|
+
({path} = this.app.makeResource(name, this.options()));
|
|
1161
|
+
break;
|
|
1162
|
+
case "collection":
|
|
1163
|
+
({path} = this.app.makeResource(name + "Collection", this.options()));
|
|
1164
|
+
break;
|
|
1165
|
+
case "all": {
|
|
1166
|
+
const o1 = this.app.makeResource(name, { force: this.option("force") });
|
|
1167
|
+
const o2 = this.app.makeResource(name + "Collection", {
|
|
1168
|
+
collection: true,
|
|
1169
|
+
force: this.option("force")
|
|
1170
|
+
});
|
|
1171
|
+
path = `${o1.path}, ${o2.path}`;
|
|
1172
|
+
break;
|
|
1173
|
+
}
|
|
1174
|
+
default: this.fail(`Unknown action: ${action}`);
|
|
1175
|
+
}
|
|
1176
|
+
this.success(`Created: ${path}`);
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
|
|
1180
|
+
//#endregion
|
|
1181
|
+
//#region src/cli/logo.ts
|
|
1182
|
+
var logo_default = String.raw`
|
|
19
1183
|
_____
|
|
20
1184
|
| __ \
|
|
21
1185
|
| |__) |___ ___ ___ _ __ __ _
|
|
22
1186
|
| _ // _ \/ __|/ _ \| '__/ _, |
|
|
23
1187
|
| | \ \ __/\__ \ (_) | | | (_| |
|
|
24
1188
|
|_| \_\___||___/\___/|_| \__,_|
|
|
25
|
-
`;var Q=class{_status=200;headers={};constructor(e,t){this.response=e,this.body=t}setStatusCode(e){return this._status=e,this}setBody(e){return this.body=e,this}status(){return this._status}statusText(){if(`statusMessage`in this.response)return this.response.statusMessage;if(`statusText`in this.response)return this.response.statusText}setCookie(e,t,n){return this.#e(`Set-Cookie`,`${e}=${t}; ${Object.entries(n||{}).map(([e,t])=>`${e}=${t}`).join(`; `)}`),this}setHeaders(e){for(let[t,n]of Object.entries(e))this.#e(t,n);return this}header(e,t){return this.#e(e,t),this}#e(e,t){this.headers[e]=t,`headers`in this.response?this.response.headers.set(e,t):`setHeader`in this.response&&this.response.setHeader(e,t)}send(e){if(e!==void 0&&(this.body=e),`send`in this.response&&typeof this.response.send==`function`){`statusCode`in this.response&&(this.response.statusCode=this._status);let e=this.response.send(this.body);return e&&`status`in e&&typeof e.status==`function`?e.status(this._status):`status`in this.response&&typeof this.response.status==`function`&&this.response.status(this._status),this.body}return`status`in this.response&&typeof this.response.status!=`function`&&(this.response.status=this._status),this.body}then(e,t){return Promise.resolve(this.send()).then(e,t)}catch(e){return this.then(void 0,e)}finally(e){return this.then(e,e)}},$=class{static preferredCase;static responseStructure;static config;static ctx;instanceConfig;additionalMeta;called={};constructor(){Z()}static setCtx(e){k(e),this.ctx=e}when(e,t){return Ee(e,t)}whenNotNull(e){return De(e)}mergeWhen(e,t){return Oe(e,t)}with(e){if(this.called.with=!0,e===void 0)return this.additionalMeta||{};let t=typeof e==`function`?e(this.getResourceForMeta()):e;return this.additionalMeta=M(this.additionalMeta,t),this.called.json&&this.applyMetaToBody(t,this.resolveCurrentRootKey()),this}withMeta(e){return this.with(e),this}resolveMergedMeta(e){return M(Te(this,e),this.additionalMeta)}runResponse(e){this.called.toResponse=!0,e.ensureJson();let t=e.body(),n=e.createServerResponse(e.rawResponse,t);return this.called.withResponse=!0,e.callWithResponse(n,e.rawResponse),typeof n?.setBody==`function`&&n.setBody(e.body()),n}runThen(e){this.called.then=!0,e.ensureJson();let t=e.body(),n;e.rawResponse===void 0?(this.called.withResponse=!0,e.callWithResponse()):(n=e.createServerResponse(e.rawResponse,t),this.called.withResponse=!0,e.callWithResponse(n,e.rawResponse));let r=e.body();typeof n?.setBody==`function`&&n.setBody(r);let i=Promise.resolve(r).then(e.onfulfilled,e.onrejected);return typeof n?.send==`function`?n.send(r):e.rawResponse!==void 0&&e.sendRawResponse&&e.sendRawResponse(e.rawResponse,r),i}config(e){return e===void 0?this.instanceConfig||{}:(this.instanceConfig={...this.instanceConfig||{},...e,responseStructure:{...this.instanceConfig?.responseStructure||{},...e.responseStructure||{}}},this)}resolveSerializerConfig(e,t){let n=typeof e.config==`function`?e.config():{};return{preferredCase:this.instanceConfig?.preferredCase??n?.preferredCase,responseStructure:{...n?.responseStructure||{},...this.instanceConfig?.responseStructure||{}}}}resolveSerializerCaseStyle(e,t){return this.resolveSerializerConfig(e,t).preferredCase??e.preferredCase??t?.preferredCase??ie()}resolveSerializerResponseStructure(e,t){let n=this.resolveSerializerConfig(e,t),r=b();return{wrap:n.responseStructure?.wrap??e.responseStructure?.wrap??t?.responseStructure?.wrap??r?.wrap??!0,rootKey:n.responseStructure?.rootKey??e.responseStructure?.rootKey??t?.responseStructure?.rootKey??r?.rootKey??`data`,factory:n.responseStructure?.factory??e.responseStructure?.factory??t?.responseStructure?.factory??r?.factory}}},He=class e extends ${body={data:{}};res;resource;collects;withResponseContext;constructor(t,n){if(super(),n&&(e.ctx=n),this.resource=t,n){let e=E(n);e&&w(e),this.res=O(n)}let r=!!this.resource&&typeof this.resource==`object`&&`data`in this.resource,i=r?this.resource.data:void 0,a=!!i&&!Array.isArray(i),o=r?i:this.resource;if(o&&typeof o==`object`&&!Array.isArray(o)&&!P(o)){let e=N(o)?Object.keys(o.toObject()):Object.keys(o);for(let t of e)t in this||Object.defineProperty(this,t,{enumerable:!0,configurable:!0,get:()=>N(o)&&typeof o.getAttribute==`function`?o.getAttribute(t):a?i[t]:this.resource[t],set:e=>{if(N(o)&&typeof o.setAttribute==`function`){o.setAttribute(t,e);return}if(a){i[t]=e;return}this.resource[t]=e}})}}data(){return this.resource}getBody(){return this.json(),this.body}setBody(e){return this.body=e,this}resolveCollectsConfig(){let e=this.collects;if(!e)return;let t=typeof e.config==`function`?e.config():{};return{preferredCase:t.preferredCase??e.preferredCase,responseStructure:{...e.responseStructure||{},...t.responseStructure||{}}}}resolveResponseStructure(){return this.resolveSerializerResponseStructure(this.constructor,this.resolveCollectsConfig())}resolveCurrentRootKey(){return this.resolveResponseStructure().rootKey}applyMetaToBody(e,t){this.body=j(this.body,e,t)}getResourceForMeta(){return this.resource}getPayloadKey(){let{wrap:e,rootKey:t,factory:n}=this.resolveResponseStructure();return n||!e?void 0:t}json(){if(!this.called.json){this.called.json=!0;let t=F(this.data());Array.isArray(t)&&this.collects&&(t=t.map(e=>new this.collects(e).data())),!Array.isArray(t)&&t&&t.data!==void 0&&(t=t.data),t=U(t);let n=B(this.resource),{metaKey:r}=I(),i=r?n[r]:void 0;r&&delete n[r];let a=this.resolveSerializerCaseStyle(this.constructor,this.resolveCollectsConfig());if(a){let e=G(a);t=K(t,e)}let o=this.resolveMergedMeta(e.prototype.with),{wrap:s,rootKey:c,factory:l}=this.resolveResponseStructure();this.body=V({payload:t,meta:i,metaKey:r,wrap:s,rootKey:c,factory:l,context:{type:`generic`,resource:this.resource}}),this.body=j(this.body,{...n,...o||{}},c)}return this}toObject(){this.called.toObject=!0,this.json();let e=F(this.resource);return!Array.isArray(e)&&e&&e.data!==void 0&&(e=e.data),e}toArray(){return this.called.toArray=!0,this.toObject()}additional(e){this.called.additional=!0,this.json();let t=e.data;delete e.data,delete e.pagination;let n=this.getPayloadKey();return t&&n&&this.body[n]!==void 0&&(this.body[n]=Array.isArray(this.body[n])?[...this.body[n],...t]:{...this.body[n],...t}),this.body={...this.body,...e},this}response(t){let n=t??this.res??e.ctx?.res??e.ctx;return this.runResponse({ensureJson:()=>this.json(),rawResponse:n,body:()=>this.body,createServerResponse:(e,t)=>{let n=new Q(e,t);return this.withResponseContext={response:n,raw:e},n},callWithResponse:(e,t)=>{this.withResponse(e,t)}})}withResponse(e,t){return this}then(t,n){return this.runThen({ensureJson:()=>this.json(),body:()=>this.body,rawResponse:this.res??e.ctx?.res??e.ctx,createServerResponse:(e,t)=>{let n=new Q(e,t);return this.withResponseContext={response:n,raw:e},n},callWithResponse:(e,t)=>{this.withResponse(e,t)},sendRawResponse:(e,t)=>{e.send(t)},onfulfilled:t,onrejected:n})}catch(t){return this.runThen({ensureJson:()=>this.json(),body:()=>this.body,rawResponse:this.res??e.ctx?.res??e.ctx,createServerResponse:(e,t)=>{let n=new Q(e,t);return this.withResponseContext={response:n,raw:e},n},callWithResponse:(e,t)=>{this.withResponse(e,t)},sendRawResponse:(e,t)=>{e.send(t)},onrejected:t})}finally(t){return this.runThen({ensureJson:()=>this.json(),body:()=>this.body,rawResponse:this.res??e.ctx?.res??e.ctx,createServerResponse:(e,t)=>{let n=new Q(e,t);return this.withResponseContext={response:n,raw:e},n},callWithResponse:(e,t)=>{this.withResponse(e,t)},sendRawResponse:(e,t)=>{e.send(t)},onfulfilled:t,onrejected:t})}},Ue=class e extends ${body={data:[]};res;resource;collects;withResponseContext;isPaginatedCollectible(e){if(!e||typeof e!=`object`)return!1;let t=e;return t.pagination&&Array.isArray(t.data)?!0:t.meta&&typeof t.meta==`object`&&`currentPage`in t.meta?Array.isArray(t.data)||P(t.data):!1}constructor(t,n){if(super(),n&&(e.ctx=n),this.resource=t,n){let e=E(n);e&&w(e),this.res=O(n)}}data(){return this.toObject()}getBody(){return this.json(),this.body}setBody(e){return this.body=e,this}resolveCollectsConfig(){let e=this.collects;if(!e)return;let t=typeof e.config==`function`?e.config():{};return{preferredCase:t.preferredCase??e.preferredCase,responseStructure:{...e.responseStructure||{},...t.responseStructure||{}}}}resolveResponseStructure(){return this.resolveSerializerResponseStructure(this.constructor,this.resolveCollectsConfig())}resolveCurrentRootKey(){return this.resolveResponseStructure().rootKey}applyMetaToBody(e,t){this.body=j(this.body,e,t)}getResourceForMeta(){return this.resource}getPayloadKey(){let{wrap:e,rootKey:t,factory:n}=this.resolveResponseStructure();return n||!e?void 0:t}json(){if(!this.called.json){this.called.json=!0;let t=this.data();this.collects&&(t=t.map(e=>new this.collects(e).data())),t=F(t),t=U(t);let n=Array.isArray(this.resource)?{}:B(this.resource),{metaKey:r}=I(),i=r?n[r]:void 0;r&&delete n[r];let a=this.resolveSerializerCaseStyle(this.constructor,this.resolveCollectsConfig());if(a){let e=G(a);t=K(t,e)}let o=this.resolveMergedMeta(e.prototype.with),{wrap:s,rootKey:c,factory:l}=this.resolveResponseStructure();this.body=V({payload:t,meta:i,metaKey:r,wrap:s,rootKey:c,factory:l,context:{type:`collection`,resource:this.resource}}),this.body=j(this.body,{...n,...o||{}},c)}return this}toObject(){return this.called.toObject=!0,this.json(),F(Array.isArray(this.resource)?this.resource:P(this.resource)?this.resource.all():this.resource.data)}toArray(){return this.called.toArray=!0,this.toObject()}additional(e){this.called.additional=!0,this.json(),delete e.cursor,delete e.pagination;let t=this.getPayloadKey();return e.data&&t&&Array.isArray(this.body[t])&&(this.body[t]=[...this.body[t],...e.data]),this.body={...this.body,...e},this}response(t){let n=t??this.res??e.ctx?.res??e.ctx;return this.runResponse({ensureJson:()=>this.json(),rawResponse:n,body:()=>this.body,createServerResponse:(e,t)=>{let n=new Q(e,t);return this.withResponseContext={response:n,raw:e},n},callWithResponse:(e,t)=>{this.withResponse(e,t)}})}withResponse(e,t){return this}setCollects(e){return this.collects=e,this}then(t,n){return this.runThen({ensureJson:()=>this.json(),body:()=>this.body,rawResponse:this.res??e.ctx?.res??e.ctx,createServerResponse:(e,t)=>{let n=new Q(e,t);return this.withResponseContext={response:n,raw:e},n},callWithResponse:(e,t)=>{this.withResponse(e,t)},sendRawResponse:(e,t)=>{e.send(t)},onfulfilled:t,onrejected:n})}catch(t){return this.runThen({ensureJson:()=>this.json(),body:()=>this.body,rawResponse:this.res??e.ctx?.res??e.ctx,createServerResponse:(e,t)=>{let n=new Q(e,t);return this.withResponseContext={response:n,raw:e},n},callWithResponse:(e,t)=>{this.withResponse(e,t)},sendRawResponse:(e,t)=>{e.send(t)},onrejected:t})}finally(t){return this.runThen({ensureJson:()=>this.json(),body:()=>this.body,rawResponse:this.res??e.ctx?.res??e.ctx,createServerResponse:(e,t)=>{let n=new Q(e,t);return this.withResponseContext={response:n,raw:e},n},callWithResponse:(e,t)=>{this.withResponse(e,t)},sendRawResponse:(e,t)=>{e.send(t)},onfulfilled:t,onrejected:t})}},We=class e extends ${body={data:{}};res;resource;withResponseContext;constructor(t,n){if(super(),n&&(e.ctx=n),this.resource=t,n){let e=E(n);e&&w(e),this.res=O(n)}let r=this.resource.data??this.resource;if(!Array.isArray(r)){let e=N(r)?Object.keys(r.toObject()):Object.keys(r);for(let t of e)t in this||Object.defineProperty(this,t,{enumerable:!0,configurable:!0,get:()=>N(r)&&typeof r.getAttribute==`function`?r.getAttribute(t):this.resource.data?.[t]??this.resource[t],set:e=>{if(N(r)&&typeof r.setAttribute==`function`){r.setAttribute(t,e);return}this.resource.data&&this.resource.data[t]?this.resource.data[t]=e:this.resource[t]=e}})}}static collection(e){return new Ue(e).setCollects(this)}data(){return this.toObject()}getBody(){return this.json(),this.body}setBody(e){return this.body=e,this}resolveResponseStructure(){return this.resolveSerializerResponseStructure(this.constructor)}resolveCurrentRootKey(){return this.resolveResponseStructure().rootKey}applyMetaToBody(e,t){this.body=j(this.body,e,t)}getResourceForMeta(){return this.resource}getPayloadKey(){let{wrap:e,rootKey:t,factory:n}=this.resolveResponseStructure();return n||!e?void 0:t}json(){if(!this.called.json){this.called.json=!0;let t=F(this.data());!Array.isArray(t)&&t&&t.data!==void 0&&(t=t.data),t=U(t);let n=this.resolveSerializerCaseStyle(this.constructor);if(n){let e=G(n);t=K(t,e)}let r=this.resolveMergedMeta(e.prototype.with),{wrap:i,rootKey:a,factory:o}=this.resolveResponseStructure();this.body=V({payload:t,wrap:i,rootKey:a,factory:o,context:{type:`resource`,resource:this.resource}}),this.body=j(this.body,r,a)}return this}toObject(){this.called.toObject=!0,this.json();let e=F(this.resource);return!Array.isArray(e)&&e&&e.data!==void 0&&(e=e.data),e}toArray(){return this.called.toArray=!0,this.toObject()}additional(e){this.called.additional=!0,this.json();let t=this.getPayloadKey();return e.data&&t&&this.body[t]!==void 0&&(this.body[t]=Array.isArray(this.body[t])?[...this.body[t],...e.data]:{...this.body[t],...e.data}),this.body={...this.body,...e},this}response(t){let n=t??this.res??e.ctx?.res??e.ctx;return this.runResponse({ensureJson:()=>this.json(),rawResponse:n,body:()=>this.body,createServerResponse:(e,t)=>{let n=new Q(e,t);return this.withResponseContext={response:n,raw:e},n},callWithResponse:(e,t)=>{this.withResponse(e,t)}})}withResponse(e,t){return this}then(t,n){return this.runThen({ensureJson:()=>this.json(),body:()=>this.body,rawResponse:this.res??e.ctx?.res??e.ctx,createServerResponse:(e,t)=>{let n=new Q(e,t);return this.withResponseContext={response:n,raw:e},n},callWithResponse:(e,t)=>{this.withResponse(e,t)},sendRawResponse:(e,t)=>{e.send(t)},onfulfilled:t,onrejected:n})}catch(t){return this.runThen({ensureJson:()=>this.json(),body:()=>this.body,rawResponse:this.res??e.ctx?.res??e.ctx,createServerResponse:(e,t)=>{let n=new Q(e,t);return this.withResponseContext={response:n,raw:e},n},callWithResponse:(e,t)=>{this.withResponse(e,t)},sendRawResponse:(e,t)=>{e.send(t)},onrejected:t})}finally(t){return this.runThen({ensureJson:()=>this.json(),body:()=>this.body,rawResponse:this.res??e.ctx?.res??e.ctx,createServerResponse:(e,t)=>{let n=new Q(e,t);return this.withResponseContext={response:n,raw:e},n},callWithResponse:(e,t)=>{this.withResponse(e,t)},sendRawResponse:(e,t)=>{e.send(t)},onfulfilled:t,onrejected:t})}};exports.ApiResource=ee,exports.CONDITIONAL_ATTRIBUTE_MISSING=H,exports.CliApp=ze,exports.GenericResource=He,exports.InitCommand=Be,exports.MakeResource=Ve,exports.Resource=We,exports.ResourceCollection=Ue,exports.ServerResponse=Q,exports.appendRootProperties=j,exports.applyRuntimeConfig=Fe,exports.buildPaginationExtras=B,exports.buildResponseEnvelope=V,exports.createArkormCurrentPageResolver=Ce,exports.defineConfig=J,exports.extractRequestUrl=E,exports.extractResponseFromCtx=O,exports.getCaseTransformer=G,exports.getDefaultConfig=Ne,exports.getGlobalBaseUrl=me,exports.getGlobalCase=ie,exports.getGlobalCursorMeta=ye,exports.getGlobalPageName=C,exports.getGlobalPaginatedExtras=S,exports.getGlobalPaginatedLinks=fe,exports.getGlobalPaginatedMeta=_e,exports.getGlobalResponseFactory=ue,exports.getGlobalResponseRootKey=ce,exports.getGlobalResponseStructure=b,exports.getGlobalResponseWrap=se,exports.getPaginationExtraKeys=I,exports.getRequestUrl=T,exports.hasPaginationLink=z,exports.isArkormLikeCollection=P,exports.isArkormLikeModel=N,exports.isPlainObject=A,exports.loadRuntimeConfig=Z,exports.mergeMetadata=M,exports.normalizeSerializableData=F,exports.resetRuntimeConfigForTests=Pe,exports.resolveCurrentPage=L,exports.resolveMergeWhen=Oe,exports.resolveWhen=Ee,exports.resolveWhenNotNull=De,exports.resolveWithHookMetadata=Te,exports.sanitizeConditionalAttributes=U,exports.setCtx=k,exports.setGlobalBaseUrl=pe,exports.setGlobalCase=re,exports.setGlobalCursorMeta=ve,exports.setGlobalPageName=he,exports.setGlobalPaginatedExtras=x,exports.setGlobalPaginatedLinks=de,exports.setGlobalPaginatedMeta=ge,exports.setGlobalResponseFactory=le,exports.setGlobalResponseRootKey=ae,exports.setGlobalResponseStructure=y,exports.setGlobalResponseWrap=oe,exports.setRequestUrl=w,exports.splitWords=W,exports.toCamelCase=ke,exports.toKebabCase=Me,exports.toPascalCase=je,exports.toSnakeCase=Ae,exports.transformKeys=K;
|
|
1189
|
+
`;
|
|
1190
|
+
|
|
1191
|
+
//#endregion
|
|
1192
|
+
//#region src/ServerResponse.ts
|
|
1193
|
+
/**
|
|
1194
|
+
* ServerResponse class to handle HTTP response construction and sending, compatible
|
|
1195
|
+
* with both Express and H3 response objects.
|
|
1196
|
+
*
|
|
1197
|
+
* @author Legacy (3m1n3nc3)
|
|
1198
|
+
* @since 0.1.0
|
|
1199
|
+
*/
|
|
1200
|
+
var ServerResponse = class {
|
|
1201
|
+
_status = 200;
|
|
1202
|
+
headers = {};
|
|
1203
|
+
constructor(response, body) {
|
|
1204
|
+
this.response = response;
|
|
1205
|
+
this.body = body;
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Set the HTTP status code for the response
|
|
1209
|
+
*
|
|
1210
|
+
* @param status
|
|
1211
|
+
* @returns The current ServerResponse instance
|
|
1212
|
+
*/
|
|
1213
|
+
setStatusCode(status) {
|
|
1214
|
+
this._status = status;
|
|
1215
|
+
return this;
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Replace the response body that will be dispatched.
|
|
1219
|
+
*
|
|
1220
|
+
* @param body
|
|
1221
|
+
* @returns The current ServerResponse instance
|
|
1222
|
+
*/
|
|
1223
|
+
setBody(body) {
|
|
1224
|
+
this.body = body;
|
|
1225
|
+
return this;
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Get the current HTTP status code for the response
|
|
1229
|
+
*
|
|
1230
|
+
* @returns
|
|
1231
|
+
*/
|
|
1232
|
+
status() {
|
|
1233
|
+
return this._status;
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Get the current HTTP status text for the response
|
|
1237
|
+
*
|
|
1238
|
+
* @returns
|
|
1239
|
+
*/
|
|
1240
|
+
statusText() {
|
|
1241
|
+
if ("statusMessage" in this.response) return this.response.statusMessage;
|
|
1242
|
+
else if ("statusText" in this.response) return this.response.statusText;
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Set a cookie in the response header
|
|
1246
|
+
*
|
|
1247
|
+
* @param name The name of the cookie
|
|
1248
|
+
* @param value The value of the cookie
|
|
1249
|
+
* @param options Optional cookie attributes (e.g., path, domain, maxAge)
|
|
1250
|
+
* @returns The current ServerResponse instance
|
|
1251
|
+
*/
|
|
1252
|
+
setCookie(name, value, options) {
|
|
1253
|
+
this.#addHeader("Set-Cookie", `${name}=${value}; ${Object.entries(options || {}).map(([key, val]) => `${key}=${val}`).join("; ")}`);
|
|
1254
|
+
return this;
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Convert the resource to a JSON response body
|
|
1258
|
+
*
|
|
1259
|
+
* @param headers Optional headers to add to the response
|
|
1260
|
+
* @returns The current ServerResponse instance
|
|
1261
|
+
*/
|
|
1262
|
+
setHeaders(headers) {
|
|
1263
|
+
for (const [key, value] of Object.entries(headers)) this.#addHeader(key, value);
|
|
1264
|
+
return this;
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Add a single header to the response
|
|
1268
|
+
*
|
|
1269
|
+
* @param key The name of the header
|
|
1270
|
+
* @param value The value of the header
|
|
1271
|
+
* @returns The current ServerResponse instance
|
|
1272
|
+
*/
|
|
1273
|
+
header(key, value) {
|
|
1274
|
+
this.#addHeader(key, value);
|
|
1275
|
+
return this;
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Add a single header to the response
|
|
1279
|
+
*
|
|
1280
|
+
* @param key The name of the header
|
|
1281
|
+
* @param value The value of the header
|
|
1282
|
+
*/
|
|
1283
|
+
#addHeader(key, value) {
|
|
1284
|
+
this.headers[key] = value;
|
|
1285
|
+
if ("headers" in this.response) this.response.headers.set(key, value);
|
|
1286
|
+
else if ("setHeader" in this.response) this.response.setHeader(key, value);
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* Dispatch the current body and apply any deferred transport state.
|
|
1290
|
+
*
|
|
1291
|
+
* @param body Optional body override
|
|
1292
|
+
* @returns The dispatched response body
|
|
1293
|
+
*/
|
|
1294
|
+
send(body) {
|
|
1295
|
+
if (typeof body !== "undefined") this.body = body;
|
|
1296
|
+
if ("send" in this.response && typeof this.response.send === "function") {
|
|
1297
|
+
if ("statusCode" in this.response) this.response.statusCode = this._status;
|
|
1298
|
+
const sentResponse = this.response.send(this.body);
|
|
1299
|
+
if (sentResponse && "status" in sentResponse && typeof sentResponse.status === "function") sentResponse.status(this._status);
|
|
1300
|
+
else if ("status" in this.response && typeof this.response.status === "function") this.response.status(this._status);
|
|
1301
|
+
return this.body;
|
|
1302
|
+
}
|
|
1303
|
+
if ("status" in this.response && typeof this.response.status !== "function") this.response.status = this._status;
|
|
1304
|
+
return this.body;
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Promise-like then method to allow chaining with async/await or .then() syntax
|
|
1308
|
+
*
|
|
1309
|
+
* @param onfulfilled Callback to handle the fulfilled state of the promise, receiving the response body
|
|
1310
|
+
* @param onrejected Callback to handle the rejected state of the promise, receiving the error reason
|
|
1311
|
+
* @returns A promise that resolves to the result of the onfulfilled or onrejected callback
|
|
1312
|
+
*/
|
|
1313
|
+
then(onfulfilled, onrejected) {
|
|
1314
|
+
return Promise.resolve(this.send()).then(onfulfilled, onrejected);
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Promise-like catch method to handle rejected state of the promise
|
|
1318
|
+
*
|
|
1319
|
+
* @param onrejected
|
|
1320
|
+
* @returns
|
|
1321
|
+
*/
|
|
1322
|
+
catch(onrejected) {
|
|
1323
|
+
return this.then(void 0, onrejected);
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Promise-like finally method to handle cleanup after promise is settled
|
|
1327
|
+
*
|
|
1328
|
+
* @param onfinally
|
|
1329
|
+
* @returns
|
|
1330
|
+
*/
|
|
1331
|
+
finally(onfinally) {
|
|
1332
|
+
return this.then(onfinally, onfinally);
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
|
|
1336
|
+
//#endregion
|
|
1337
|
+
//#region src/BaseSerializer.ts
|
|
1338
|
+
/**
|
|
1339
|
+
* @description BaseSerializer is an abstract class that provides common functionality for
|
|
1340
|
+
* serializing resources. It handles configuration, metadata management, and response
|
|
1341
|
+
* structure resolution. Concrete serializers should extend this class and implement
|
|
1342
|
+
* the abstract methods to define how resources are transformed and how metadata is
|
|
1343
|
+
* applied to the response body.
|
|
1344
|
+
*
|
|
1345
|
+
* @author Legacy (3m1n3nc3)
|
|
1346
|
+
* @since 0.2.8
|
|
1347
|
+
*/
|
|
1348
|
+
var BaseSerializer = class {
|
|
1349
|
+
static preferredCase;
|
|
1350
|
+
static responseStructure;
|
|
1351
|
+
static config;
|
|
1352
|
+
static ctx;
|
|
1353
|
+
instanceConfig;
|
|
1354
|
+
additionalMeta;
|
|
1355
|
+
called = {};
|
|
1356
|
+
constructor() {
|
|
1357
|
+
loadRuntimeConfig();
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Sets the current request context for pagination URL detection.
|
|
1361
|
+
* Call from middleware to make the request path available to all
|
|
1362
|
+
* resources created during the request lifecycle.
|
|
1363
|
+
*
|
|
1364
|
+
* Accepts an Express Request, H3Event, `{ req }` object, or a plain URL string.
|
|
1365
|
+
*
|
|
1366
|
+
* @param ctx The request context.
|
|
1367
|
+
*/
|
|
1368
|
+
static setCtx(ctx) {
|
|
1369
|
+
setCtx(ctx);
|
|
1370
|
+
this.ctx = ctx;
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* Helper method to conditionally resolve a value based on a condition.
|
|
1374
|
+
*
|
|
1375
|
+
* @param condition
|
|
1376
|
+
* @param value
|
|
1377
|
+
* @returns
|
|
1378
|
+
*/
|
|
1379
|
+
when(condition, value) {
|
|
1380
|
+
return resolveWhen(condition, value);
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Helper method to conditionally resolve a value only if it's not null or undefined.
|
|
1384
|
+
*
|
|
1385
|
+
* @param value
|
|
1386
|
+
* @returns
|
|
1387
|
+
*/
|
|
1388
|
+
whenNotNull(value) {
|
|
1389
|
+
return resolveWhenNotNull(value);
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Helper method to conditionally merge values into the response based on a condition.
|
|
1393
|
+
*
|
|
1394
|
+
* @param condition
|
|
1395
|
+
* @param value
|
|
1396
|
+
* @returns
|
|
1397
|
+
*/
|
|
1398
|
+
mergeWhen(condition, value) {
|
|
1399
|
+
return resolveMergeWhen(condition, value);
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Add additional metadata to the response. If called without arguments.
|
|
1403
|
+
*
|
|
1404
|
+
* @param meta
|
|
1405
|
+
* @returns
|
|
1406
|
+
*/
|
|
1407
|
+
with(meta) {
|
|
1408
|
+
this.called.with = true;
|
|
1409
|
+
if (typeof meta === "undefined") return this.additionalMeta || {};
|
|
1410
|
+
const resolvedMeta = typeof meta === "function" ? meta(this.getResourceForMeta()) : meta;
|
|
1411
|
+
this.additionalMeta = mergeMetadata(this.additionalMeta, resolvedMeta);
|
|
1412
|
+
if (this.called.json) this.applyMetaToBody(resolvedMeta, this.resolveCurrentRootKey());
|
|
1413
|
+
return this;
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Add additional metadata to the response, ensuring it is merged with any existing metadata.
|
|
1417
|
+
*
|
|
1418
|
+
* @param meta
|
|
1419
|
+
* @returns
|
|
1420
|
+
*/
|
|
1421
|
+
withMeta(meta) {
|
|
1422
|
+
this.with(meta);
|
|
1423
|
+
return this;
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Resolve the merged metadata for the current response, combining any metadata
|
|
1427
|
+
* defined in hooks with additional metadata added via with() or withMeta().
|
|
1428
|
+
*
|
|
1429
|
+
* @param withMethod
|
|
1430
|
+
* @returns
|
|
1431
|
+
*/
|
|
1432
|
+
resolveMergedMeta(withMethod) {
|
|
1433
|
+
return mergeMetadata(resolveWithHookMetadata(this, withMethod), this.additionalMeta);
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Run the response generation process, ensuring that the response is properly structured.
|
|
1437
|
+
*
|
|
1438
|
+
* @param input
|
|
1439
|
+
* @returns
|
|
1440
|
+
*/
|
|
1441
|
+
runResponse(input) {
|
|
1442
|
+
this.called.toResponse = true;
|
|
1443
|
+
input.ensureJson();
|
|
1444
|
+
const resolvedBody = input.body();
|
|
1445
|
+
const response = input.createServerResponse(input.rawResponse, resolvedBody);
|
|
1446
|
+
this.called.withResponse = true;
|
|
1447
|
+
input.callWithResponse(response, input.rawResponse);
|
|
1448
|
+
if (typeof response?.setBody === "function") response.setBody(input.body());
|
|
1449
|
+
return response;
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Run the thenable process for the resource, allowing for async handling and
|
|
1453
|
+
* response generation.
|
|
1454
|
+
*
|
|
1455
|
+
* @param input
|
|
1456
|
+
* @returns
|
|
1457
|
+
*/
|
|
1458
|
+
runThen(input) {
|
|
1459
|
+
this.called.then = true;
|
|
1460
|
+
input.ensureJson();
|
|
1461
|
+
const initialBody = input.body();
|
|
1462
|
+
let response;
|
|
1463
|
+
if (typeof input.rawResponse !== "undefined") {
|
|
1464
|
+
response = input.createServerResponse(input.rawResponse, initialBody);
|
|
1465
|
+
this.called.withResponse = true;
|
|
1466
|
+
input.callWithResponse(response, input.rawResponse);
|
|
1467
|
+
} else {
|
|
1468
|
+
this.called.withResponse = true;
|
|
1469
|
+
input.callWithResponse();
|
|
1470
|
+
}
|
|
1471
|
+
const resolvedBody = input.body();
|
|
1472
|
+
if (typeof response?.setBody === "function") response.setBody(resolvedBody);
|
|
1473
|
+
const resolved = Promise.resolve(resolvedBody).then(input.onfulfilled, input.onrejected);
|
|
1474
|
+
if (typeof response?.send === "function") response.send(resolvedBody);
|
|
1475
|
+
else if (typeof input.rawResponse !== "undefined" && input.sendRawResponse) input.sendRawResponse(input.rawResponse, resolvedBody);
|
|
1476
|
+
return resolved;
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Get or set the resource-level configuration for this serializer instance.
|
|
1480
|
+
*
|
|
1481
|
+
* @param config The configuration object to set for this serializer instance, or undefined to get the current configuration.
|
|
1482
|
+
* @returns
|
|
1483
|
+
*/
|
|
1484
|
+
config(config) {
|
|
1485
|
+
if (typeof config === "undefined") return this.instanceConfig || {};
|
|
1486
|
+
this.instanceConfig = {
|
|
1487
|
+
...this.instanceConfig || {},
|
|
1488
|
+
...config,
|
|
1489
|
+
responseStructure: {
|
|
1490
|
+
...this.instanceConfig?.responseStructure || {},
|
|
1491
|
+
...config.responseStructure || {}
|
|
1492
|
+
}
|
|
1493
|
+
};
|
|
1494
|
+
return this;
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Resolve the effective serializer configuration for this instance, combining
|
|
1498
|
+
* class-level and instance-level configurations.
|
|
1499
|
+
*
|
|
1500
|
+
* @param localConstructor
|
|
1501
|
+
* @param _fallbackConfig
|
|
1502
|
+
* @returns
|
|
1503
|
+
*/
|
|
1504
|
+
resolveSerializerConfig(localConstructor, _fallbackConfig) {
|
|
1505
|
+
const classConfig = typeof localConstructor.config === "function" ? localConstructor.config() : {};
|
|
1506
|
+
return {
|
|
1507
|
+
preferredCase: this.instanceConfig?.preferredCase ?? classConfig?.preferredCase,
|
|
1508
|
+
responseStructure: {
|
|
1509
|
+
...classConfig?.responseStructure || {},
|
|
1510
|
+
...this.instanceConfig?.responseStructure || {}
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Resolve the preferred case style for this serializer instance, considering
|
|
1516
|
+
* instance-level configuration, class-level configuration, and global defaults.
|
|
1517
|
+
*
|
|
1518
|
+
* @param localConstructor The constructor of the serializer class.
|
|
1519
|
+
* @param fallbackConfig The fallback configuration to use if no other configuration is found.
|
|
1520
|
+
* @returns The resolved case style for this serializer instance.
|
|
1521
|
+
*/
|
|
1522
|
+
resolveSerializerCaseStyle(localConstructor, fallbackConfig) {
|
|
1523
|
+
return this.resolveSerializerConfig(localConstructor, fallbackConfig).preferredCase ?? localConstructor.preferredCase ?? fallbackConfig?.preferredCase ?? getGlobalCase();
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Resolve the response structure configuration for this serializer instance, considering
|
|
1527
|
+
*
|
|
1528
|
+
* @param localConstructor
|
|
1529
|
+
* @param fallbackConfig
|
|
1530
|
+
* @returns
|
|
1531
|
+
*/
|
|
1532
|
+
resolveSerializerResponseStructure(localConstructor, fallbackConfig) {
|
|
1533
|
+
const localConfig = this.resolveSerializerConfig(localConstructor, fallbackConfig);
|
|
1534
|
+
const global = getGlobalResponseStructure();
|
|
1535
|
+
return {
|
|
1536
|
+
wrap: localConfig.responseStructure?.wrap ?? localConstructor.responseStructure?.wrap ?? fallbackConfig?.responseStructure?.wrap ?? global?.wrap ?? true,
|
|
1537
|
+
rootKey: localConfig.responseStructure?.rootKey ?? localConstructor.responseStructure?.rootKey ?? fallbackConfig?.responseStructure?.rootKey ?? global?.rootKey ?? "data",
|
|
1538
|
+
factory: localConfig.responseStructure?.factory ?? localConstructor.responseStructure?.factory ?? fallbackConfig?.responseStructure?.factory ?? global?.factory
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
|
|
1543
|
+
//#endregion
|
|
1544
|
+
//#region src/GenericResource.ts
|
|
1545
|
+
/**
|
|
1546
|
+
* GenericResource class to handle API resource transformation and response building
|
|
1547
|
+
*
|
|
1548
|
+
* @author Legacy (3m1n3nc3)
|
|
1549
|
+
* @since 0.1.0
|
|
1550
|
+
* @see BaseSerializer for shared serialization logic and configuration handling
|
|
1551
|
+
*/
|
|
1552
|
+
var GenericResource = class GenericResource extends BaseSerializer {
|
|
1553
|
+
body = { data: {} };
|
|
1554
|
+
res;
|
|
1555
|
+
resource;
|
|
1556
|
+
collects;
|
|
1557
|
+
withResponseContext;
|
|
1558
|
+
constructor(rsc, ctx) {
|
|
1559
|
+
super();
|
|
1560
|
+
if (ctx) GenericResource.ctx = ctx;
|
|
1561
|
+
this.resource = rsc;
|
|
1562
|
+
if (ctx) {
|
|
1563
|
+
const url = extractRequestUrl(ctx);
|
|
1564
|
+
if (url) setRequestUrl(url);
|
|
1565
|
+
this.res = extractResponseFromCtx(ctx);
|
|
1566
|
+
}
|
|
1567
|
+
const hasDataPayload = !!this.resource && typeof this.resource === "object" && "data" in this.resource;
|
|
1568
|
+
const dataPayload = hasDataPayload ? this.resource.data : void 0;
|
|
1569
|
+
const hasObjectDataPayload = !!dataPayload && !Array.isArray(dataPayload);
|
|
1570
|
+
const source = hasDataPayload ? dataPayload : this.resource;
|
|
1571
|
+
/**
|
|
1572
|
+
* Copy properties from rsc to this instance for easy
|
|
1573
|
+
* access, but only if data is not an array
|
|
1574
|
+
*/
|
|
1575
|
+
if (source && typeof source === "object" && !Array.isArray(source) && !isArkormLikeCollection(source)) {
|
|
1576
|
+
const sourceKeys = isArkormLikeModel(source) ? Object.keys(source.toObject()) : Object.keys(source);
|
|
1577
|
+
for (const key of sourceKeys) if (!(key in this)) Object.defineProperty(this, key, {
|
|
1578
|
+
enumerable: true,
|
|
1579
|
+
configurable: true,
|
|
1580
|
+
get: () => {
|
|
1581
|
+
if (isArkormLikeModel(source) && typeof source.getAttribute === "function") return source.getAttribute(key);
|
|
1582
|
+
if (hasObjectDataPayload) return dataPayload[key];
|
|
1583
|
+
return this.resource[key];
|
|
1584
|
+
},
|
|
1585
|
+
set: (value) => {
|
|
1586
|
+
if (isArkormLikeModel(source) && typeof source.setAttribute === "function") {
|
|
1587
|
+
source.setAttribute(key, value);
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
if (hasObjectDataPayload) {
|
|
1591
|
+
dataPayload[key] = value;
|
|
1592
|
+
return;
|
|
1593
|
+
}
|
|
1594
|
+
this.resource[key] = value;
|
|
1595
|
+
}
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* Get the original resource data
|
|
1601
|
+
*/
|
|
1602
|
+
data() {
|
|
1603
|
+
return this.resource;
|
|
1604
|
+
}
|
|
1605
|
+
/**
|
|
1606
|
+
* Get the current serialized output body.
|
|
1607
|
+
*/
|
|
1608
|
+
getBody() {
|
|
1609
|
+
this.json();
|
|
1610
|
+
return this.body;
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Replace the current serialized output body.
|
|
1614
|
+
*/
|
|
1615
|
+
setBody(body) {
|
|
1616
|
+
this.body = body;
|
|
1617
|
+
return this;
|
|
1618
|
+
}
|
|
1619
|
+
resolveCollectsConfig() {
|
|
1620
|
+
const collectedResource = this.collects;
|
|
1621
|
+
if (!collectedResource) return;
|
|
1622
|
+
const collectedConfig = typeof collectedResource.config === "function" ? collectedResource.config() : {};
|
|
1623
|
+
return {
|
|
1624
|
+
preferredCase: collectedConfig.preferredCase ?? collectedResource.preferredCase,
|
|
1625
|
+
responseStructure: {
|
|
1626
|
+
...collectedResource.responseStructure || {},
|
|
1627
|
+
...collectedConfig.responseStructure || {}
|
|
1628
|
+
}
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
resolveResponseStructure() {
|
|
1632
|
+
return this.resolveSerializerResponseStructure(this.constructor, this.resolveCollectsConfig());
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1635
|
+
* Resolve the current root key for the response structure, based on configuration and defaults.
|
|
1636
|
+
*
|
|
1637
|
+
* @returns
|
|
1638
|
+
*/
|
|
1639
|
+
resolveCurrentRootKey() {
|
|
1640
|
+
return this.resolveResponseStructure().rootKey;
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* Apply metadata properties to the response body, ensuring they are merged with.
|
|
1644
|
+
*
|
|
1645
|
+
* @param meta
|
|
1646
|
+
* @param rootKey
|
|
1647
|
+
*/
|
|
1648
|
+
applyMetaToBody(meta, rootKey) {
|
|
1649
|
+
this.body = appendRootProperties(this.body, meta, rootKey);
|
|
1650
|
+
}
|
|
1651
|
+
/**
|
|
1652
|
+
* Get the resource data to be used for generating metadata.
|
|
1653
|
+
*
|
|
1654
|
+
* @returns
|
|
1655
|
+
*/
|
|
1656
|
+
getResourceForMeta() {
|
|
1657
|
+
return this.resource;
|
|
1658
|
+
}
|
|
1659
|
+
getPayloadKey() {
|
|
1660
|
+
const { wrap, rootKey, factory } = this.resolveResponseStructure();
|
|
1661
|
+
return factory || !wrap ? void 0 : rootKey;
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* Convert resource to JSON response format
|
|
1665
|
+
*
|
|
1666
|
+
* @returns
|
|
1667
|
+
*/
|
|
1668
|
+
json() {
|
|
1669
|
+
if (!this.called.json) {
|
|
1670
|
+
this.called.json = true;
|
|
1671
|
+
let data = normalizeSerializableData(this.data());
|
|
1672
|
+
if (Array.isArray(data) && this.collects) data = data.map((item) => new this.collects(item).data());
|
|
1673
|
+
if (!Array.isArray(data) && data && typeof data.data !== "undefined") data = data.data;
|
|
1674
|
+
data = sanitizeConditionalAttributes(data);
|
|
1675
|
+
const paginationExtras = buildPaginationExtras(this.resource);
|
|
1676
|
+
const { metaKey } = getPaginationExtraKeys();
|
|
1677
|
+
const configuredMeta = metaKey ? paginationExtras[metaKey] : void 0;
|
|
1678
|
+
if (metaKey) delete paginationExtras[metaKey];
|
|
1679
|
+
const caseStyle = this.resolveSerializerCaseStyle(this.constructor, this.resolveCollectsConfig());
|
|
1680
|
+
if (caseStyle) {
|
|
1681
|
+
const transformer = getCaseTransformer(caseStyle);
|
|
1682
|
+
data = transformKeys(data, transformer);
|
|
1683
|
+
}
|
|
1684
|
+
const customMeta = this.resolveMergedMeta(GenericResource.prototype.with);
|
|
1685
|
+
const { wrap, rootKey, factory } = this.resolveResponseStructure();
|
|
1686
|
+
this.body = buildResponseEnvelope({
|
|
1687
|
+
payload: data,
|
|
1688
|
+
meta: configuredMeta,
|
|
1689
|
+
metaKey,
|
|
1690
|
+
wrap,
|
|
1691
|
+
rootKey,
|
|
1692
|
+
factory,
|
|
1693
|
+
context: {
|
|
1694
|
+
type: "generic",
|
|
1695
|
+
resource: this.resource
|
|
1696
|
+
}
|
|
1697
|
+
});
|
|
1698
|
+
this.body = appendRootProperties(this.body, {
|
|
1699
|
+
...paginationExtras,
|
|
1700
|
+
...customMeta || {}
|
|
1701
|
+
}, rootKey);
|
|
1702
|
+
}
|
|
1703
|
+
return this;
|
|
1704
|
+
}
|
|
1705
|
+
/**
|
|
1706
|
+
* Convert resource to object format (for collections).
|
|
1707
|
+
*
|
|
1708
|
+
* @returns
|
|
1709
|
+
*/
|
|
1710
|
+
toObject() {
|
|
1711
|
+
this.called.toObject = true;
|
|
1712
|
+
this.json();
|
|
1713
|
+
let data = normalizeSerializableData(this.resource);
|
|
1714
|
+
if (!Array.isArray(data) && data && typeof data.data !== "undefined") data = data.data;
|
|
1715
|
+
return data;
|
|
1716
|
+
}
|
|
1717
|
+
/**
|
|
1718
|
+
* Convert resource to object format and return original data.
|
|
1719
|
+
*
|
|
1720
|
+
* @deprecated Use toObject() instead.
|
|
1721
|
+
* @alias toArray
|
|
1722
|
+
* @since 0.2.9
|
|
1723
|
+
*/
|
|
1724
|
+
toArray() {
|
|
1725
|
+
this.called.toArray = true;
|
|
1726
|
+
return this.toObject();
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Add additional properties to the response body
|
|
1730
|
+
*
|
|
1731
|
+
* @param extra Additional properties to merge into the response body
|
|
1732
|
+
* @returns
|
|
1733
|
+
*/
|
|
1734
|
+
additional(extra) {
|
|
1735
|
+
this.called.additional = true;
|
|
1736
|
+
this.json();
|
|
1737
|
+
const extraData = extra.data;
|
|
1738
|
+
delete extra.data;
|
|
1739
|
+
delete extra.pagination;
|
|
1740
|
+
const payloadKey = this.getPayloadKey();
|
|
1741
|
+
if (extraData && payloadKey && typeof this.body[payloadKey] !== "undefined") this.body[payloadKey] = Array.isArray(this.body[payloadKey]) ? [...this.body[payloadKey], ...extraData] : {
|
|
1742
|
+
...this.body[payloadKey],
|
|
1743
|
+
...extraData
|
|
1744
|
+
};
|
|
1745
|
+
this.body = {
|
|
1746
|
+
...this.body,
|
|
1747
|
+
...extra
|
|
1748
|
+
};
|
|
1749
|
+
return this;
|
|
1750
|
+
}
|
|
1751
|
+
/**
|
|
1752
|
+
* Build a response object, writing to the provided raw response if possible.
|
|
1753
|
+
*
|
|
1754
|
+
* @param res
|
|
1755
|
+
* @returns
|
|
1756
|
+
*/
|
|
1757
|
+
response(res) {
|
|
1758
|
+
const rawResponse = res ?? this.res ?? GenericResource.ctx?.res ?? GenericResource.ctx;
|
|
1759
|
+
return this.runResponse({
|
|
1760
|
+
ensureJson: () => this.json(),
|
|
1761
|
+
rawResponse,
|
|
1762
|
+
body: () => this.body,
|
|
1763
|
+
createServerResponse: (raw, body) => {
|
|
1764
|
+
const response = new ServerResponse(raw, body);
|
|
1765
|
+
this.withResponseContext = {
|
|
1766
|
+
response,
|
|
1767
|
+
raw
|
|
1768
|
+
};
|
|
1769
|
+
return response;
|
|
1770
|
+
},
|
|
1771
|
+
callWithResponse: (response, raw) => {
|
|
1772
|
+
this.withResponse(response, raw);
|
|
1773
|
+
}
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
/**
|
|
1777
|
+
* Customize the outgoing transport response right before dispatch.
|
|
1778
|
+
*
|
|
1779
|
+
* Override in custom classes to mutate headers/status/body.
|
|
1780
|
+
*/
|
|
1781
|
+
withResponse(_response, _rawResponse) {
|
|
1782
|
+
return this;
|
|
1783
|
+
}
|
|
1784
|
+
/**
|
|
1785
|
+
* Promise-like then method to allow chaining with async/await or .then() syntax
|
|
1786
|
+
*
|
|
1787
|
+
* @param onfulfilled Callback to handle the fulfilled state of the promise, receiving the response body
|
|
1788
|
+
* @param onrejected Callback to handle the rejected state of the promise, receiving the error reason
|
|
1789
|
+
* @returns A promise that resolves to the result of the onfulfilled or onrejected callback
|
|
1790
|
+
*/
|
|
1791
|
+
then(onfulfilled, onrejected) {
|
|
1792
|
+
return this.runThen({
|
|
1793
|
+
ensureJson: () => this.json(),
|
|
1794
|
+
body: () => this.body,
|
|
1795
|
+
rawResponse: this.res ?? GenericResource.ctx?.res ?? GenericResource.ctx,
|
|
1796
|
+
createServerResponse: (raw, body) => {
|
|
1797
|
+
const response = new ServerResponse(raw, body);
|
|
1798
|
+
this.withResponseContext = {
|
|
1799
|
+
response,
|
|
1800
|
+
raw
|
|
1801
|
+
};
|
|
1802
|
+
return response;
|
|
1803
|
+
},
|
|
1804
|
+
callWithResponse: (response, raw) => {
|
|
1805
|
+
this.withResponse(response, raw);
|
|
1806
|
+
},
|
|
1807
|
+
sendRawResponse: (raw, body) => {
|
|
1808
|
+
raw.send(body);
|
|
1809
|
+
},
|
|
1810
|
+
onfulfilled,
|
|
1811
|
+
onrejected
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Promise-like catch method to handle rejected state of the promise
|
|
1816
|
+
*
|
|
1817
|
+
* @param onrejected
|
|
1818
|
+
* @returns
|
|
1819
|
+
*/
|
|
1820
|
+
catch(onrejected) {
|
|
1821
|
+
return this.runThen({
|
|
1822
|
+
ensureJson: () => this.json(),
|
|
1823
|
+
body: () => this.body,
|
|
1824
|
+
rawResponse: this.res ?? GenericResource.ctx?.res ?? GenericResource.ctx,
|
|
1825
|
+
createServerResponse: (raw, body) => {
|
|
1826
|
+
const response = new ServerResponse(raw, body);
|
|
1827
|
+
this.withResponseContext = {
|
|
1828
|
+
response,
|
|
1829
|
+
raw
|
|
1830
|
+
};
|
|
1831
|
+
return response;
|
|
1832
|
+
},
|
|
1833
|
+
callWithResponse: (response, raw) => {
|
|
1834
|
+
this.withResponse(response, raw);
|
|
1835
|
+
},
|
|
1836
|
+
sendRawResponse: (raw, body) => {
|
|
1837
|
+
raw.send(body);
|
|
1838
|
+
},
|
|
1839
|
+
onrejected
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
/**
|
|
1843
|
+
* Promise-like finally method to handle cleanup after promise is settled
|
|
1844
|
+
*
|
|
1845
|
+
* @param onfinally
|
|
1846
|
+
* @returns
|
|
1847
|
+
*/
|
|
1848
|
+
finally(onfinally) {
|
|
1849
|
+
return this.runThen({
|
|
1850
|
+
ensureJson: () => this.json(),
|
|
1851
|
+
body: () => this.body,
|
|
1852
|
+
rawResponse: this.res ?? GenericResource.ctx?.res ?? GenericResource.ctx,
|
|
1853
|
+
createServerResponse: (raw, body) => {
|
|
1854
|
+
const response = new ServerResponse(raw, body);
|
|
1855
|
+
this.withResponseContext = {
|
|
1856
|
+
response,
|
|
1857
|
+
raw
|
|
1858
|
+
};
|
|
1859
|
+
return response;
|
|
1860
|
+
},
|
|
1861
|
+
callWithResponse: (response, raw) => {
|
|
1862
|
+
this.withResponse(response, raw);
|
|
1863
|
+
},
|
|
1864
|
+
sendRawResponse: (raw, body) => {
|
|
1865
|
+
raw.send(body);
|
|
1866
|
+
},
|
|
1867
|
+
onfulfilled: onfinally,
|
|
1868
|
+
onrejected: onfinally
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1871
|
+
};
|
|
1872
|
+
|
|
1873
|
+
//#endregion
|
|
1874
|
+
//#region src/ResourceCollection.ts
|
|
1875
|
+
/**
|
|
1876
|
+
* ResourceCollection class to handle API resource transformation and response building
|
|
1877
|
+
* for collections
|
|
1878
|
+
*
|
|
1879
|
+
* @author Legacy (3m1n3nc3)
|
|
1880
|
+
* @since 0.1.0
|
|
1881
|
+
* @see BaseSerializer for shared serialization logic and configuration handling
|
|
1882
|
+
*/
|
|
1883
|
+
var ResourceCollection = class ResourceCollection extends BaseSerializer {
|
|
1884
|
+
body = { data: [] };
|
|
1885
|
+
res;
|
|
1886
|
+
resource;
|
|
1887
|
+
collects;
|
|
1888
|
+
withResponseContext;
|
|
1889
|
+
/**
|
|
1890
|
+
* Type guard to determine if the provided value is a Collectible with pagination information.
|
|
1891
|
+
*
|
|
1892
|
+
* @param value The value to check.
|
|
1893
|
+
* @returns True if the value is a Collectible with pagination information, false otherwise.
|
|
1894
|
+
*/
|
|
1895
|
+
isPaginatedCollectible(value) {
|
|
1896
|
+
if (!value || typeof value !== "object") return false;
|
|
1897
|
+
const resource = value;
|
|
1898
|
+
if (resource.pagination && Array.isArray(resource.data)) return true;
|
|
1899
|
+
if (!(!!resource.meta && typeof resource.meta === "object" && "currentPage" in resource.meta)) return false;
|
|
1900
|
+
return Array.isArray(resource.data) || isArkormLikeCollection(resource.data);
|
|
1901
|
+
}
|
|
1902
|
+
constructor(rsc, ctx) {
|
|
1903
|
+
super();
|
|
1904
|
+
if (ctx) ResourceCollection.ctx = ctx;
|
|
1905
|
+
this.resource = rsc;
|
|
1906
|
+
if (ctx) {
|
|
1907
|
+
const url = extractRequestUrl(ctx);
|
|
1908
|
+
if (url) setRequestUrl(url);
|
|
1909
|
+
this.res = extractResponseFromCtx(ctx);
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1913
|
+
* Get the original resource data
|
|
1914
|
+
*/
|
|
1915
|
+
data() {
|
|
1916
|
+
return this.toObject();
|
|
1917
|
+
}
|
|
1918
|
+
/**
|
|
1919
|
+
* Get the current serialized output body.
|
|
1920
|
+
*/
|
|
1921
|
+
getBody() {
|
|
1922
|
+
this.json();
|
|
1923
|
+
return this.body;
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Replace the current serialized output body.
|
|
1927
|
+
*/
|
|
1928
|
+
setBody(body) {
|
|
1929
|
+
this.body = body;
|
|
1930
|
+
return this;
|
|
1931
|
+
}
|
|
1932
|
+
resolveCollectsConfig() {
|
|
1933
|
+
const collectedResource = this.collects;
|
|
1934
|
+
if (!collectedResource) return;
|
|
1935
|
+
const collectedConfig = typeof collectedResource.config === "function" ? collectedResource.config() : {};
|
|
1936
|
+
return {
|
|
1937
|
+
preferredCase: collectedConfig.preferredCase ?? collectedResource.preferredCase,
|
|
1938
|
+
responseStructure: {
|
|
1939
|
+
...collectedResource.responseStructure || {},
|
|
1940
|
+
...collectedConfig.responseStructure || {}
|
|
1941
|
+
}
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
resolveResponseStructure() {
|
|
1945
|
+
return this.resolveSerializerResponseStructure(this.constructor, this.resolveCollectsConfig());
|
|
1946
|
+
}
|
|
1947
|
+
/**
|
|
1948
|
+
* Resolve the current root key for the response structure, based on configuration and defaults.
|
|
1949
|
+
*
|
|
1950
|
+
* @returns
|
|
1951
|
+
*/
|
|
1952
|
+
resolveCurrentRootKey() {
|
|
1953
|
+
return this.resolveResponseStructure().rootKey;
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
* Apply metadata properties to the response body, ensuring they are merged with
|
|
1957
|
+
* any existing properties and respecting the configured root key.
|
|
1958
|
+
*
|
|
1959
|
+
* @param meta
|
|
1960
|
+
* @param rootKey
|
|
1961
|
+
*/
|
|
1962
|
+
applyMetaToBody(meta, rootKey) {
|
|
1963
|
+
this.body = appendRootProperties(this.body, meta, rootKey);
|
|
1964
|
+
}
|
|
1965
|
+
/**
|
|
1966
|
+
* Get the resource data to be used for generating metadata, allowing for
|
|
1967
|
+
* customization in subclasses.
|
|
1968
|
+
*
|
|
1969
|
+
* @returns
|
|
1970
|
+
*/
|
|
1971
|
+
getResourceForMeta() {
|
|
1972
|
+
return this.resource;
|
|
1973
|
+
}
|
|
1974
|
+
/**
|
|
1975
|
+
* Get the appropriate key for the response payload based on the current response
|
|
1976
|
+
* structure configuration.
|
|
1977
|
+
*
|
|
1978
|
+
* @returns The key to use for the response payload, or undefined if no key is needed.
|
|
1979
|
+
*/
|
|
1980
|
+
getPayloadKey() {
|
|
1981
|
+
const { wrap, rootKey, factory } = this.resolveResponseStructure();
|
|
1982
|
+
return factory || !wrap ? void 0 : rootKey;
|
|
1983
|
+
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Convert resource to JSON response format
|
|
1986
|
+
*
|
|
1987
|
+
* @returns
|
|
1988
|
+
*/
|
|
1989
|
+
json() {
|
|
1990
|
+
if (!this.called.json) {
|
|
1991
|
+
this.called.json = true;
|
|
1992
|
+
let data = this.data();
|
|
1993
|
+
if (this.collects) data = data.map((item) => new this.collects(item).data());
|
|
1994
|
+
data = normalizeSerializableData(data);
|
|
1995
|
+
data = sanitizeConditionalAttributes(data);
|
|
1996
|
+
const paginationExtras = !Array.isArray(this.resource) ? buildPaginationExtras(this.resource) : {};
|
|
1997
|
+
const { metaKey } = getPaginationExtraKeys();
|
|
1998
|
+
const configuredMeta = metaKey ? paginationExtras[metaKey] : void 0;
|
|
1999
|
+
if (metaKey) delete paginationExtras[metaKey];
|
|
2000
|
+
const caseStyle = this.resolveSerializerCaseStyle(this.constructor, this.resolveCollectsConfig());
|
|
2001
|
+
if (caseStyle) {
|
|
2002
|
+
const transformer = getCaseTransformer(caseStyle);
|
|
2003
|
+
data = transformKeys(data, transformer);
|
|
2004
|
+
}
|
|
2005
|
+
const customMeta = this.resolveMergedMeta(ResourceCollection.prototype.with);
|
|
2006
|
+
const { wrap, rootKey, factory } = this.resolveResponseStructure();
|
|
2007
|
+
this.body = buildResponseEnvelope({
|
|
2008
|
+
payload: data,
|
|
2009
|
+
meta: configuredMeta,
|
|
2010
|
+
metaKey,
|
|
2011
|
+
wrap,
|
|
2012
|
+
rootKey,
|
|
2013
|
+
factory,
|
|
2014
|
+
context: {
|
|
2015
|
+
type: "collection",
|
|
2016
|
+
resource: this.resource
|
|
2017
|
+
}
|
|
2018
|
+
});
|
|
2019
|
+
this.body = appendRootProperties(this.body, {
|
|
2020
|
+
...paginationExtras,
|
|
2021
|
+
...customMeta || {}
|
|
2022
|
+
}, rootKey);
|
|
2023
|
+
}
|
|
2024
|
+
return this;
|
|
2025
|
+
}
|
|
2026
|
+
/**
|
|
2027
|
+
* Convert resource to object format and return original data.
|
|
2028
|
+
*
|
|
2029
|
+
* @returns
|
|
2030
|
+
*/
|
|
2031
|
+
toObject() {
|
|
2032
|
+
this.called.toObject = true;
|
|
2033
|
+
this.json();
|
|
2034
|
+
return normalizeSerializableData(Array.isArray(this.resource) ? this.resource : isArkormLikeCollection(this.resource) ? this.resource.all() : this.resource.data);
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Convert resource to object format and return original data.
|
|
2038
|
+
*
|
|
2039
|
+
* @deprecated Use toObject() instead.
|
|
2040
|
+
* @alias toArray
|
|
2041
|
+
* @since 0.2.9
|
|
2042
|
+
*/
|
|
2043
|
+
toArray() {
|
|
2044
|
+
this.called.toArray = true;
|
|
2045
|
+
return this.toObject();
|
|
2046
|
+
}
|
|
2047
|
+
/**
|
|
2048
|
+
* Add additional properties to the response body
|
|
2049
|
+
*
|
|
2050
|
+
* @param extra Additional properties to merge into the response body
|
|
2051
|
+
* @returns
|
|
2052
|
+
*/
|
|
2053
|
+
additional(extra) {
|
|
2054
|
+
this.called.additional = true;
|
|
2055
|
+
this.json();
|
|
2056
|
+
delete extra.cursor;
|
|
2057
|
+
delete extra.pagination;
|
|
2058
|
+
const payloadKey = this.getPayloadKey();
|
|
2059
|
+
if (extra.data && payloadKey && Array.isArray(this.body[payloadKey])) this.body[payloadKey] = [...this.body[payloadKey], ...extra.data];
|
|
2060
|
+
this.body = {
|
|
2061
|
+
...this.body,
|
|
2062
|
+
...extra
|
|
2063
|
+
};
|
|
2064
|
+
return this;
|
|
2065
|
+
}
|
|
2066
|
+
/**
|
|
2067
|
+
* Build a response object, optionally accepting a raw response to mutate in withResponse.
|
|
2068
|
+
*
|
|
2069
|
+
* @param res Optional raw response object (e.g. Express Response or H3Event res)
|
|
2070
|
+
* @returns
|
|
2071
|
+
*/
|
|
2072
|
+
response(res) {
|
|
2073
|
+
const rawResponse = res ?? this.res ?? ResourceCollection.ctx?.res ?? ResourceCollection.ctx;
|
|
2074
|
+
return this.runResponse({
|
|
2075
|
+
ensureJson: () => this.json(),
|
|
2076
|
+
rawResponse,
|
|
2077
|
+
body: () => this.body,
|
|
2078
|
+
createServerResponse: (raw, body) => {
|
|
2079
|
+
const response = new ServerResponse(raw, body);
|
|
2080
|
+
this.withResponseContext = {
|
|
2081
|
+
response,
|
|
2082
|
+
raw
|
|
2083
|
+
};
|
|
2084
|
+
return response;
|
|
2085
|
+
},
|
|
2086
|
+
callWithResponse: (response, raw) => {
|
|
2087
|
+
this.withResponse(response, raw);
|
|
2088
|
+
}
|
|
2089
|
+
});
|
|
2090
|
+
}
|
|
2091
|
+
/**
|
|
2092
|
+
* Customize the outgoing transport response right before dispatch.
|
|
2093
|
+
*
|
|
2094
|
+
* Override in custom classes to mutate headers/status/body.
|
|
2095
|
+
*/
|
|
2096
|
+
withResponse(_response, _rawResponse) {
|
|
2097
|
+
return this;
|
|
2098
|
+
}
|
|
2099
|
+
setCollects(collects) {
|
|
2100
|
+
this.collects = collects;
|
|
2101
|
+
return this;
|
|
2102
|
+
}
|
|
2103
|
+
/**
|
|
2104
|
+
* Promise-like then method to allow chaining with async/await or .then() syntax
|
|
2105
|
+
*
|
|
2106
|
+
* @param onfulfilled Callback to handle the fulfilled state of the promise, receiving the response body
|
|
2107
|
+
* @param onrejected Callback to handle the rejected state of the promise, receiving the error reason
|
|
2108
|
+
* @returns A promise that resolves to the result of the onfulfilled or onrejected callback
|
|
2109
|
+
*/
|
|
2110
|
+
then(onfulfilled, onrejected) {
|
|
2111
|
+
return this.runThen({
|
|
2112
|
+
ensureJson: () => this.json(),
|
|
2113
|
+
body: () => this.body,
|
|
2114
|
+
rawResponse: this.res ?? ResourceCollection.ctx?.res ?? ResourceCollection.ctx,
|
|
2115
|
+
createServerResponse: (raw, body) => {
|
|
2116
|
+
const response = new ServerResponse(raw, body);
|
|
2117
|
+
this.withResponseContext = {
|
|
2118
|
+
response,
|
|
2119
|
+
raw
|
|
2120
|
+
};
|
|
2121
|
+
return response;
|
|
2122
|
+
},
|
|
2123
|
+
callWithResponse: (response, raw) => {
|
|
2124
|
+
this.withResponse(response, raw);
|
|
2125
|
+
},
|
|
2126
|
+
sendRawResponse: (raw, body) => {
|
|
2127
|
+
raw.send(body);
|
|
2128
|
+
},
|
|
2129
|
+
onfulfilled,
|
|
2130
|
+
onrejected
|
|
2131
|
+
});
|
|
2132
|
+
}
|
|
2133
|
+
/**
|
|
2134
|
+
* Promise-like catch method to handle rejected state of the promise
|
|
2135
|
+
*
|
|
2136
|
+
* @param onrejected
|
|
2137
|
+
* @returns
|
|
2138
|
+
*/
|
|
2139
|
+
catch(onrejected) {
|
|
2140
|
+
return this.runThen({
|
|
2141
|
+
ensureJson: () => this.json(),
|
|
2142
|
+
body: () => this.body,
|
|
2143
|
+
rawResponse: this.res ?? ResourceCollection.ctx?.res ?? ResourceCollection.ctx,
|
|
2144
|
+
createServerResponse: (raw, body) => {
|
|
2145
|
+
const response = new ServerResponse(raw, body);
|
|
2146
|
+
this.withResponseContext = {
|
|
2147
|
+
response,
|
|
2148
|
+
raw
|
|
2149
|
+
};
|
|
2150
|
+
return response;
|
|
2151
|
+
},
|
|
2152
|
+
callWithResponse: (response, raw) => {
|
|
2153
|
+
this.withResponse(response, raw);
|
|
2154
|
+
},
|
|
2155
|
+
sendRawResponse: (raw, body) => {
|
|
2156
|
+
raw.send(body);
|
|
2157
|
+
},
|
|
2158
|
+
onrejected
|
|
2159
|
+
});
|
|
2160
|
+
}
|
|
2161
|
+
/**
|
|
2162
|
+
* Promise-like finally method to handle cleanup after promise is settled
|
|
2163
|
+
*
|
|
2164
|
+
* @param onfinally
|
|
2165
|
+
* @returns
|
|
2166
|
+
*/
|
|
2167
|
+
finally(onfinally) {
|
|
2168
|
+
return this.runThen({
|
|
2169
|
+
ensureJson: () => this.json(),
|
|
2170
|
+
body: () => this.body,
|
|
2171
|
+
rawResponse: this.res ?? ResourceCollection.ctx?.res ?? ResourceCollection.ctx,
|
|
2172
|
+
createServerResponse: (raw, body) => {
|
|
2173
|
+
const response = new ServerResponse(raw, body);
|
|
2174
|
+
this.withResponseContext = {
|
|
2175
|
+
response,
|
|
2176
|
+
raw
|
|
2177
|
+
};
|
|
2178
|
+
return response;
|
|
2179
|
+
},
|
|
2180
|
+
callWithResponse: (response, raw) => {
|
|
2181
|
+
this.withResponse(response, raw);
|
|
2182
|
+
},
|
|
2183
|
+
sendRawResponse: (raw, body) => {
|
|
2184
|
+
raw.send(body);
|
|
2185
|
+
},
|
|
2186
|
+
onfulfilled: onfinally,
|
|
2187
|
+
onrejected: onfinally
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
};
|
|
2191
|
+
|
|
2192
|
+
//#endregion
|
|
2193
|
+
//#region src/Resource.ts
|
|
2194
|
+
/**
|
|
2195
|
+
* Resource class to handle API resource transformation and response building
|
|
2196
|
+
*
|
|
2197
|
+
*
|
|
2198
|
+
* @author Legacy (3m1n3nc3)
|
|
2199
|
+
* @since 0.1.0
|
|
2200
|
+
* @see BaseSerializer for shared serialization logic and configuration handling
|
|
2201
|
+
*/
|
|
2202
|
+
var Resource = class Resource extends BaseSerializer {
|
|
2203
|
+
body = { data: {} };
|
|
2204
|
+
res;
|
|
2205
|
+
resource;
|
|
2206
|
+
withResponseContext;
|
|
2207
|
+
constructor(rsc, ctx) {
|
|
2208
|
+
super();
|
|
2209
|
+
if (ctx) Resource.ctx = ctx;
|
|
2210
|
+
this.resource = rsc;
|
|
2211
|
+
if (ctx) {
|
|
2212
|
+
const url = extractRequestUrl(ctx);
|
|
2213
|
+
if (url) setRequestUrl(url);
|
|
2214
|
+
this.res = extractResponseFromCtx(ctx);
|
|
2215
|
+
}
|
|
2216
|
+
const source = this.resource.data ?? this.resource;
|
|
2217
|
+
/**
|
|
2218
|
+
* Copy properties from rsc to this instance for easy
|
|
2219
|
+
* access, but only if data is not an array
|
|
2220
|
+
*/
|
|
2221
|
+
if (!Array.isArray(source)) {
|
|
2222
|
+
const sourceKeys = isArkormLikeModel(source) ? Object.keys(source.toObject()) : Object.keys(source);
|
|
2223
|
+
for (const key of sourceKeys) if (!(key in this)) Object.defineProperty(this, key, {
|
|
2224
|
+
enumerable: true,
|
|
2225
|
+
configurable: true,
|
|
2226
|
+
get: () => {
|
|
2227
|
+
if (isArkormLikeModel(source) && typeof source.getAttribute === "function") return source.getAttribute(key);
|
|
2228
|
+
return this.resource.data?.[key] ?? this.resource[key];
|
|
2229
|
+
},
|
|
2230
|
+
set: (value) => {
|
|
2231
|
+
if (isArkormLikeModel(source) && typeof source.setAttribute === "function") {
|
|
2232
|
+
source.setAttribute(key, value);
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
if (this.resource.data && this.resource.data[key]) this.resource.data[key] = value;
|
|
2236
|
+
else this.resource[key] = value;
|
|
2237
|
+
}
|
|
2238
|
+
});
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
/**
|
|
2242
|
+
* Create a ResourceCollection from an array of resource data or a Collectible instance
|
|
2243
|
+
*
|
|
2244
|
+
* @param data
|
|
2245
|
+
* @returns
|
|
2246
|
+
*/
|
|
2247
|
+
static collection(data) {
|
|
2248
|
+
return new ResourceCollection(data).setCollects(this);
|
|
2249
|
+
}
|
|
2250
|
+
/**
|
|
2251
|
+
* Get the original resource data
|
|
2252
|
+
*/
|
|
2253
|
+
data() {
|
|
2254
|
+
return this.toObject();
|
|
2255
|
+
}
|
|
2256
|
+
/**
|
|
2257
|
+
* Get the current serialized output body.
|
|
2258
|
+
*/
|
|
2259
|
+
getBody() {
|
|
2260
|
+
this.json();
|
|
2261
|
+
return this.body;
|
|
2262
|
+
}
|
|
2263
|
+
/**
|
|
2264
|
+
* Replace the current serialized output body.
|
|
2265
|
+
*/
|
|
2266
|
+
setBody(body) {
|
|
2267
|
+
this.body = body;
|
|
2268
|
+
return this;
|
|
2269
|
+
}
|
|
2270
|
+
resolveResponseStructure() {
|
|
2271
|
+
return this.resolveSerializerResponseStructure(this.constructor);
|
|
2272
|
+
}
|
|
2273
|
+
/**
|
|
2274
|
+
* Resolve the current root key for the response body based on configuration and defaults.
|
|
2275
|
+
*
|
|
2276
|
+
* @returns
|
|
2277
|
+
*/
|
|
2278
|
+
resolveCurrentRootKey() {
|
|
2279
|
+
return this.resolveResponseStructure().rootKey;
|
|
2280
|
+
}
|
|
2281
|
+
applyMetaToBody(meta, rootKey) {
|
|
2282
|
+
this.body = appendRootProperties(this.body, meta, rootKey);
|
|
2283
|
+
}
|
|
2284
|
+
getResourceForMeta() {
|
|
2285
|
+
return this.resource;
|
|
2286
|
+
}
|
|
2287
|
+
getPayloadKey() {
|
|
2288
|
+
const { wrap, rootKey, factory } = this.resolveResponseStructure();
|
|
2289
|
+
return factory || !wrap ? void 0 : rootKey;
|
|
2290
|
+
}
|
|
2291
|
+
/**
|
|
2292
|
+
* Convert resource to JSON response format
|
|
2293
|
+
*
|
|
2294
|
+
* @returns
|
|
2295
|
+
*/
|
|
2296
|
+
json() {
|
|
2297
|
+
if (!this.called.json) {
|
|
2298
|
+
this.called.json = true;
|
|
2299
|
+
let data = normalizeSerializableData(this.data());
|
|
2300
|
+
if (!Array.isArray(data) && data && typeof data.data !== "undefined") data = data.data;
|
|
2301
|
+
data = sanitizeConditionalAttributes(data);
|
|
2302
|
+
const caseStyle = this.resolveSerializerCaseStyle(this.constructor);
|
|
2303
|
+
if (caseStyle) {
|
|
2304
|
+
const transformer = getCaseTransformer(caseStyle);
|
|
2305
|
+
data = transformKeys(data, transformer);
|
|
2306
|
+
}
|
|
2307
|
+
const customMeta = this.resolveMergedMeta(Resource.prototype.with);
|
|
2308
|
+
const { wrap, rootKey, factory } = this.resolveResponseStructure();
|
|
2309
|
+
this.body = buildResponseEnvelope({
|
|
2310
|
+
payload: data,
|
|
2311
|
+
wrap,
|
|
2312
|
+
rootKey,
|
|
2313
|
+
factory,
|
|
2314
|
+
context: {
|
|
2315
|
+
type: "resource",
|
|
2316
|
+
resource: this.resource
|
|
2317
|
+
}
|
|
2318
|
+
});
|
|
2319
|
+
this.body = appendRootProperties(this.body, customMeta, rootKey);
|
|
2320
|
+
}
|
|
2321
|
+
return this;
|
|
2322
|
+
}
|
|
2323
|
+
/**
|
|
2324
|
+
* Convert resource to object format (for collections) or return original data for single resources.
|
|
2325
|
+
*
|
|
2326
|
+
* @returns
|
|
2327
|
+
*/
|
|
2328
|
+
toObject() {
|
|
2329
|
+
this.called.toObject = true;
|
|
2330
|
+
this.json();
|
|
2331
|
+
let data = normalizeSerializableData(this.resource);
|
|
2332
|
+
if (!Array.isArray(data) && data && typeof data.data !== "undefined") data = data.data;
|
|
2333
|
+
return data;
|
|
2334
|
+
}
|
|
2335
|
+
/**
|
|
2336
|
+
* Convert resource to object format and return original data.
|
|
2337
|
+
*
|
|
2338
|
+
* @deprecated Use toObject() instead.
|
|
2339
|
+
* @alias toArray
|
|
2340
|
+
* @since 0.2.9
|
|
2341
|
+
*/
|
|
2342
|
+
toArray() {
|
|
2343
|
+
this.called.toArray = true;
|
|
2344
|
+
return this.toObject();
|
|
2345
|
+
}
|
|
2346
|
+
/**
|
|
2347
|
+
* Add additional properties to the response body
|
|
2348
|
+
*
|
|
2349
|
+
* @param extra Additional properties to merge into the response body
|
|
2350
|
+
* @returns
|
|
2351
|
+
*/
|
|
2352
|
+
additional(extra) {
|
|
2353
|
+
this.called.additional = true;
|
|
2354
|
+
this.json();
|
|
2355
|
+
const payloadKey = this.getPayloadKey();
|
|
2356
|
+
if (extra.data && payloadKey && typeof this.body[payloadKey] !== "undefined") this.body[payloadKey] = Array.isArray(this.body[payloadKey]) ? [...this.body[payloadKey], ...extra.data] : {
|
|
2357
|
+
...this.body[payloadKey],
|
|
2358
|
+
...extra.data
|
|
2359
|
+
};
|
|
2360
|
+
this.body = {
|
|
2361
|
+
...this.body,
|
|
2362
|
+
...extra
|
|
2363
|
+
};
|
|
2364
|
+
return this;
|
|
2365
|
+
}
|
|
2366
|
+
/**
|
|
2367
|
+
* Build a response object, optionally accepting a raw response to mutate in withResponse.
|
|
2368
|
+
*
|
|
2369
|
+
* @param res Optional raw response object (e.g. Express Response or H3Event res)
|
|
2370
|
+
* @returns
|
|
2371
|
+
*/
|
|
2372
|
+
response(res) {
|
|
2373
|
+
const rawResponse = res ?? this.res ?? Resource.ctx?.res ?? Resource.ctx;
|
|
2374
|
+
return this.runResponse({
|
|
2375
|
+
ensureJson: () => this.json(),
|
|
2376
|
+
rawResponse,
|
|
2377
|
+
body: () => this.body,
|
|
2378
|
+
createServerResponse: (raw, body) => {
|
|
2379
|
+
const response = new ServerResponse(raw, body);
|
|
2380
|
+
this.withResponseContext = {
|
|
2381
|
+
response,
|
|
2382
|
+
raw
|
|
2383
|
+
};
|
|
2384
|
+
return response;
|
|
2385
|
+
},
|
|
2386
|
+
callWithResponse: (response, raw) => {
|
|
2387
|
+
this.withResponse(response, raw);
|
|
2388
|
+
}
|
|
2389
|
+
});
|
|
2390
|
+
}
|
|
2391
|
+
/**
|
|
2392
|
+
* Customize the outgoing transport response right before dispatch.
|
|
2393
|
+
*
|
|
2394
|
+
* Override in custom classes to mutate headers/status/body.
|
|
2395
|
+
*/
|
|
2396
|
+
withResponse(_response, _rawResponse) {
|
|
2397
|
+
return this;
|
|
2398
|
+
}
|
|
2399
|
+
/**
|
|
2400
|
+
* Promise-like then method to allow chaining with async/await or .then() syntax
|
|
2401
|
+
*
|
|
2402
|
+
* @param onfulfilled Callback to handle the fulfilled state of the promise, receiving the response body
|
|
2403
|
+
* @param onrejected Callback to handle the rejected state of the promise, receiving the error reason
|
|
2404
|
+
* @returns A promise that resolves to the result of the onfulfilled or onrejected callback
|
|
2405
|
+
*/
|
|
2406
|
+
then(onfulfilled, onrejected) {
|
|
2407
|
+
return this.runThen({
|
|
2408
|
+
ensureJson: () => this.json(),
|
|
2409
|
+
body: () => this.body,
|
|
2410
|
+
rawResponse: this.res ?? Resource.ctx?.res ?? Resource.ctx,
|
|
2411
|
+
createServerResponse: (raw, body) => {
|
|
2412
|
+
const response = new ServerResponse(raw, body);
|
|
2413
|
+
this.withResponseContext = {
|
|
2414
|
+
response,
|
|
2415
|
+
raw
|
|
2416
|
+
};
|
|
2417
|
+
return response;
|
|
2418
|
+
},
|
|
2419
|
+
callWithResponse: (response, raw) => {
|
|
2420
|
+
this.withResponse(response, raw);
|
|
2421
|
+
},
|
|
2422
|
+
sendRawResponse: (raw, body) => {
|
|
2423
|
+
raw.send(body);
|
|
2424
|
+
},
|
|
2425
|
+
onfulfilled,
|
|
2426
|
+
onrejected
|
|
2427
|
+
});
|
|
2428
|
+
}
|
|
2429
|
+
/**
|
|
2430
|
+
* Promise-like catch method to handle rejected state of the promise
|
|
2431
|
+
*
|
|
2432
|
+
* @param onrejected
|
|
2433
|
+
* @returns
|
|
2434
|
+
*/
|
|
2435
|
+
catch(onrejected) {
|
|
2436
|
+
return this.runThen({
|
|
2437
|
+
ensureJson: () => this.json(),
|
|
2438
|
+
body: () => this.body,
|
|
2439
|
+
rawResponse: this.res ?? Resource.ctx?.res ?? Resource.ctx,
|
|
2440
|
+
createServerResponse: (raw, body) => {
|
|
2441
|
+
const response = new ServerResponse(raw, body);
|
|
2442
|
+
this.withResponseContext = {
|
|
2443
|
+
response,
|
|
2444
|
+
raw
|
|
2445
|
+
};
|
|
2446
|
+
return response;
|
|
2447
|
+
},
|
|
2448
|
+
callWithResponse: (response, raw) => {
|
|
2449
|
+
this.withResponse(response, raw);
|
|
2450
|
+
},
|
|
2451
|
+
sendRawResponse: (raw, body) => {
|
|
2452
|
+
raw.send(body);
|
|
2453
|
+
},
|
|
2454
|
+
onrejected
|
|
2455
|
+
});
|
|
2456
|
+
}
|
|
2457
|
+
/**
|
|
2458
|
+
* Promise-like finally method to handle cleanup after promise is settled
|
|
2459
|
+
*
|
|
2460
|
+
* @param onfinally
|
|
2461
|
+
* @returns
|
|
2462
|
+
*/
|
|
2463
|
+
finally(onfinally) {
|
|
2464
|
+
return this.runThen({
|
|
2465
|
+
ensureJson: () => this.json(),
|
|
2466
|
+
body: () => this.body,
|
|
2467
|
+
rawResponse: this.res ?? Resource.ctx?.res ?? Resource.ctx,
|
|
2468
|
+
createServerResponse: (raw, body) => {
|
|
2469
|
+
const response = new ServerResponse(raw, body);
|
|
2470
|
+
this.withResponseContext = {
|
|
2471
|
+
response,
|
|
2472
|
+
raw
|
|
2473
|
+
};
|
|
2474
|
+
return response;
|
|
2475
|
+
},
|
|
2476
|
+
callWithResponse: (response, raw) => {
|
|
2477
|
+
this.withResponse(response, raw);
|
|
2478
|
+
},
|
|
2479
|
+
sendRawResponse: (raw, body) => {
|
|
2480
|
+
raw.send(body);
|
|
2481
|
+
},
|
|
2482
|
+
onfulfilled: onfinally,
|
|
2483
|
+
onrejected: onfinally
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2486
|
+
};
|
|
2487
|
+
|
|
2488
|
+
//#endregion
|
|
2489
|
+
exports.ApiResource = ApiResource;
|
|
2490
|
+
exports.CONDITIONAL_ATTRIBUTE_MISSING = CONDITIONAL_ATTRIBUTE_MISSING;
|
|
2491
|
+
exports.CliApp = CliApp;
|
|
2492
|
+
exports.GenericResource = GenericResource;
|
|
2493
|
+
exports.InitCommand = InitCommand;
|
|
2494
|
+
exports.MakeResource = MakeResource;
|
|
2495
|
+
exports.Resource = Resource;
|
|
2496
|
+
exports.ResourceCollection = ResourceCollection;
|
|
2497
|
+
exports.ServerResponse = ServerResponse;
|
|
2498
|
+
exports.appendRootProperties = appendRootProperties;
|
|
2499
|
+
exports.applyRuntimeConfig = applyRuntimeConfig;
|
|
2500
|
+
exports.buildPaginationExtras = buildPaginationExtras;
|
|
2501
|
+
exports.buildResponseEnvelope = buildResponseEnvelope;
|
|
2502
|
+
exports.createArkormCurrentPageResolver = createArkormCurrentPageResolver;
|
|
2503
|
+
exports.defineConfig = defineConfig;
|
|
2504
|
+
exports.extractRequestUrl = extractRequestUrl;
|
|
2505
|
+
exports.extractResponseFromCtx = extractResponseFromCtx;
|
|
2506
|
+
exports.getCaseTransformer = getCaseTransformer;
|
|
2507
|
+
exports.getDefaultConfig = getDefaultConfig;
|
|
2508
|
+
exports.getGlobalBaseUrl = getGlobalBaseUrl;
|
|
2509
|
+
exports.getGlobalCase = getGlobalCase;
|
|
2510
|
+
exports.getGlobalCursorMeta = getGlobalCursorMeta;
|
|
2511
|
+
exports.getGlobalPageName = getGlobalPageName;
|
|
2512
|
+
exports.getGlobalPaginatedExtras = getGlobalPaginatedExtras;
|
|
2513
|
+
exports.getGlobalPaginatedLinks = getGlobalPaginatedLinks;
|
|
2514
|
+
exports.getGlobalPaginatedMeta = getGlobalPaginatedMeta;
|
|
2515
|
+
exports.getGlobalResponseFactory = getGlobalResponseFactory;
|
|
2516
|
+
exports.getGlobalResponseRootKey = getGlobalResponseRootKey;
|
|
2517
|
+
exports.getGlobalResponseStructure = getGlobalResponseStructure;
|
|
2518
|
+
exports.getGlobalResponseWrap = getGlobalResponseWrap;
|
|
2519
|
+
exports.getPaginationExtraKeys = getPaginationExtraKeys;
|
|
2520
|
+
exports.getRequestUrl = getRequestUrl;
|
|
2521
|
+
exports.hasPaginationLink = hasPaginationLink;
|
|
2522
|
+
exports.isArkormLikeCollection = isArkormLikeCollection;
|
|
2523
|
+
exports.isArkormLikeModel = isArkormLikeModel;
|
|
2524
|
+
exports.isPlainObject = isPlainObject;
|
|
2525
|
+
exports.loadRuntimeConfig = loadRuntimeConfig;
|
|
2526
|
+
exports.mergeMetadata = mergeMetadata;
|
|
2527
|
+
exports.normalizeSerializableData = normalizeSerializableData;
|
|
2528
|
+
exports.resetRuntimeConfigForTests = resetRuntimeConfigForTests;
|
|
2529
|
+
exports.resolveCurrentPage = resolveCurrentPage;
|
|
2530
|
+
exports.resolveMergeWhen = resolveMergeWhen;
|
|
2531
|
+
exports.resolveWhen = resolveWhen;
|
|
2532
|
+
exports.resolveWhenNotNull = resolveWhenNotNull;
|
|
2533
|
+
exports.resolveWithHookMetadata = resolveWithHookMetadata;
|
|
2534
|
+
exports.sanitizeConditionalAttributes = sanitizeConditionalAttributes;
|
|
2535
|
+
exports.setCtx = setCtx;
|
|
2536
|
+
exports.setGlobalBaseUrl = setGlobalBaseUrl;
|
|
2537
|
+
exports.setGlobalCase = setGlobalCase;
|
|
2538
|
+
exports.setGlobalCursorMeta = setGlobalCursorMeta;
|
|
2539
|
+
exports.setGlobalPageName = setGlobalPageName;
|
|
2540
|
+
exports.setGlobalPaginatedExtras = setGlobalPaginatedExtras;
|
|
2541
|
+
exports.setGlobalPaginatedLinks = setGlobalPaginatedLinks;
|
|
2542
|
+
exports.setGlobalPaginatedMeta = setGlobalPaginatedMeta;
|
|
2543
|
+
exports.setGlobalResponseFactory = setGlobalResponseFactory;
|
|
2544
|
+
exports.setGlobalResponseRootKey = setGlobalResponseRootKey;
|
|
2545
|
+
exports.setGlobalResponseStructure = setGlobalResponseStructure;
|
|
2546
|
+
exports.setGlobalResponseWrap = setGlobalResponseWrap;
|
|
2547
|
+
exports.setRequestUrl = setRequestUrl;
|
|
2548
|
+
exports.splitWords = splitWords;
|
|
2549
|
+
exports.toCamelCase = toCamelCase;
|
|
2550
|
+
exports.toKebabCase = toKebabCase;
|
|
2551
|
+
exports.toPascalCase = toPascalCase;
|
|
2552
|
+
exports.toSnakeCase = toSnakeCase;
|
|
2553
|
+
exports.transformKeys = transformKeys;
|