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.
Files changed (3) hide show
  1. package/dist/index.cjs +2533 -5
  2. package/dist/index.mjs +2440 -5
  3. 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
- `),u=`import ${o} from './${o}'\n`,d=(!!t?.collection||e.endsWith(`Collection`))&&(0,c.existsSync)((0,l.join)(this.config.resourcesDir,`${o}.ts`)),f=this.generateFile(a,i,{ResourceName:n,CollectionResourceName:n.replace(/(Resource|Collection)$/,``)+`Resource`,"collects = Resource":d?s:``,"import = Resource":d?u:``},t);return{name:n,path:f}}},Be=class extends f.Command{signature=`init
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
- `;description=`Initialize Resora`;async handle(){this.app.command=this,this.app.init(),this.success(`Resora initialized`)}},Ve=class extends f.Command{signature=`#create:
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
- `;description=`Create a new resource or resource collection file`;async handle(){this.app.command=this;let e=``,t=this.argument(`name`)||this.argument(`prefix`),n=this.dictionary.name||this.dictionary.baseCommand;if([`resource`,`collection`].includes(n)&&!t)return void this.error(`Error: Name argument is required.`);if(n===`all`&&!t)return void this.error(`Error: Prefix argument is required.`);switch(n){case`resource`:({path:e}=this.app.makeResource(t,this.options()));break;case`collection`:({path:e}=this.app.makeResource(t+`Collection`,this.options()));break;case`all`:{let n=this.app.makeResource(t,{force:this.option(`force`)}),r=this.app.makeResource(t+`Collection`,{collection:!0,force:this.option(`force`)});e=`${n.path}, ${r.path}`;break}default:this.fail(`Unknown action: ${n}`)}this.success(`Created: ${e}`)}};String.raw`
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;