resora 1.3.2 → 1.3.3

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
@@ -491,6 +491,30 @@ const normalizeSerializableData = (value) => {
491
491
  }, {});
492
492
  return value;
493
493
  };
494
+ const isPromiseLike = (value) => {
495
+ return !!value && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
496
+ };
497
+ /**
498
+ * Async variant of normalizeSerializableData. It resolves promise-like values at
499
+ * every nesting level before converting Arkorm-like models and collections.
500
+ *
501
+ * @param value The value to normalize
502
+ * @returns The normalized value, ready for serialization
503
+ */
504
+ const normalizeSerializableDataAsync = async (value) => {
505
+ const resolvedValue = isPromiseLike(value) ? await value : value;
506
+ if (Array.isArray(resolvedValue)) return Promise.all(resolvedValue.map((item) => normalizeSerializableDataAsync(item)));
507
+ if (isResoraCollectionLike(resolvedValue)) return normalizeSerializableDataAsync(resolvedValue.toObject());
508
+ if (isArkormLikeModel(resolvedValue)) return normalizeSerializableDataAsync(resolvedValue.toObject());
509
+ if (isArkormLikeCollection(resolvedValue)) return normalizeSerializableDataAsync(resolvedValue.all());
510
+ if (isPlainObject(resolvedValue)) return (await Promise.all(Object.entries(resolvedValue).map(async ([key, nestedValue]) => {
511
+ return [key, await normalizeSerializableDataAsync(nestedValue)];
512
+ }))).reduce((accumulator, [key, nestedValue]) => {
513
+ accumulator[key] = nestedValue;
514
+ return accumulator;
515
+ }, {});
516
+ return resolvedValue;
517
+ };
494
518
 
495
519
  //#endregion
496
520
  //#region src/utilities/pagination.ts
@@ -1901,6 +1925,48 @@ var GenericResource = class GenericResource extends BaseSerializer {
1901
1925
  }
1902
1926
  serialize(data);
1903
1927
  }
1928
+ async serializeGenericResourceAsync(resource, ctx) {
1929
+ let data = await normalizeSerializableDataAsync(resource);
1930
+ const serialize = (resolvedData) => {
1931
+ data = resolvedData;
1932
+ if (!Array.isArray(data) && data && typeof data.data !== "undefined") data = data.data;
1933
+ data = sanitizeConditionalAttributes(data);
1934
+ const paginationExtras = buildPaginationExtras(this.resource);
1935
+ const { metaKey } = getPaginationExtraKeys();
1936
+ const configuredMeta = metaKey ? paginationExtras[metaKey] : void 0;
1937
+ if (metaKey) delete paginationExtras[metaKey];
1938
+ const caseStyle = this.resolveSerializerCaseStyle(this.constructor, this.resolveCollectsConfig());
1939
+ if (caseStyle) {
1940
+ const transformer = getCaseTransformer(caseStyle);
1941
+ data = transformKeys(data, transformer);
1942
+ }
1943
+ const customMeta = this.resolveMergedMeta(GenericResource.prototype.with);
1944
+ const { wrap, rootKey, factory } = this.resolveResponseStructure();
1945
+ this.body = buildResponseEnvelope({
1946
+ payload: data,
1947
+ meta: configuredMeta,
1948
+ metaKey,
1949
+ wrap,
1950
+ rootKey,
1951
+ factory,
1952
+ context: {
1953
+ type: "generic",
1954
+ resource: this.resource
1955
+ }
1956
+ });
1957
+ this.body = appendRootProperties(this.body, {
1958
+ ...paginationExtras,
1959
+ ...customMeta || {}
1960
+ }, rootKey);
1961
+ this.body = this.applySerializePlugins(this.body);
1962
+ };
1963
+ if (Array.isArray(data) && this.collects) {
1964
+ const collected = data.map((item) => new this.collects(item).data(ctx));
1965
+ data = await Promise.all(collected);
1966
+ data = await normalizeSerializableDataAsync(data);
1967
+ }
1968
+ serialize(data);
1969
+ }
1904
1970
  /**
1905
1971
  * Convert resource to JSON response format
1906
1972
  *
@@ -1912,8 +1978,7 @@ var GenericResource = class GenericResource extends BaseSerializer {
1912
1978
  const ctx = this.resolveSerializationContext();
1913
1979
  const resource = this.data(ctx);
1914
1980
  if (this.isPromiseLike(resource)) this.serializationPromise = Promise.resolve(resource).then((resolved) => {
1915
- const result = this.serializeGenericResource(resolved, ctx);
1916
- if (this.isPromiseLike(result)) return result;
1981
+ return this.serializeGenericResourceAsync(resolved, ctx);
1917
1982
  });
1918
1983
  else {
1919
1984
  const result = this.serializeGenericResource(resource, ctx);
@@ -2244,6 +2309,38 @@ var ResourceCollection = class ResourceCollection extends BaseSerializer {
2244
2309
  }, rootKey);
2245
2310
  this.body = this.applySerializePlugins(this.body);
2246
2311
  }
2312
+ async serializeCollectionDataAsync(items) {
2313
+ let data = await normalizeSerializableDataAsync(items);
2314
+ data = sanitizeConditionalAttributes(data);
2315
+ const paginationExtras = !Array.isArray(this.resource) ? buildPaginationExtras(this.resource) : {};
2316
+ const { metaKey } = getPaginationExtraKeys();
2317
+ const configuredMeta = metaKey ? paginationExtras[metaKey] : void 0;
2318
+ if (metaKey) delete paginationExtras[metaKey];
2319
+ const caseStyle = this.resolveSerializerCaseStyle(this.constructor, this.resolveCollectsConfig());
2320
+ if (caseStyle) {
2321
+ const transformer = getCaseTransformer(caseStyle);
2322
+ data = transformKeys(data, transformer);
2323
+ }
2324
+ const customMeta = this.resolveMergedMeta(ResourceCollection.prototype.with);
2325
+ const { wrap, rootKey, factory } = this.resolveResponseStructure();
2326
+ this.body = buildResponseEnvelope({
2327
+ payload: data,
2328
+ meta: configuredMeta,
2329
+ metaKey,
2330
+ wrap,
2331
+ rootKey,
2332
+ factory,
2333
+ context: {
2334
+ type: "collection",
2335
+ resource: this.resource
2336
+ }
2337
+ });
2338
+ this.body = appendRootProperties(this.body, {
2339
+ ...paginationExtras,
2340
+ ...customMeta || {}
2341
+ }, rootKey);
2342
+ this.body = this.applySerializePlugins(this.body);
2343
+ }
2247
2344
  resolveCollectionDataForSerialization(items, ctx) {
2248
2345
  if (this.collects && this.data === ResourceCollection.prototype.data) {
2249
2346
  const collected = items.map((item) => new this.collects(item).data(ctx));
@@ -2265,11 +2362,16 @@ var ResourceCollection = class ResourceCollection extends BaseSerializer {
2265
2362
  const serialize = (items) => {
2266
2363
  const resolvedData = this.resolveCollectionDataForSerialization(items, ctx);
2267
2364
  if (this.isPromiseLike(resolvedData)) return resolvedData.then((resolved) => {
2268
- this.serializeCollectionData(resolved);
2365
+ return this.serializeCollectionDataAsync(resolved);
2269
2366
  });
2270
2367
  this.serializeCollectionData(resolvedData);
2271
2368
  };
2272
- if (this.isPromiseLike(data)) this.serializationPromise = Promise.resolve(data).then(serialize);
2369
+ if (this.isPromiseLike(data)) this.serializationPromise = Promise.resolve(data).then((items) => {
2370
+ const resolvedData = this.resolveCollectionDataForSerialization(items, ctx);
2371
+ return Promise.resolve(resolvedData).then((resolved) => {
2372
+ return this.serializeCollectionDataAsync(resolved);
2373
+ });
2374
+ });
2273
2375
  else {
2274
2376
  const result = serialize(data);
2275
2377
  if (this.isPromiseLike(result)) this.serializationPromise = Promise.resolve(result);
@@ -2568,6 +2670,30 @@ var Resource = class Resource extends BaseSerializer {
2568
2670
  this.body = appendRootProperties(this.body, customMeta, rootKey);
2569
2671
  this.body = this.applySerializePlugins(this.body);
2570
2672
  }
2673
+ async serializeResourceAsync(resource) {
2674
+ let data = await normalizeSerializableDataAsync(resource);
2675
+ if (!Array.isArray(data) && data && typeof data.data !== "undefined") data = data.data;
2676
+ data = sanitizeConditionalAttributes(data);
2677
+ const caseStyle = this.resolveSerializerCaseStyle(this.constructor);
2678
+ if (caseStyle) {
2679
+ const transformer = getCaseTransformer(caseStyle);
2680
+ data = transformKeys(data, transformer);
2681
+ }
2682
+ const customMeta = this.resolveMergedMeta(Resource.prototype.with);
2683
+ const { wrap, rootKey, factory } = this.resolveResponseStructure();
2684
+ this.body = buildResponseEnvelope({
2685
+ payload: data,
2686
+ wrap,
2687
+ rootKey,
2688
+ factory,
2689
+ context: {
2690
+ type: "resource",
2691
+ resource: this.resource
2692
+ }
2693
+ });
2694
+ this.body = appendRootProperties(this.body, customMeta, rootKey);
2695
+ this.body = this.applySerializePlugins(this.body);
2696
+ }
2571
2697
  /**
2572
2698
  * Convert resource to JSON response format
2573
2699
  *
@@ -2579,7 +2705,7 @@ var Resource = class Resource extends BaseSerializer {
2579
2705
  const ctx = this.resolveSerializationContext();
2580
2706
  const resource = this.data(ctx);
2581
2707
  if (this.isPromiseLike(resource)) this.serializationPromise = Promise.resolve(resource).then((resolved) => {
2582
- this.serializeResource(resolved);
2708
+ return this.serializeResourceAsync(resolved);
2583
2709
  });
2584
2710
  else this.serializeResource(resource);
2585
2711
  }
@@ -2795,6 +2921,7 @@ exports.isResoraCollectionLike = isResoraCollectionLike;
2795
2921
  exports.loadRuntimeConfig = loadRuntimeConfig;
2796
2922
  exports.mergeMetadata = mergeMetadata;
2797
2923
  exports.normalizeSerializableData = normalizeSerializableData;
2924
+ exports.normalizeSerializableDataAsync = normalizeSerializableDataAsync;
2798
2925
  exports.registerPlugin = registerPlugin;
2799
2926
  exports.registerUtility = registerUtility;
2800
2927
  exports.resetPluginsForTests = resetPluginsForTests;
package/dist/index.d.cts CHANGED
@@ -790,6 +790,7 @@ declare class ResourceCollection<R extends ResourceData[] | Collectible | Collec
790
790
  */
791
791
  private getPayloadKey;
792
792
  private serializeCollectionData;
793
+ private serializeCollectionDataAsync;
793
794
  private resolveCollectionDataForSerialization;
794
795
  /**
795
796
  * Convert resource to JSON response format
@@ -909,6 +910,7 @@ declare class Resource<R extends ResourceData | NonCollectible = ResourceData> e
909
910
  protected getSerializerType(): "resource";
910
911
  private getPayloadKey;
911
912
  private serializeResource;
913
+ private serializeResourceAsync;
912
914
  /**
913
915
  * Convert resource to JSON response format
914
916
  *
@@ -1031,6 +1033,7 @@ declare class GenericResource<R extends NonCollectible | Collectible | Collectio
1031
1033
  protected getSerializerType(): "generic";
1032
1034
  private getPayloadKey;
1033
1035
  private serializeGenericResource;
1036
+ private serializeGenericResourceAsync;
1034
1037
  /**
1035
1038
  * Convert resource to JSON response format
1036
1039
  *
@@ -1222,7 +1225,7 @@ declare const defineConfig: (config: ResoraConfig) => Config;
1222
1225
  //#endregion
1223
1226
  //#region src/utilities/arkorm.d.ts
1224
1227
  type ArkormLikeModel = {
1225
- toObject: () => Record<string, any>;
1228
+ toObject: () => Record<string, any> | PromiseLike<Record<string, any>>;
1226
1229
  getRawAttributes?: () => Record<string, any>;
1227
1230
  getAttribute?: (key: string) => unknown;
1228
1231
  setAttribute?: (key: string, value: unknown) => unknown;
@@ -1267,6 +1270,14 @@ declare const isResoraCollectionLike: (value: unknown) => value is ResoraCollect
1267
1270
  * @returns The normalized value, ready for serialization
1268
1271
  */
1269
1272
  declare const normalizeSerializableData: (value: unknown) => unknown;
1273
+ /**
1274
+ * Async variant of normalizeSerializableData. It resolves promise-like values at
1275
+ * every nesting level before converting Arkorm-like models and collections.
1276
+ *
1277
+ * @param value The value to normalize
1278
+ * @returns The normalized value, ready for serialization
1279
+ */
1280
+ declare const normalizeSerializableDataAsync: (value: unknown) => Promise<unknown>;
1270
1281
  //#endregion
1271
1282
  //#region src/utilities/metadata.d.ts
1272
1283
  /**
@@ -1600,4 +1611,4 @@ declare const extractResponseFromCtx: (ctx: unknown) => any | undefined;
1600
1611
  */
1601
1612
  declare const setCtx: (ctx: unknown) => void;
1602
1613
  //#endregion
1603
- export { ApiResource, CONDITIONAL_ATTRIBUTE_MISSING, CaseStyle, CliApp, Collectible, CollectionBody, CollectionLike, Config, Cursor, GenericBody, GenericResource, InitCommand, MakeResource, MetaData, NonCollectible, PaginatedMetaData, Pagination, PaginatorLike, ResoraConfig, ResoraPlugin, ResoraPluginApi, ResoraPluginUtility, Resource, ResourceBody, ResourceCollection, ResourceData, ResourceDef, ResourceLevelConfig, ResponseData, ResponseDataCollection, ResponseFactory, ResponseFactoryContext, ResponseKind, ResponsePluginEvent, ResponseStructureConfig, SendPluginEvent, SerializePluginEvent, ServerResponse, appendRootProperties, applyRuntimeConfig, buildPaginationExtras, buildResponseEnvelope, createArkormCurrentPageResolver, defineConfig, definePlugin, extractRequestUrl, extractResponseFromCtx, getCaseTransformer, getCtx, getDefaultConfig, getGlobalBaseUrl, getGlobalCase, getGlobalCursorMeta, getGlobalPageName, getGlobalPaginatedExtras, getGlobalPaginatedLinks, getGlobalPaginatedMeta, getGlobalResponseFactory, getGlobalResponseRootKey, getGlobalResponseStructure, getGlobalResponseWrap, getPaginationExtraKeys, getRegisteredPlugins, getRequestUrl, getUtility, hasPaginationLink, isArkormLikeCollection, isArkormLikeModel, isPlainObject, isResoraCollectionLike, loadRuntimeConfig, mergeMetadata, normalizeSerializableData, registerPlugin, registerUtility, resetPluginsForTests, resetRuntimeConfigForTests, resolveCurrentPage, resolveMergeWhen, resolveWhen, resolveWhenNotNull, resolveWithHookMetadata, runPluginHook, runWithCtx, sanitizeConditionalAttributes, setCtx, setGlobalBaseUrl, setGlobalCase, setGlobalCursorMeta, setGlobalPageName, setGlobalPaginatedExtras, setGlobalPaginatedLinks, setGlobalPaginatedMeta, setGlobalResponseFactory, setGlobalResponseRootKey, setGlobalResponseStructure, setGlobalResponseWrap, setRequestUrl, splitWords, toCamelCase, toKebabCase, toPascalCase, toSnakeCase, transformKeys };
1614
+ export { ApiResource, CONDITIONAL_ATTRIBUTE_MISSING, CaseStyle, CliApp, Collectible, CollectionBody, CollectionLike, Config, Cursor, GenericBody, GenericResource, InitCommand, MakeResource, MetaData, NonCollectible, PaginatedMetaData, Pagination, PaginatorLike, ResoraConfig, ResoraPlugin, ResoraPluginApi, ResoraPluginUtility, Resource, ResourceBody, ResourceCollection, ResourceData, ResourceDef, ResourceLevelConfig, ResponseData, ResponseDataCollection, ResponseFactory, ResponseFactoryContext, ResponseKind, ResponsePluginEvent, ResponseStructureConfig, SendPluginEvent, SerializePluginEvent, ServerResponse, appendRootProperties, applyRuntimeConfig, buildPaginationExtras, buildResponseEnvelope, createArkormCurrentPageResolver, defineConfig, definePlugin, extractRequestUrl, extractResponseFromCtx, getCaseTransformer, getCtx, getDefaultConfig, getGlobalBaseUrl, getGlobalCase, getGlobalCursorMeta, getGlobalPageName, getGlobalPaginatedExtras, getGlobalPaginatedLinks, getGlobalPaginatedMeta, getGlobalResponseFactory, getGlobalResponseRootKey, getGlobalResponseStructure, getGlobalResponseWrap, getPaginationExtraKeys, getRegisteredPlugins, getRequestUrl, getUtility, hasPaginationLink, isArkormLikeCollection, isArkormLikeModel, isPlainObject, isResoraCollectionLike, loadRuntimeConfig, mergeMetadata, normalizeSerializableData, normalizeSerializableDataAsync, registerPlugin, registerUtility, resetPluginsForTests, resetRuntimeConfigForTests, resolveCurrentPage, resolveMergeWhen, resolveWhen, resolveWhenNotNull, resolveWithHookMetadata, runPluginHook, runWithCtx, sanitizeConditionalAttributes, setCtx, setGlobalBaseUrl, setGlobalCase, setGlobalCursorMeta, setGlobalPageName, setGlobalPaginatedExtras, setGlobalPaginatedLinks, setGlobalPaginatedMeta, setGlobalResponseFactory, setGlobalResponseRootKey, setGlobalResponseStructure, setGlobalResponseWrap, setRequestUrl, splitWords, toCamelCase, toKebabCase, toPascalCase, toSnakeCase, transformKeys };
package/dist/index.d.mts CHANGED
@@ -790,6 +790,7 @@ declare class ResourceCollection<R extends ResourceData[] | Collectible | Collec
790
790
  */
791
791
  private getPayloadKey;
792
792
  private serializeCollectionData;
793
+ private serializeCollectionDataAsync;
793
794
  private resolveCollectionDataForSerialization;
794
795
  /**
795
796
  * Convert resource to JSON response format
@@ -909,6 +910,7 @@ declare class Resource<R extends ResourceData | NonCollectible = ResourceData> e
909
910
  protected getSerializerType(): "resource";
910
911
  private getPayloadKey;
911
912
  private serializeResource;
913
+ private serializeResourceAsync;
912
914
  /**
913
915
  * Convert resource to JSON response format
914
916
  *
@@ -1031,6 +1033,7 @@ declare class GenericResource<R extends NonCollectible | Collectible | Collectio
1031
1033
  protected getSerializerType(): "generic";
1032
1034
  private getPayloadKey;
1033
1035
  private serializeGenericResource;
1036
+ private serializeGenericResourceAsync;
1034
1037
  /**
1035
1038
  * Convert resource to JSON response format
1036
1039
  *
@@ -1222,7 +1225,7 @@ declare const defineConfig: (config: ResoraConfig) => Config;
1222
1225
  //#endregion
1223
1226
  //#region src/utilities/arkorm.d.ts
1224
1227
  type ArkormLikeModel = {
1225
- toObject: () => Record<string, any>;
1228
+ toObject: () => Record<string, any> | PromiseLike<Record<string, any>>;
1226
1229
  getRawAttributes?: () => Record<string, any>;
1227
1230
  getAttribute?: (key: string) => unknown;
1228
1231
  setAttribute?: (key: string, value: unknown) => unknown;
@@ -1267,6 +1270,14 @@ declare const isResoraCollectionLike: (value: unknown) => value is ResoraCollect
1267
1270
  * @returns The normalized value, ready for serialization
1268
1271
  */
1269
1272
  declare const normalizeSerializableData: (value: unknown) => unknown;
1273
+ /**
1274
+ * Async variant of normalizeSerializableData. It resolves promise-like values at
1275
+ * every nesting level before converting Arkorm-like models and collections.
1276
+ *
1277
+ * @param value The value to normalize
1278
+ * @returns The normalized value, ready for serialization
1279
+ */
1280
+ declare const normalizeSerializableDataAsync: (value: unknown) => Promise<unknown>;
1270
1281
  //#endregion
1271
1282
  //#region src/utilities/metadata.d.ts
1272
1283
  /**
@@ -1600,4 +1611,4 @@ declare const extractResponseFromCtx: (ctx: unknown) => any | undefined;
1600
1611
  */
1601
1612
  declare const setCtx: (ctx: unknown) => void;
1602
1613
  //#endregion
1603
- export { ApiResource, CONDITIONAL_ATTRIBUTE_MISSING, CaseStyle, CliApp, Collectible, CollectionBody, CollectionLike, Config, Cursor, GenericBody, GenericResource, InitCommand, MakeResource, MetaData, NonCollectible, PaginatedMetaData, Pagination, PaginatorLike, ResoraConfig, ResoraPlugin, ResoraPluginApi, ResoraPluginUtility, Resource, ResourceBody, ResourceCollection, ResourceData, ResourceDef, ResourceLevelConfig, ResponseData, ResponseDataCollection, ResponseFactory, ResponseFactoryContext, ResponseKind, ResponsePluginEvent, ResponseStructureConfig, SendPluginEvent, SerializePluginEvent, ServerResponse, appendRootProperties, applyRuntimeConfig, buildPaginationExtras, buildResponseEnvelope, createArkormCurrentPageResolver, defineConfig, definePlugin, extractRequestUrl, extractResponseFromCtx, getCaseTransformer, getCtx, getDefaultConfig, getGlobalBaseUrl, getGlobalCase, getGlobalCursorMeta, getGlobalPageName, getGlobalPaginatedExtras, getGlobalPaginatedLinks, getGlobalPaginatedMeta, getGlobalResponseFactory, getGlobalResponseRootKey, getGlobalResponseStructure, getGlobalResponseWrap, getPaginationExtraKeys, getRegisteredPlugins, getRequestUrl, getUtility, hasPaginationLink, isArkormLikeCollection, isArkormLikeModel, isPlainObject, isResoraCollectionLike, loadRuntimeConfig, mergeMetadata, normalizeSerializableData, registerPlugin, registerUtility, resetPluginsForTests, resetRuntimeConfigForTests, resolveCurrentPage, resolveMergeWhen, resolveWhen, resolveWhenNotNull, resolveWithHookMetadata, runPluginHook, runWithCtx, sanitizeConditionalAttributes, setCtx, setGlobalBaseUrl, setGlobalCase, setGlobalCursorMeta, setGlobalPageName, setGlobalPaginatedExtras, setGlobalPaginatedLinks, setGlobalPaginatedMeta, setGlobalResponseFactory, setGlobalResponseRootKey, setGlobalResponseStructure, setGlobalResponseWrap, setRequestUrl, splitWords, toCamelCase, toKebabCase, toPascalCase, toSnakeCase, transformKeys };
1614
+ export { ApiResource, CONDITIONAL_ATTRIBUTE_MISSING, CaseStyle, CliApp, Collectible, CollectionBody, CollectionLike, Config, Cursor, GenericBody, GenericResource, InitCommand, MakeResource, MetaData, NonCollectible, PaginatedMetaData, Pagination, PaginatorLike, ResoraConfig, ResoraPlugin, ResoraPluginApi, ResoraPluginUtility, Resource, ResourceBody, ResourceCollection, ResourceData, ResourceDef, ResourceLevelConfig, ResponseData, ResponseDataCollection, ResponseFactory, ResponseFactoryContext, ResponseKind, ResponsePluginEvent, ResponseStructureConfig, SendPluginEvent, SerializePluginEvent, ServerResponse, appendRootProperties, applyRuntimeConfig, buildPaginationExtras, buildResponseEnvelope, createArkormCurrentPageResolver, defineConfig, definePlugin, extractRequestUrl, extractResponseFromCtx, getCaseTransformer, getCtx, getDefaultConfig, getGlobalBaseUrl, getGlobalCase, getGlobalCursorMeta, getGlobalPageName, getGlobalPaginatedExtras, getGlobalPaginatedLinks, getGlobalPaginatedMeta, getGlobalResponseFactory, getGlobalResponseRootKey, getGlobalResponseStructure, getGlobalResponseWrap, getPaginationExtraKeys, getRegisteredPlugins, getRequestUrl, getUtility, hasPaginationLink, isArkormLikeCollection, isArkormLikeModel, isPlainObject, isResoraCollectionLike, loadRuntimeConfig, mergeMetadata, normalizeSerializableData, normalizeSerializableDataAsync, registerPlugin, registerUtility, resetPluginsForTests, resetRuntimeConfigForTests, resolveCurrentPage, resolveMergeWhen, resolveWhen, resolveWhenNotNull, resolveWithHookMetadata, runPluginHook, runWithCtx, sanitizeConditionalAttributes, setCtx, setGlobalBaseUrl, setGlobalCase, setGlobalCursorMeta, setGlobalPageName, setGlobalPaginatedExtras, setGlobalPaginatedLinks, setGlobalPaginatedMeta, setGlobalResponseFactory, setGlobalResponseRootKey, setGlobalResponseStructure, setGlobalResponseWrap, setRequestUrl, splitWords, toCamelCase, toKebabCase, toPascalCase, toSnakeCase, transformKeys };
package/dist/index.mjs CHANGED
@@ -462,6 +462,30 @@ const normalizeSerializableData = (value) => {
462
462
  }, {});
463
463
  return value;
464
464
  };
465
+ const isPromiseLike = (value) => {
466
+ return !!value && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
467
+ };
468
+ /**
469
+ * Async variant of normalizeSerializableData. It resolves promise-like values at
470
+ * every nesting level before converting Arkorm-like models and collections.
471
+ *
472
+ * @param value The value to normalize
473
+ * @returns The normalized value, ready for serialization
474
+ */
475
+ const normalizeSerializableDataAsync = async (value) => {
476
+ const resolvedValue = isPromiseLike(value) ? await value : value;
477
+ if (Array.isArray(resolvedValue)) return Promise.all(resolvedValue.map((item) => normalizeSerializableDataAsync(item)));
478
+ if (isResoraCollectionLike(resolvedValue)) return normalizeSerializableDataAsync(resolvedValue.toObject());
479
+ if (isArkormLikeModel(resolvedValue)) return normalizeSerializableDataAsync(resolvedValue.toObject());
480
+ if (isArkormLikeCollection(resolvedValue)) return normalizeSerializableDataAsync(resolvedValue.all());
481
+ if (isPlainObject(resolvedValue)) return (await Promise.all(Object.entries(resolvedValue).map(async ([key, nestedValue]) => {
482
+ return [key, await normalizeSerializableDataAsync(nestedValue)];
483
+ }))).reduce((accumulator, [key, nestedValue]) => {
484
+ accumulator[key] = nestedValue;
485
+ return accumulator;
486
+ }, {});
487
+ return resolvedValue;
488
+ };
465
489
 
466
490
  //#endregion
467
491
  //#region src/utilities/pagination.ts
@@ -1872,6 +1896,48 @@ var GenericResource = class GenericResource extends BaseSerializer {
1872
1896
  }
1873
1897
  serialize(data);
1874
1898
  }
1899
+ async serializeGenericResourceAsync(resource, ctx) {
1900
+ let data = await normalizeSerializableDataAsync(resource);
1901
+ const serialize = (resolvedData) => {
1902
+ data = resolvedData;
1903
+ if (!Array.isArray(data) && data && typeof data.data !== "undefined") data = data.data;
1904
+ data = sanitizeConditionalAttributes(data);
1905
+ const paginationExtras = buildPaginationExtras(this.resource);
1906
+ const { metaKey } = getPaginationExtraKeys();
1907
+ const configuredMeta = metaKey ? paginationExtras[metaKey] : void 0;
1908
+ if (metaKey) delete paginationExtras[metaKey];
1909
+ const caseStyle = this.resolveSerializerCaseStyle(this.constructor, this.resolveCollectsConfig());
1910
+ if (caseStyle) {
1911
+ const transformer = getCaseTransformer(caseStyle);
1912
+ data = transformKeys(data, transformer);
1913
+ }
1914
+ const customMeta = this.resolveMergedMeta(GenericResource.prototype.with);
1915
+ const { wrap, rootKey, factory } = this.resolveResponseStructure();
1916
+ this.body = buildResponseEnvelope({
1917
+ payload: data,
1918
+ meta: configuredMeta,
1919
+ metaKey,
1920
+ wrap,
1921
+ rootKey,
1922
+ factory,
1923
+ context: {
1924
+ type: "generic",
1925
+ resource: this.resource
1926
+ }
1927
+ });
1928
+ this.body = appendRootProperties(this.body, {
1929
+ ...paginationExtras,
1930
+ ...customMeta || {}
1931
+ }, rootKey);
1932
+ this.body = this.applySerializePlugins(this.body);
1933
+ };
1934
+ if (Array.isArray(data) && this.collects) {
1935
+ const collected = data.map((item) => new this.collects(item).data(ctx));
1936
+ data = await Promise.all(collected);
1937
+ data = await normalizeSerializableDataAsync(data);
1938
+ }
1939
+ serialize(data);
1940
+ }
1875
1941
  /**
1876
1942
  * Convert resource to JSON response format
1877
1943
  *
@@ -1883,8 +1949,7 @@ var GenericResource = class GenericResource extends BaseSerializer {
1883
1949
  const ctx = this.resolveSerializationContext();
1884
1950
  const resource = this.data(ctx);
1885
1951
  if (this.isPromiseLike(resource)) this.serializationPromise = Promise.resolve(resource).then((resolved) => {
1886
- const result = this.serializeGenericResource(resolved, ctx);
1887
- if (this.isPromiseLike(result)) return result;
1952
+ return this.serializeGenericResourceAsync(resolved, ctx);
1888
1953
  });
1889
1954
  else {
1890
1955
  const result = this.serializeGenericResource(resource, ctx);
@@ -2215,6 +2280,38 @@ var ResourceCollection = class ResourceCollection extends BaseSerializer {
2215
2280
  }, rootKey);
2216
2281
  this.body = this.applySerializePlugins(this.body);
2217
2282
  }
2283
+ async serializeCollectionDataAsync(items) {
2284
+ let data = await normalizeSerializableDataAsync(items);
2285
+ data = sanitizeConditionalAttributes(data);
2286
+ const paginationExtras = !Array.isArray(this.resource) ? buildPaginationExtras(this.resource) : {};
2287
+ const { metaKey } = getPaginationExtraKeys();
2288
+ const configuredMeta = metaKey ? paginationExtras[metaKey] : void 0;
2289
+ if (metaKey) delete paginationExtras[metaKey];
2290
+ const caseStyle = this.resolveSerializerCaseStyle(this.constructor, this.resolveCollectsConfig());
2291
+ if (caseStyle) {
2292
+ const transformer = getCaseTransformer(caseStyle);
2293
+ data = transformKeys(data, transformer);
2294
+ }
2295
+ const customMeta = this.resolveMergedMeta(ResourceCollection.prototype.with);
2296
+ const { wrap, rootKey, factory } = this.resolveResponseStructure();
2297
+ this.body = buildResponseEnvelope({
2298
+ payload: data,
2299
+ meta: configuredMeta,
2300
+ metaKey,
2301
+ wrap,
2302
+ rootKey,
2303
+ factory,
2304
+ context: {
2305
+ type: "collection",
2306
+ resource: this.resource
2307
+ }
2308
+ });
2309
+ this.body = appendRootProperties(this.body, {
2310
+ ...paginationExtras,
2311
+ ...customMeta || {}
2312
+ }, rootKey);
2313
+ this.body = this.applySerializePlugins(this.body);
2314
+ }
2218
2315
  resolveCollectionDataForSerialization(items, ctx) {
2219
2316
  if (this.collects && this.data === ResourceCollection.prototype.data) {
2220
2317
  const collected = items.map((item) => new this.collects(item).data(ctx));
@@ -2236,11 +2333,16 @@ var ResourceCollection = class ResourceCollection extends BaseSerializer {
2236
2333
  const serialize = (items) => {
2237
2334
  const resolvedData = this.resolveCollectionDataForSerialization(items, ctx);
2238
2335
  if (this.isPromiseLike(resolvedData)) return resolvedData.then((resolved) => {
2239
- this.serializeCollectionData(resolved);
2336
+ return this.serializeCollectionDataAsync(resolved);
2240
2337
  });
2241
2338
  this.serializeCollectionData(resolvedData);
2242
2339
  };
2243
- if (this.isPromiseLike(data)) this.serializationPromise = Promise.resolve(data).then(serialize);
2340
+ if (this.isPromiseLike(data)) this.serializationPromise = Promise.resolve(data).then((items) => {
2341
+ const resolvedData = this.resolveCollectionDataForSerialization(items, ctx);
2342
+ return Promise.resolve(resolvedData).then((resolved) => {
2343
+ return this.serializeCollectionDataAsync(resolved);
2344
+ });
2345
+ });
2244
2346
  else {
2245
2347
  const result = serialize(data);
2246
2348
  if (this.isPromiseLike(result)) this.serializationPromise = Promise.resolve(result);
@@ -2539,6 +2641,30 @@ var Resource = class Resource extends BaseSerializer {
2539
2641
  this.body = appendRootProperties(this.body, customMeta, rootKey);
2540
2642
  this.body = this.applySerializePlugins(this.body);
2541
2643
  }
2644
+ async serializeResourceAsync(resource) {
2645
+ let data = await normalizeSerializableDataAsync(resource);
2646
+ if (!Array.isArray(data) && data && typeof data.data !== "undefined") data = data.data;
2647
+ data = sanitizeConditionalAttributes(data);
2648
+ const caseStyle = this.resolveSerializerCaseStyle(this.constructor);
2649
+ if (caseStyle) {
2650
+ const transformer = getCaseTransformer(caseStyle);
2651
+ data = transformKeys(data, transformer);
2652
+ }
2653
+ const customMeta = this.resolveMergedMeta(Resource.prototype.with);
2654
+ const { wrap, rootKey, factory } = this.resolveResponseStructure();
2655
+ this.body = buildResponseEnvelope({
2656
+ payload: data,
2657
+ wrap,
2658
+ rootKey,
2659
+ factory,
2660
+ context: {
2661
+ type: "resource",
2662
+ resource: this.resource
2663
+ }
2664
+ });
2665
+ this.body = appendRootProperties(this.body, customMeta, rootKey);
2666
+ this.body = this.applySerializePlugins(this.body);
2667
+ }
2542
2668
  /**
2543
2669
  * Convert resource to JSON response format
2544
2670
  *
@@ -2550,7 +2676,7 @@ var Resource = class Resource extends BaseSerializer {
2550
2676
  const ctx = this.resolveSerializationContext();
2551
2677
  const resource = this.data(ctx);
2552
2678
  if (this.isPromiseLike(resource)) this.serializationPromise = Promise.resolve(resource).then((resolved) => {
2553
- this.serializeResource(resolved);
2679
+ return this.serializeResourceAsync(resolved);
2554
2680
  });
2555
2681
  else this.serializeResource(resource);
2556
2682
  }
@@ -2722,4 +2848,4 @@ var Resource = class Resource extends BaseSerializer {
2722
2848
  };
2723
2849
 
2724
2850
  //#endregion
2725
- export { ApiResource, CONDITIONAL_ATTRIBUTE_MISSING, CliApp, GenericResource, InitCommand, MakeResource, Resource, ResourceCollection, ServerResponse, appendRootProperties, applyRuntimeConfig, buildPaginationExtras, buildResponseEnvelope, createArkormCurrentPageResolver, defineConfig, definePlugin, extractRequestUrl, extractResponseFromCtx, getCaseTransformer, getCtx, getDefaultConfig, getGlobalBaseUrl, getGlobalCase, getGlobalCursorMeta, getGlobalPageName, getGlobalPaginatedExtras, getGlobalPaginatedLinks, getGlobalPaginatedMeta, getGlobalResponseFactory, getGlobalResponseRootKey, getGlobalResponseStructure, getGlobalResponseWrap, getPaginationExtraKeys, getRegisteredPlugins, getRequestUrl, getUtility, hasPaginationLink, isArkormLikeCollection, isArkormLikeModel, isPlainObject, isResoraCollectionLike, loadRuntimeConfig, mergeMetadata, normalizeSerializableData, registerPlugin, registerUtility, resetPluginsForTests, resetRuntimeConfigForTests, resolveCurrentPage, resolveMergeWhen, resolveWhen, resolveWhenNotNull, resolveWithHookMetadata, runPluginHook, runWithCtx, sanitizeConditionalAttributes, setCtx, setGlobalBaseUrl, setGlobalCase, setGlobalCursorMeta, setGlobalPageName, setGlobalPaginatedExtras, setGlobalPaginatedLinks, setGlobalPaginatedMeta, setGlobalResponseFactory, setGlobalResponseRootKey, setGlobalResponseStructure, setGlobalResponseWrap, setRequestUrl, splitWords, toCamelCase, toKebabCase, toPascalCase, toSnakeCase, transformKeys };
2851
+ export { ApiResource, CONDITIONAL_ATTRIBUTE_MISSING, CliApp, GenericResource, InitCommand, MakeResource, Resource, ResourceCollection, ServerResponse, appendRootProperties, applyRuntimeConfig, buildPaginationExtras, buildResponseEnvelope, createArkormCurrentPageResolver, defineConfig, definePlugin, extractRequestUrl, extractResponseFromCtx, getCaseTransformer, getCtx, getDefaultConfig, getGlobalBaseUrl, getGlobalCase, getGlobalCursorMeta, getGlobalPageName, getGlobalPaginatedExtras, getGlobalPaginatedLinks, getGlobalPaginatedMeta, getGlobalResponseFactory, getGlobalResponseRootKey, getGlobalResponseStructure, getGlobalResponseWrap, getPaginationExtraKeys, getRegisteredPlugins, getRequestUrl, getUtility, hasPaginationLink, isArkormLikeCollection, isArkormLikeModel, isPlainObject, isResoraCollectionLike, loadRuntimeConfig, mergeMetadata, normalizeSerializableData, normalizeSerializableDataAsync, registerPlugin, registerUtility, resetPluginsForTests, resetRuntimeConfigForTests, resolveCurrentPage, resolveMergeWhen, resolveWhen, resolveWhenNotNull, resolveWithHookMetadata, runPluginHook, runWithCtx, sanitizeConditionalAttributes, setCtx, setGlobalBaseUrl, setGlobalCase, setGlobalCursorMeta, setGlobalPageName, setGlobalPaginatedExtras, setGlobalPaginatedLinks, setGlobalPaginatedMeta, setGlobalResponseFactory, setGlobalResponseRootKey, setGlobalResponseStructure, setGlobalResponseWrap, setRequestUrl, splitWords, toCamelCase, toKebabCase, toPascalCase, toSnakeCase, transformKeys };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "resora",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "A structured API response layer for Node.js and TypeScript with automatic JSON responses, collection support, and pagination handling.",
5
5
  "keywords": [
6
6
  "api",