Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 29 additions & 11 deletions src/BaseSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ export abstract class BaseSerializer<TResource = any> {
protected abstract getSerializerType (): ResponseKind
protected abstract setBody (body: any): this

/**
* Report whether a synchronous serialization pass encountered async data.
*/
isSerializationPending (): boolean {
return this.called.json === false
}

/**
* Apply registered plugins for the serialization process, allowing plugins to
* modify the response body and metadata before the response is sent.
Expand Down Expand Up @@ -279,6 +286,7 @@ export abstract class BaseSerializer<TResource = any> {
*/
protected runResponse<TBody, TRawResponse, TServerResponse> (input: {
ensureJson: () => void
ensureJsonAsync?: () => Promise<void>
rawResponse: TRawResponse
body: () => TBody
createServerResponse: (raw: TRawResponse, body: TBody) => TServerResponse
Expand All @@ -287,21 +295,31 @@ export abstract class BaseSerializer<TResource = any> {
this.called.toResponse = true
input.ensureJson()

const resolvedBody = input.body()
const response = input.createServerResponse(input.rawResponse, resolvedBody)
const response = input.createServerResponse(input.rawResponse, input.body())
const finalizeResponse = () => {
this.called.withResponse = true
input.callWithResponse(response, input.rawResponse)

this.called.withResponse = true
input.callWithResponse(response, input.rawResponse)
if (typeof (response as any)?.setBody === 'function') {
(response as any).setBody(input.body())
}

if (typeof (response as any)?.setBody === 'function') {
(response as any).setBody(input.body())
return this.applyResponsePlugins({
body: input.body(),
rawResponse: input.rawResponse,
response,
})
}

this.applyResponsePlugins({
body: input.body(),
rawResponse: input.rawResponse,
response,
})
if (!this.called.json && input.ensureJsonAsync && typeof (response as any)?.setBodyResolver === 'function') {
(response as any).setBodyResolver(async () => {
await input.ensureJsonAsync!()

return finalizeResponse()
})
} else {
finalizeResponse()
}

return response
}
Expand Down
24 changes: 20 additions & 4 deletions src/GenericResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ import {
getPaginationExtraKeys,
isArkormLikeCollection,
isArkormLikeModel,
isPromiseLike,
normalizeSerializableData,
normalizeSerializableDataAsync,
requiresAsyncNormalization,
sanitizeConditionalAttributes,
setRequestUrl,
transformKeys,
Expand All @@ -44,6 +44,8 @@ export class GenericResource<
> extends BaseSerializer<R> {
[key: string]: any;
private body: GenericBody<R> = { data: {} as any }
private pendingData?: unknown
private pendingDataCollected = false
private res?: Response
public resource: R
public collects?: typeof Resource<T>
Expand Down Expand Up @@ -242,7 +244,8 @@ export class GenericResource<
const ctx = this.resolveSerializationContext()
const resource = this.data(ctx)

if (isPromiseLike(resource)) {
if (requiresAsyncNormalization(resource)) {
this.pendingData = resource
this.called.json = false

return this
Expand All @@ -254,6 +257,14 @@ export class GenericResource<
data = data.map(item => new this.collects!(item).data(ctx))
}

if (requiresAsyncNormalization(data)) {
this.pendingData = data
this.pendingDataCollected = true
this.called.json = false

return this
}

if (!Array.isArray(data) && data && typeof data.data !== 'undefined') {
data = data.data
}
Expand Down Expand Up @@ -313,11 +324,15 @@ export class GenericResource<
this.called.json = true

const ctx = this.resolveSerializationContext()
const resource = await this.data(ctx)
const hasPendingData = typeof this.pendingData !== 'undefined'
const pendingDataCollected = this.pendingDataCollected
const resource = hasPendingData ? this.pendingData : this.data(ctx)
this.pendingData = undefined
this.pendingDataCollected = false

let data: any = await normalizeSerializableDataAsync(resource)

if (Array.isArray(data) && this.collects) {
if (Array.isArray(data) && this.collects && !pendingDataCollected) {
data = await Promise.all(data.map(async item => new this.collects!(item).data(ctx)))
data = await normalizeSerializableDataAsync(data)
}
Expand Down Expand Up @@ -458,6 +473,7 @@ export class GenericResource<

return this.runResponse({
ensureJson: () => this.json(),
ensureJsonAsync: () => this.jsonAsync(),
rawResponse,
body: () => this.body,
createServerResponse: (raw, body) => {
Expand Down
10 changes: 7 additions & 3 deletions src/Resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import {
extractResponseFromCtx,
getCaseTransformer,
isArkormLikeModel,
isPromiseLike,
normalizeSerializableData,
normalizeSerializableDataAsync,
requiresAsyncNormalization,
sanitizeConditionalAttributes,
setRequestUrl,
transformKeys,
Expand All @@ -38,6 +38,7 @@ import {
export class Resource<R extends ResourceData | NonCollectible = ResourceData> extends BaseSerializer<R> {
[key: string]: any;
private body: ResourceBody<R> = { data: {} as any }
private pendingData?: unknown
private res?: Response
public resource: R
protected withResponseContext?: {
Expand Down Expand Up @@ -196,7 +197,8 @@ export class Resource<R extends ResourceData | NonCollectible = ResourceData> ex
const ctx = this.resolveSerializationContext()
const resource = this.data(ctx)

if (isPromiseLike(resource)) {
if (requiresAsyncNormalization(resource)) {
this.pendingData = resource
this.called.json = false

return this
Expand Down Expand Up @@ -249,7 +251,8 @@ export class Resource<R extends ResourceData | NonCollectible = ResourceData> ex
this.called.json = true

const ctx = this.resolveSerializationContext()
const resource = await this.data(ctx)
const resource = this.pendingData ?? this.data(ctx)
this.pendingData = undefined

let data: any = await normalizeSerializableDataAsync(resource)

Expand Down Expand Up @@ -364,6 +367,7 @@ export class Resource<R extends ResourceData | NonCollectible = ResourceData> ex

return this.runResponse({
ensureJson: () => this.json(),
ensureJsonAsync: () => this.jsonAsync(),
rawResponse,
body: () => this.body,
createServerResponse: (raw, body) => {
Expand Down
24 changes: 20 additions & 4 deletions src/ResourceCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import {
getCaseTransformer,
getPaginationExtraKeys,
isArkormLikeCollection,
isPromiseLike,
normalizeSerializableData,
normalizeSerializableDataAsync,
requiresAsyncNormalization,
sanitizeConditionalAttributes,
setRequestUrl,
transformKeys,
Expand All @@ -43,6 +43,8 @@ export class ResourceCollection<
> extends BaseSerializer<R> {
[key: string]: any;
private body: CollectionBody<R> = { data: [] as any }
private pendingData?: unknown
private pendingDataCollected = false
private res?: Response
public resource: R
public collects?: typeof Resource<T>
Expand Down Expand Up @@ -252,7 +254,8 @@ export class ResourceCollection<
const ctx = this.resolveSerializationContext()
let data: ResourceData[] = this.data(ctx) as never

if (isPromiseLike(data)) {
if (requiresAsyncNormalization(data)) {
this.pendingData = data
this.called.json = false

return this
Expand All @@ -262,6 +265,14 @@ export class ResourceCollection<
data = data.map((item: any) => new this.collects!(item).data(ctx))
}

if (requiresAsyncNormalization(data)) {
this.pendingData = data
this.pendingDataCollected = true
this.called.json = false

return this
}

data = normalizeSerializableData(data) as ResourceData[]

data = sanitizeConditionalAttributes(data) as ResourceData[]
Expand Down Expand Up @@ -326,9 +337,13 @@ export class ResourceCollection<
this.called.json = true

const ctx = this.resolveSerializationContext()
let data: ResourceData[] = await this.data(ctx) as never
const hasPendingData = typeof this.pendingData !== 'undefined'
const pendingDataCollected = this.pendingDataCollected
let data: ResourceData[] = (hasPendingData ? this.pendingData : await this.data(ctx)) as never
this.pendingData = undefined
this.pendingDataCollected = false

if (this.collects && this.data === ResourceCollection.prototype.data) {
if (this.collects && this.data === ResourceCollection.prototype.data && !pendingDataCollected) {
data = await Promise.all(data.map(async (item: any) => new this.collects!(item).data(ctx))) as ResourceData[]
}

Expand Down Expand Up @@ -505,6 +520,7 @@ export class ResourceCollection<

return this.runResponse({
ensureJson: () => this.json(),
ensureJsonAsync: () => this.jsonAsync(),
rawResponse,
body: () => this.body,
createServerResponse: (raw, body) => {
Expand Down
Loading
Loading