resora 0.2.18 → 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/README.md CHANGED
@@ -174,6 +174,23 @@ It works with:
174
174
 
175
175
  Adapters can be added without changing application logic.
176
176
 
177
+ ## Plugin System
178
+
179
+ Resora exposes a first-class plugin registry for opt-in integrations and lifecycle extensions.
180
+
181
+ ```ts
182
+ import { registerPlugin } from 'resora';
183
+ import { clearRouterExpressPlugin } from '@resora/plugin-clear-router';
184
+
185
+ registerPlugin(clearRouterExpressPlugin);
186
+ ```
187
+
188
+ Plugins can:
189
+
190
+ - hook into serialization and response dispatch
191
+ - inject framework integrations without core changes
192
+ - register reusable transformation utilities
193
+
177
194
  ## Conditional Rendering Example
178
195
 
179
196
  ```ts
package/bin/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import{copyFileSync as e,existsSync as t,mkdirSync as n,readFileSync as r,rmSync as i,writeFileSync as a}from"fs";import o,{dirname as s,join as c}from"path";import{createRequire as l}from"module";import{pathToFileURL as u}from"url";import{Command as d,Kernel as f}from"@h3ravel/musket";let p={first:`first`,last:`last`,prev:`prev`,next:`next`},m={previous:`previous`,next:`next`};const h=e=>{p={...p,...e}},g=e=>{m={...m,...e}};let _=o.resolve(process.cwd(),`node_modules/resora/stubs`);t(_)||(_=o.resolve(process.cwd(),`stubs`));const v=()=>({stubsDir:_,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`}}),y=e=>{let t=v();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 b=!1,x;const S=e=>{e.preferredCase!==`camel`&&e.preferredCase,e.responseStructure,e.paginatedExtras,h(e.paginatedLinks),e.paginatedMeta,g(e.cursorMeta),e.baseUrl,e.pageName},C=async e=>await import(`${u(e).href}?resora_runtime=${Date.now()}`),w=e=>{S(y((e?.default??e)||{})),b=!0},T=()=>{let e=l(import.meta.url),n=[o.join(process.cwd(),`resora.config.cjs`)];for(let r of n)if(t(r))try{return w(e(r)),!0}catch{continue}return!1};(async()=>{if(!b){if(x)return await x;T()||(x=(async()=>{let e=[o.join(process.cwd(),`resora.config.js`),o.join(process.cwd(),`resora.config.ts`)];for(let n of e)if(t(n))try{w(await C(n));return}catch{continue}b=!0})(),await x)}})();var E=class{command;config={};constructor(e={}){this.config=y(e)}async loadConfig(e={}){this.config=y(e);let n=[c(process.cwd(),`resora.config.ts`),c(process.cwd(),`resora.config.js`),c(process.cwd(),`resora.config.cjs`)];for(let e of n)if(t(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 n=c(process.cwd(),`resora.config.js`),i=c(this.config.stubsDir,this.config.stubs.config);return t(n)&&!this.command.option(`force`)&&(this.command.error(`Error: ${n} already exists.`),process.exit(1)),this.ensureDirectory(n),t(n)&&this.command.option(`force`)&&e(n,n.replace(/\.js$/,`.backup.${Date.now()}.js`)),a(n,r(i,`utf-8`)),{path:n}}ensureDirectory(e){let r=s(e);t(r)||n(r,{recursive:!0})}generateFile(e,n,o,s){t(n)&&!s?.force?(this.command.error(`Error: ${n} already exists.`),process.exit(1)):t(n)&&s?.force&&i(n);let c=r(e,`utf-8`);for(let[e,t]of Object.entries(o))c=c.replace(RegExp(`{{${e}}}`,`g`),t);return this.ensureDirectory(n),a(n,c),n}makeResource(e,n){let r=e;n?.collection&&!e.endsWith(`Collection`)&&!e.endsWith(`Resource`)?r+=`Collection`:!n?.collection&&!e.endsWith(`Resource`)&&!e.endsWith(`Collection`)&&(r+=`Resource`);let i=`${r}.ts`,a=c(this.config.resourcesDir,i),o=c(this.config.stubsDir,n?.collection||e.endsWith(`Collection`)?this.config.stubs.collection:this.config.stubs.resource);t(o)||(this.command.error(`Error: Stub file ${o} not found.`),process.exit(1)),r=r.split(`/`).pop()?.split(`.`).shift();let s=r.replace(/(Resource|Collection)$/,``)+`Resource`,l=[`/**`,` * The resource that this collection collects.`,` */`,`collects = ${s}`].join(`
3
- `),u=`import ${s} from './${s}'\n`,d=(!!n?.collection||e.endsWith(`Collection`))&&t(c(this.config.resourcesDir,`${s}.ts`)),f=this.generateFile(o,a,{ResourceName:r,CollectionResourceName:r.replace(/(Resource|Collection)$/,``)+`Resource`,"collects = Resource":d?l:``,"import = Resource":d?u:``},n);return{name:r,path:f}}},D=class extends d{signature=`init
2
+ import{copyFileSync as e,existsSync as t,mkdirSync as n,readFileSync as r,rmSync as i,writeFileSync as a}from"fs";import o,{dirname as s,join as c}from"path";import{AsyncLocalStorage as l}from"node:async_hooks";import{createRequire as u}from"module";import{pathToFileURL as d}from"url";import{Command as f,Kernel as p}from"@h3ravel/musket";let m={first:`first`,last:`last`,prev:`prev`,next:`next`},h={previous:`previous`,next:`next`};const g=e=>{m={...m,...e}},_=e=>{h={...h,...e}};new l;let v=o.resolve(process.cwd(),`node_modules/resora/stubs`);t(v)||(v=o.resolve(process.cwd(),`stubs`));const y=()=>({stubsDir:v,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`}}),b=e=>{let t=y();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 x=!1,S;const C=e=>{e.preferredCase!==`camel`&&e.preferredCase,e.responseStructure,e.paginatedExtras,g(e.paginatedLinks),e.paginatedMeta,_(e.cursorMeta),e.baseUrl,e.pageName},w=async e=>await import(`${d(e).href}?resora_runtime=${Date.now()}`),T=e=>{C(b((e?.default??e)||{})),x=!0},E=()=>{let e=u(import.meta.url),n=[o.join(process.cwd(),`resora.config.cjs`)];for(let r of n)if(t(r))try{return T(e(r)),!0}catch{continue}return!1};(async()=>{if(!x){if(S)return await S;E()||(S=(async()=>{let e=[o.join(process.cwd(),`resora.config.js`),o.join(process.cwd(),`resora.config.ts`)];for(let n of e)if(t(n))try{T(await w(n));return}catch{continue}x=!0})(),await S)}})();var D=class{command;config={};constructor(e={}){this.config=b(e)}async loadConfig(e={}){this.config=b(e);let n=[c(process.cwd(),`resora.config.ts`),c(process.cwd(),`resora.config.js`),c(process.cwd(),`resora.config.cjs`)];for(let e of n)if(t(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 n=c(process.cwd(),`resora.config.js`),i=c(this.config.stubsDir,this.config.stubs.config);return t(n)&&!this.command.option(`force`)&&(this.command.error(`Error: ${n} already exists.`),process.exit(1)),this.ensureDirectory(n),t(n)&&this.command.option(`force`)&&e(n,n.replace(/\.js$/,`.backup.${Date.now()}.js`)),a(n,r(i,`utf-8`)),{path:n}}ensureDirectory(e){let r=s(e);t(r)||n(r,{recursive:!0})}generateFile(e,n,o,s){t(n)&&!s?.force?(this.command.error(`Error: ${n} already exists.`),process.exit(1)):t(n)&&s?.force&&i(n);let c=r(e,`utf-8`);for(let[e,t]of Object.entries(o))c=c.replace(RegExp(`{{${e}}}`,`g`),t);return this.ensureDirectory(n),a(n,c),n}makeResource(e,n){let r=e;n?.collection&&!e.endsWith(`Collection`)&&!e.endsWith(`Resource`)?r+=`Collection`:!n?.collection&&!e.endsWith(`Resource`)&&!e.endsWith(`Collection`)&&(r+=`Resource`);let i=`${r}.ts`,a=c(this.config.resourcesDir,i),o=c(this.config.stubsDir,n?.collection||e.endsWith(`Collection`)?this.config.stubs.collection:this.config.stubs.resource);t(o)||(this.command.error(`Error: Stub file ${o} not found.`),process.exit(1)),r=r.split(`/`).pop()?.split(`.`).shift();let s=r.replace(/(Resource|Collection)$/,``)+`Resource`,l=[`/**`,` * The resource that this collection collects.`,` */`,`collects = ${s}`].join(`
3
+ `),u=`import ${s} from './${s}'\n`,d=(!!n?.collection||e.endsWith(`Collection`))&&t(c(this.config.resourcesDir,`${s}.ts`)),f=this.generateFile(o,a,{ResourceName:r,CollectionResourceName:r.replace(/(Resource|Collection)$/,``)+`Resource`,"collects = Resource":d?l:``,"import = Resource":d?u:``},n);return{name:r,path:f}}},O=class extends f{signature=`init
4
4
  {--force : Force overwrite if config file already exists (existing file will be backed up) }
5
- `;description=`Initialize Resora`;async handle(){this.app.command=this,this.app.init(),this.success(`Resora initialized`)}},O=class extends d{signature=`#create:
5
+ `;description=`Initialize Resora`;async handle(){this.app.command=this,this.app.init(),this.success(`Resora initialized`)}},k=class extends f{signature=`#create:
6
6
  {resource : Generates a new resource file.
7
7
  | {name : Name of the resource to create}
8
8
  | {--c|collection : Make a resource collection}
@@ -16,11 +16,11 @@ import{copyFileSync as e,existsSync as t,mkdirSync as n,readFileSync as r,rmSync
16
16
  | {prefix : prefix of the resources to create, "Admin" will create AdminResource, AdminCollection}
17
17
  | {--force : Create the resource or collection file even if it already exists.}
18
18
  }
19
- `;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}`)}},k=String.raw`
19
+ `;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}`)}},A=String.raw`
20
20
  _____
21
21
  | __ \
22
22
  | |__) |___ ___ ___ _ __ __ _
23
23
  | _ // _ \/ __|/ _ \| '__/ _, |
24
24
  | | \ \ __/\__ \ (_) | | | (_| |
25
25
  |_| \_\___||___/\___/|_| \__,_|
26
- `;const A=new E;await f.init(await A.loadConfig(),{logo:k,name:`Resora CLI`,baseCommands:[O,D,...A.getConfig().extraCommands||[]],exceptionHandler(e){throw e}});export{};
26
+ `;const j=new D;await p.init(await j.loadConfig(),{logo:A,name:`Resora CLI`,baseCommands:[k,O,...j.getConfig().extraCommands||[]],exceptionHandler(e){throw e}});export{};
package/dist/index.cjs CHANGED
@@ -29,6 +29,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
29
  let fs = require("fs");
30
30
  let path = require("path");
31
31
  path = __toESM(path);
32
+ let node_async_hooks = require("node:async_hooks");
32
33
  let module$1 = require("module");
33
34
  let url = require("url");
34
35
  let _h3ravel_musket = require("@h3ravel/musket");
@@ -282,6 +283,8 @@ const getGlobalCursorMeta = () => {
282
283
  return globalCursorMeta;
283
284
  };
284
285
  let globalRequestUrl;
286
+ let globalCtx;
287
+ const requestContextStore = new node_async_hooks.AsyncLocalStorage();
285
288
  /**
286
289
  * Sets the current request URL, used as a fallback for pagination link generation
287
290
  * when no explicit path is provided in the pagination data.
@@ -297,7 +300,16 @@ const setRequestUrl = (url) => {
297
300
  * @returns The current request URL, or undefined if not set.
298
301
  */
299
302
  const getRequestUrl = () => {
300
- return globalRequestUrl;
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;
301
313
  };
302
314
  /**
303
315
  * Extracts the request URL pathname (with query string) from an HTTP context.
@@ -359,6 +371,7 @@ const extractResponseFromCtx = (ctx) => {
359
371
  * @param ctx An HTTP context `{ req, res }`, Express Request, H3 HTTPEvent, or bare request.
360
372
  */
361
373
  const setCtx = (ctx) => {
374
+ globalCtx = ctx;
362
375
  setRequestUrl(extractRequestUrl(ctx));
363
376
  };
364
377
 
@@ -1188,6 +1201,51 @@ var logo_default = String.raw`
1188
1201
  |_| \_\___||___/\___/|_| \__,_|
1189
1202
  `;
1190
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
+
1191
1249
  //#endregion
1192
1250
  //#region src/ServerResponse.ts
1193
1251
  /**
@@ -1293,14 +1351,38 @@ var ServerResponse = class {
1293
1351
  */
1294
1352
  send(body) {
1295
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 };
1296
1364
  if ("send" in this.response && typeof this.response.send === "function") {
1297
1365
  if ("statusCode" in this.response) this.response.statusCode = this._status;
1298
1366
  const sentResponse = this.response.send(this.body);
1299
1367
  if (sentResponse && "status" in sentResponse && typeof sentResponse.status === "function") sentResponse.status(this._status);
1300
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
+ });
1301
1376
  return this.body;
1302
1377
  }
1303
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
+ });
1304
1386
  return this.body;
1305
1387
  }
1306
1388
  /**
@@ -1399,6 +1481,63 @@ var BaseSerializer = class {
1399
1481
  return resolveMergeWhen(condition, value);
1400
1482
  }
1401
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
+ /**
1402
1541
  * Add additional metadata to the response. If called without arguments.
1403
1542
  *
1404
1543
  * @param meta
@@ -1446,6 +1585,11 @@ var BaseSerializer = class {
1446
1585
  this.called.withResponse = true;
1447
1586
  input.callWithResponse(response, input.rawResponse);
1448
1587
  if (typeof response?.setBody === "function") response.setBody(input.body());
1588
+ this.applyResponsePlugins({
1589
+ body: input.body(),
1590
+ rawResponse: input.rawResponse,
1591
+ response
1592
+ });
1449
1593
  return response;
1450
1594
  }
1451
1595
  /**
@@ -1470,9 +1614,14 @@ var BaseSerializer = class {
1470
1614
  }
1471
1615
  const resolvedBody = input.body();
1472
1616
  if (typeof response?.setBody === "function") response.setBody(resolvedBody);
1473
- const resolved = Promise.resolve(resolvedBody).then(input.onfulfilled, input.onrejected);
1474
- if (typeof response?.send === "function") response.send(resolvedBody);
1475
- else if (typeof input.rawResponse !== "undefined" && input.sendRawResponse) input.sendRawResponse(input.rawResponse, resolvedBody);
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);
1476
1625
  return resolved;
1477
1626
  }
1478
1627
  /**
@@ -1656,6 +1805,9 @@ var GenericResource = class GenericResource extends BaseSerializer {
1656
1805
  getResourceForMeta() {
1657
1806
  return this.resource;
1658
1807
  }
1808
+ getSerializerType() {
1809
+ return "generic";
1810
+ }
1659
1811
  getPayloadKey() {
1660
1812
  const { wrap, rootKey, factory } = this.resolveResponseStructure();
1661
1813
  return factory || !wrap ? void 0 : rootKey;
@@ -1699,6 +1851,7 @@ var GenericResource = class GenericResource extends BaseSerializer {
1699
1851
  ...paginationExtras,
1700
1852
  ...customMeta || {}
1701
1853
  }, rootKey);
1854
+ this.body = this.applySerializePlugins(this.body);
1702
1855
  }
1703
1856
  return this;
1704
1857
  }
@@ -1755,7 +1908,7 @@ var GenericResource = class GenericResource extends BaseSerializer {
1755
1908
  * @returns
1756
1909
  */
1757
1910
  response(res) {
1758
- const rawResponse = res ?? this.res ?? GenericResource.ctx?.res ?? GenericResource.ctx;
1911
+ const rawResponse = this.resolveRawResponse(res ?? this.res);
1759
1912
  return this.runResponse({
1760
1913
  ensureJson: () => this.json(),
1761
1914
  rawResponse,
@@ -1792,7 +1945,7 @@ var GenericResource = class GenericResource extends BaseSerializer {
1792
1945
  return this.runThen({
1793
1946
  ensureJson: () => this.json(),
1794
1947
  body: () => this.body,
1795
- rawResponse: this.res ?? GenericResource.ctx?.res ?? GenericResource.ctx,
1948
+ rawResponse: this.resolveRawResponse(this.res),
1796
1949
  createServerResponse: (raw, body) => {
1797
1950
  const response = new ServerResponse(raw, body);
1798
1951
  this.withResponseContext = {
@@ -1805,7 +1958,7 @@ var GenericResource = class GenericResource extends BaseSerializer {
1805
1958
  this.withResponse(response, raw);
1806
1959
  },
1807
1960
  sendRawResponse: (raw, body) => {
1808
- raw.send(body);
1961
+ this.sendRawResponseBody(raw, body);
1809
1962
  },
1810
1963
  onfulfilled,
1811
1964
  onrejected
@@ -1821,7 +1974,7 @@ var GenericResource = class GenericResource extends BaseSerializer {
1821
1974
  return this.runThen({
1822
1975
  ensureJson: () => this.json(),
1823
1976
  body: () => this.body,
1824
- rawResponse: this.res ?? GenericResource.ctx?.res ?? GenericResource.ctx,
1977
+ rawResponse: this.resolveRawResponse(this.res),
1825
1978
  createServerResponse: (raw, body) => {
1826
1979
  const response = new ServerResponse(raw, body);
1827
1980
  this.withResponseContext = {
@@ -1834,7 +1987,7 @@ var GenericResource = class GenericResource extends BaseSerializer {
1834
1987
  this.withResponse(response, raw);
1835
1988
  },
1836
1989
  sendRawResponse: (raw, body) => {
1837
- raw.send(body);
1990
+ this.sendRawResponseBody(raw, body);
1838
1991
  },
1839
1992
  onrejected
1840
1993
  });
@@ -1849,7 +2002,7 @@ var GenericResource = class GenericResource extends BaseSerializer {
1849
2002
  return this.runThen({
1850
2003
  ensureJson: () => this.json(),
1851
2004
  body: () => this.body,
1852
- rawResponse: this.res ?? GenericResource.ctx?.res ?? GenericResource.ctx,
2005
+ rawResponse: this.resolveRawResponse(this.res),
1853
2006
  createServerResponse: (raw, body) => {
1854
2007
  const response = new ServerResponse(raw, body);
1855
2008
  this.withResponseContext = {
@@ -1862,7 +2015,7 @@ var GenericResource = class GenericResource extends BaseSerializer {
1862
2015
  this.withResponse(response, raw);
1863
2016
  },
1864
2017
  sendRawResponse: (raw, body) => {
1865
- raw.send(body);
2018
+ this.sendRawResponseBody(raw, body);
1866
2019
  },
1867
2020
  onfulfilled: onfinally,
1868
2021
  onrejected: onfinally
@@ -1971,6 +2124,9 @@ var ResourceCollection = class ResourceCollection extends BaseSerializer {
1971
2124
  getResourceForMeta() {
1972
2125
  return this.resource;
1973
2126
  }
2127
+ getSerializerType() {
2128
+ return "collection";
2129
+ }
1974
2130
  /**
1975
2131
  * Get the appropriate key for the response payload based on the current response
1976
2132
  * structure configuration.
@@ -2020,6 +2176,7 @@ var ResourceCollection = class ResourceCollection extends BaseSerializer {
2020
2176
  ...paginationExtras,
2021
2177
  ...customMeta || {}
2022
2178
  }, rootKey);
2179
+ this.body = this.applySerializePlugins(this.body);
2023
2180
  }
2024
2181
  return this;
2025
2182
  }
@@ -2070,7 +2227,7 @@ var ResourceCollection = class ResourceCollection extends BaseSerializer {
2070
2227
  * @returns
2071
2228
  */
2072
2229
  response(res) {
2073
- const rawResponse = res ?? this.res ?? ResourceCollection.ctx?.res ?? ResourceCollection.ctx;
2230
+ const rawResponse = this.resolveRawResponse(res ?? this.res);
2074
2231
  return this.runResponse({
2075
2232
  ensureJson: () => this.json(),
2076
2233
  rawResponse,
@@ -2103,15 +2260,15 @@ var ResourceCollection = class ResourceCollection extends BaseSerializer {
2103
2260
  /**
2104
2261
  * Promise-like then method to allow chaining with async/await or .then() syntax
2105
2262
  *
2106
- * @param onfulfilled Callback to handle the fulfilled state of the promise, receiving the response body
2107
- * @param onrejected Callback to handle the rejected state of the promise, receiving the error reason
2263
+ * @param onfulfilled Callback to handle the fulfilled state of the promise
2264
+ * @param onrejected Callback to handle the rejected state of the promise
2108
2265
  * @returns A promise that resolves to the result of the onfulfilled or onrejected callback
2109
2266
  */
2110
2267
  then(onfulfilled, onrejected) {
2111
2268
  return this.runThen({
2112
2269
  ensureJson: () => this.json(),
2113
2270
  body: () => this.body,
2114
- rawResponse: this.res ?? ResourceCollection.ctx?.res ?? ResourceCollection.ctx,
2271
+ rawResponse: this.resolveRawResponse(this.res),
2115
2272
  createServerResponse: (raw, body) => {
2116
2273
  const response = new ServerResponse(raw, body);
2117
2274
  this.withResponseContext = {
@@ -2124,7 +2281,7 @@ var ResourceCollection = class ResourceCollection extends BaseSerializer {
2124
2281
  this.withResponse(response, raw);
2125
2282
  },
2126
2283
  sendRawResponse: (raw, body) => {
2127
- raw.send(body);
2284
+ this.sendRawResponseBody(raw, body);
2128
2285
  },
2129
2286
  onfulfilled,
2130
2287
  onrejected
@@ -2140,7 +2297,7 @@ var ResourceCollection = class ResourceCollection extends BaseSerializer {
2140
2297
  return this.runThen({
2141
2298
  ensureJson: () => this.json(),
2142
2299
  body: () => this.body,
2143
- rawResponse: this.res ?? ResourceCollection.ctx?.res ?? ResourceCollection.ctx,
2300
+ rawResponse: this.resolveRawResponse(this.res),
2144
2301
  createServerResponse: (raw, body) => {
2145
2302
  const response = new ServerResponse(raw, body);
2146
2303
  this.withResponseContext = {
@@ -2153,7 +2310,7 @@ var ResourceCollection = class ResourceCollection extends BaseSerializer {
2153
2310
  this.withResponse(response, raw);
2154
2311
  },
2155
2312
  sendRawResponse: (raw, body) => {
2156
- raw.send(body);
2313
+ this.sendRawResponseBody(raw, body);
2157
2314
  },
2158
2315
  onrejected
2159
2316
  });
@@ -2168,7 +2325,7 @@ var ResourceCollection = class ResourceCollection extends BaseSerializer {
2168
2325
  return this.runThen({
2169
2326
  ensureJson: () => this.json(),
2170
2327
  body: () => this.body,
2171
- rawResponse: this.res ?? ResourceCollection.ctx?.res ?? ResourceCollection.ctx,
2328
+ rawResponse: this.resolveRawResponse(this.res),
2172
2329
  createServerResponse: (raw, body) => {
2173
2330
  const response = new ServerResponse(raw, body);
2174
2331
  this.withResponseContext = {
@@ -2181,7 +2338,7 @@ var ResourceCollection = class ResourceCollection extends BaseSerializer {
2181
2338
  this.withResponse(response, raw);
2182
2339
  },
2183
2340
  sendRawResponse: (raw, body) => {
2184
- raw.send(body);
2341
+ this.sendRawResponseBody(raw, body);
2185
2342
  },
2186
2343
  onfulfilled: onfinally,
2187
2344
  onrejected: onfinally
@@ -2284,6 +2441,9 @@ var Resource = class Resource extends BaseSerializer {
2284
2441
  getResourceForMeta() {
2285
2442
  return this.resource;
2286
2443
  }
2444
+ getSerializerType() {
2445
+ return "resource";
2446
+ }
2287
2447
  getPayloadKey() {
2288
2448
  const { wrap, rootKey, factory } = this.resolveResponseStructure();
2289
2449
  return factory || !wrap ? void 0 : rootKey;
@@ -2317,6 +2477,7 @@ var Resource = class Resource extends BaseSerializer {
2317
2477
  }
2318
2478
  });
2319
2479
  this.body = appendRootProperties(this.body, customMeta, rootKey);
2480
+ this.body = this.applySerializePlugins(this.body);
2320
2481
  }
2321
2482
  return this;
2322
2483
  }
@@ -2370,7 +2531,7 @@ var Resource = class Resource extends BaseSerializer {
2370
2531
  * @returns
2371
2532
  */
2372
2533
  response(res) {
2373
- const rawResponse = res ?? this.res ?? Resource.ctx?.res ?? Resource.ctx;
2534
+ const rawResponse = this.resolveRawResponse(res ?? this.res);
2374
2535
  return this.runResponse({
2375
2536
  ensureJson: () => this.json(),
2376
2537
  rawResponse,
@@ -2407,7 +2568,7 @@ var Resource = class Resource extends BaseSerializer {
2407
2568
  return this.runThen({
2408
2569
  ensureJson: () => this.json(),
2409
2570
  body: () => this.body,
2410
- rawResponse: this.res ?? Resource.ctx?.res ?? Resource.ctx,
2571
+ rawResponse: this.resolveRawResponse(this.res),
2411
2572
  createServerResponse: (raw, body) => {
2412
2573
  const response = new ServerResponse(raw, body);
2413
2574
  this.withResponseContext = {
@@ -2420,7 +2581,7 @@ var Resource = class Resource extends BaseSerializer {
2420
2581
  this.withResponse(response, raw);
2421
2582
  },
2422
2583
  sendRawResponse: (raw, body) => {
2423
- raw.send(body);
2584
+ this.sendRawResponseBody(raw, body);
2424
2585
  },
2425
2586
  onfulfilled,
2426
2587
  onrejected
@@ -2436,7 +2597,7 @@ var Resource = class Resource extends BaseSerializer {
2436
2597
  return this.runThen({
2437
2598
  ensureJson: () => this.json(),
2438
2599
  body: () => this.body,
2439
- rawResponse: this.res ?? Resource.ctx?.res ?? Resource.ctx,
2600
+ rawResponse: this.resolveRawResponse(this.res),
2440
2601
  createServerResponse: (raw, body) => {
2441
2602
  const response = new ServerResponse(raw, body);
2442
2603
  this.withResponseContext = {
@@ -2449,7 +2610,7 @@ var Resource = class Resource extends BaseSerializer {
2449
2610
  this.withResponse(response, raw);
2450
2611
  },
2451
2612
  sendRawResponse: (raw, body) => {
2452
- raw.send(body);
2613
+ this.sendRawResponseBody(raw, body);
2453
2614
  },
2454
2615
  onrejected
2455
2616
  });
@@ -2464,7 +2625,7 @@ var Resource = class Resource extends BaseSerializer {
2464
2625
  return this.runThen({
2465
2626
  ensureJson: () => this.json(),
2466
2627
  body: () => this.body,
2467
- rawResponse: this.res ?? Resource.ctx?.res ?? Resource.ctx,
2628
+ rawResponse: this.resolveRawResponse(this.res),
2468
2629
  createServerResponse: (raw, body) => {
2469
2630
  const response = new ServerResponse(raw, body);
2470
2631
  this.withResponseContext = {
@@ -2477,7 +2638,7 @@ var Resource = class Resource extends BaseSerializer {
2477
2638
  this.withResponse(response, raw);
2478
2639
  },
2479
2640
  sendRawResponse: (raw, body) => {
2480
- raw.send(body);
2641
+ this.sendRawResponseBody(raw, body);
2481
2642
  },
2482
2643
  onfulfilled: onfinally,
2483
2644
  onrejected: onfinally
@@ -2501,9 +2662,11 @@ exports.buildPaginationExtras = buildPaginationExtras;
2501
2662
  exports.buildResponseEnvelope = buildResponseEnvelope;
2502
2663
  exports.createArkormCurrentPageResolver = createArkormCurrentPageResolver;
2503
2664
  exports.defineConfig = defineConfig;
2665
+ exports.definePlugin = definePlugin;
2504
2666
  exports.extractRequestUrl = extractRequestUrl;
2505
2667
  exports.extractResponseFromCtx = extractResponseFromCtx;
2506
2668
  exports.getCaseTransformer = getCaseTransformer;
2669
+ exports.getCtx = getCtx;
2507
2670
  exports.getDefaultConfig = getDefaultConfig;
2508
2671
  exports.getGlobalBaseUrl = getGlobalBaseUrl;
2509
2672
  exports.getGlobalCase = getGlobalCase;
@@ -2517,7 +2680,9 @@ exports.getGlobalResponseRootKey = getGlobalResponseRootKey;
2517
2680
  exports.getGlobalResponseStructure = getGlobalResponseStructure;
2518
2681
  exports.getGlobalResponseWrap = getGlobalResponseWrap;
2519
2682
  exports.getPaginationExtraKeys = getPaginationExtraKeys;
2683
+ exports.getRegisteredPlugins = getRegisteredPlugins;
2520
2684
  exports.getRequestUrl = getRequestUrl;
2685
+ exports.getUtility = getUtility;
2521
2686
  exports.hasPaginationLink = hasPaginationLink;
2522
2687
  exports.isArkormLikeCollection = isArkormLikeCollection;
2523
2688
  exports.isArkormLikeModel = isArkormLikeModel;
@@ -2525,12 +2690,17 @@ exports.isPlainObject = isPlainObject;
2525
2690
  exports.loadRuntimeConfig = loadRuntimeConfig;
2526
2691
  exports.mergeMetadata = mergeMetadata;
2527
2692
  exports.normalizeSerializableData = normalizeSerializableData;
2693
+ exports.registerPlugin = registerPlugin;
2694
+ exports.registerUtility = registerUtility;
2695
+ exports.resetPluginsForTests = resetPluginsForTests;
2528
2696
  exports.resetRuntimeConfigForTests = resetRuntimeConfigForTests;
2529
2697
  exports.resolveCurrentPage = resolveCurrentPage;
2530
2698
  exports.resolveMergeWhen = resolveMergeWhen;
2531
2699
  exports.resolveWhen = resolveWhen;
2532
2700
  exports.resolveWhenNotNull = resolveWhenNotNull;
2533
2701
  exports.resolveWithHookMetadata = resolveWithHookMetadata;
2702
+ exports.runPluginHook = runPluginHook;
2703
+ exports.runWithCtx = runWithCtx;
2534
2704
  exports.sanitizeConditionalAttributes = sanitizeConditionalAttributes;
2535
2705
  exports.setCtx = setCtx;
2536
2706
  exports.setGlobalBaseUrl = setGlobalBaseUrl;