resora 0.2.17 → 1.0.0

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