diff --git a/package.json b/package.json index e3e1c318f..d7beb0d50 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "author": "", "license": "MIT", "devDependencies": { - "@changesets/cli": "^2.26.0" + "@changesets/cli": "^2.26.0", + "concurrently": "^7.4.0", + "tsup": "^7.1.0" } } diff --git a/packages/plugins/openapi/src/rest-generator.ts b/packages/plugins/openapi/src/rest-generator.ts index 5a15f5681..58d2e1633 100644 --- a/packages/plugins/openapi/src/rest-generator.ts +++ b/packages/plugins/openapi/src/rest-generator.ts @@ -536,12 +536,16 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase { required: ['version'], properties: { version: { type: 'string' }, - meta: this.ref('_meta'), }, }, _meta: { type: 'object', - description: 'Meta information about the response', + description: 'Meta information about the request or response', + properties: { + serialization: { + description: 'Superjson serialization metadata', + }, + }, additionalProperties: true, }, _resourceIdentifier: { @@ -759,6 +763,7 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase { required: ['data'], properties: { data: this.generateModelEntity(model, 'create'), + meta: this.ref('_meta'), }, }; @@ -766,7 +771,7 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase { type: 'object', description: `Input for updating a "${model.name}"`, required: ['data'], - properties: { data: this.generateModelEntity(model, 'update') }, + properties: { data: this.generateModelEntity(model, 'update'), meta: this.ref('_meta') }, }; const relationships: Record = {}; @@ -790,7 +795,7 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase { type: 'object', properties: { relationships: { type: 'object', properties: relationships } }, }), - + meta: this.ref('_meta'), included: { type: 'array', items: this.ref('_resource'), @@ -811,6 +816,7 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase { properties: { relationships: { type: 'object', properties: relationships } }, }) ), + meta: this.ref('_meta'), included: { type: 'array', items: this.ref('_resource'), @@ -895,14 +901,17 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase { case 'BigInt': return { type: 'integer' }; case 'Float': - case 'Decimal': return { type: 'number' }; + case 'Decimal': + return this.oneOf({ type: 'number' }, { type: 'string' }); case 'Boolean': return { type: 'boolean' }; case 'DateTime': return { type: 'string', format: 'date-time' }; + case 'Bytes': + return { type: 'string', format: 'byte', description: 'Base64 encoded byte array' }; case 'Json': - return { type: 'object' }; + return {}; default: { const fieldDecl = type.reference?.ref; invariant(fieldDecl); diff --git a/packages/plugins/openapi/src/rpc-generator.ts b/packages/plugins/openapi/src/rpc-generator.ts index 303ac3f83..497615686 100644 --- a/packages/plugins/openapi/src/rpc-generator.ts +++ b/packages/plugins/openapi/src/rpc-generator.ts @@ -144,15 +144,17 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { `${model.name}CreateArgs`, { type: 'object', + required: ['data'], properties: { select: this.ref(`${model.name}Select`), include: hasRelation ? this.ref(`${model.name}Include`) : undefined, data: this.ref(`${model.name}CreateInput`), + meta: this.ref('_Meta'), }, }, components ), - outputType: this.ref(model.name), + outputType: this.response(this.ref(model.name)), description: `Create a new ${model.name}`, successCode: 201, security: create === true ? [] : undefined, @@ -167,13 +169,15 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { `${model.name}CreateManyArgs`, { type: 'object', + required: ['data'], properties: { data: this.ref(`${model.name}CreateManyInput`), + meta: this.ref('_Meta'), }, }, components ), - outputType: this.ref('BatchPayload'), + outputType: this.response(this.ref('BatchPayload')), description: `Create several ${model.name}`, successCode: 201, security: create === true ? [] : undefined, @@ -188,15 +192,17 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { `${model.name}FindUniqueArgs`, { type: 'object', + required: ['where'], properties: { select: this.ref(`${model.name}Select`), include: hasRelation ? this.ref(`${model.name}Include`) : undefined, where: this.ref(`${model.name}WhereUniqueInput`), + meta: this.ref('_Meta'), }, }, components ), - outputType: this.ref(model.name), + outputType: this.response(this.ref(model.name)), description: `Find one unique ${model.name}`, security: read === true ? [] : undefined, }); @@ -214,11 +220,12 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { select: this.ref(`${model.name}Select`), include: hasRelation ? this.ref(`${model.name}Include`) : undefined, where: this.ref(`${model.name}WhereInput`), + meta: this.ref('_Meta'), }, }, components ), - outputType: this.ref(model.name), + outputType: this.response(this.ref(model.name)), description: `Find the first ${model.name} matching the given condition`, security: read === true ? [] : undefined, }); @@ -236,11 +243,12 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { select: this.ref(`${model.name}Select`), include: hasRelation ? this.ref(`${model.name}Include`) : undefined, where: this.ref(`${model.name}WhereInput`), + meta: this.ref('_Meta'), }, }, components ), - outputType: this.array(this.ref(model.name)), + outputType: this.response(this.array(this.ref(model.name))), description: `Find a list of ${model.name}`, security: read === true ? [] : undefined, }); @@ -254,16 +262,18 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { `${model.name}UpdateArgs`, { type: 'object', + required: ['where', 'data'], properties: { select: this.ref(`${model.name}Select`), include: hasRelation ? this.ref(`${model.name}Include`) : undefined, where: this.ref(`${model.name}WhereUniqueInput`), data: this.ref(`${model.name}UpdateInput`), + meta: this.ref('_Meta'), }, }, components ), - outputType: this.ref(model.name), + outputType: this.response(this.ref(model.name)), description: `Update a ${model.name}`, security: update === true ? [] : undefined, }); @@ -277,14 +287,16 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { `${model.name}UpdateManyArgs`, { type: 'object', + required: ['data'], properties: { where: this.ref(`${model.name}WhereInput`), data: this.ref(`${model.name}UpdateManyMutationInput`), + meta: this.ref('_Meta'), }, }, components ), - outputType: this.ref('BatchPayload'), + outputType: this.response(this.ref('BatchPayload')), description: `Update ${model.name}s matching the given condition`, security: update === true ? [] : undefined, }); @@ -298,17 +310,19 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { `${model.name}UpsertArgs`, { type: 'object', + required: ['create', 'update', 'where'], properties: { select: this.ref(`${model.name}Select`), include: hasRelation ? this.ref(`${model.name}Include`) : undefined, where: this.ref(`${model.name}WhereUniqueInput`), create: this.ref(`${model.name}CreateInput`), update: this.ref(`${model.name}UpdateInput`), + meta: this.ref('_Meta'), }, }, components ), - outputType: this.ref(model.name), + outputType: this.response(this.ref(model.name)), description: `Upsert a ${model.name}`, security: create === true && update == true ? [] : undefined, }); @@ -322,15 +336,17 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { `${model.name}DeleteUniqueArgs`, { type: 'object', + required: ['where'], properties: { select: this.ref(`${model.name}Select`), include: hasRelation ? this.ref(`${model.name}Include`) : undefined, where: this.ref(`${model.name}WhereUniqueInput`), + meta: this.ref('_Meta'), }, }, components ), - outputType: this.ref(model.name), + outputType: this.response(this.ref(model.name)), description: `Delete one unique ${model.name}`, security: del === true ? [] : undefined, }); @@ -346,11 +362,12 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { type: 'object', properties: { where: this.ref(`${model.name}WhereInput`), + meta: this.ref('_Meta'), }, }, components ), - outputType: this.ref('BatchPayload'), + outputType: this.response(this.ref('BatchPayload')), description: `Delete ${model.name}s matching the given condition`, security: del === true ? [] : undefined, }); @@ -367,11 +384,14 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { properties: { select: this.ref(`${model.name}Select`), where: this.ref(`${model.name}WhereInput`), + meta: this.ref('_Meta'), }, }, components ), - outputType: this.oneOf({ type: 'integer' }, this.ref(`${model.name}CountAggregateOutputType`)), + outputType: this.response( + this.oneOf({ type: 'integer' }, this.ref(`${model.name}CountAggregateOutputType`)) + ), description: `Find a list of ${model.name}`, security: read === true ? [] : undefined, }); @@ -391,11 +411,12 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { take: { type: 'integer' }, skip: { type: 'integer' }, ...this.aggregateFields(model), + meta: this.ref('_Meta'), }, }, components ), - outputType: this.ref(`Aggregate${model.name}`), + outputType: this.response(this.ref(`Aggregate${model.name}`)), description: `Aggregate ${model.name}s`, security: read === true ? [] : undefined, }); @@ -417,11 +438,12 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { take: { type: 'integer' }, skip: { type: 'integer' }, ...this.aggregateFields(model), + meta: this.ref('_Meta'), }, }, components ), - outputType: this.array(this.ref(`${model.name}GroupByOutputType`)), + outputType: this.response(this.array(this.ref(`${model.name}GroupByOutputType`))), description: `Group ${model.name}s by fields`, security: read === true ? [] : undefined, }); @@ -467,9 +489,19 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { }, }, '400': { + content: { + 'application/json': { + schema: this.ref('_Error'), + }, + }, description: 'Invalid request', }, '403': { + content: { + 'application/json': { + schema: this.ref('_Error'), + }, + }, description: 'Request is forbidden', }, }, @@ -490,12 +522,23 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { name: 'q', in: 'query', required: true, + description: 'Superjson-serialized Prisma query object', content: { 'application/json': { schema: inputType, }, }, }, + { + name: 'meta', + in: 'query', + description: 'Superjson serialization metadata for parameter "q"', + content: { + 'application/json': { + schema: {}, + }, + }, + }, ] satisfies OAPI.ParameterObject[]; } } @@ -563,6 +606,56 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { schemas[output.name] = this.generateOutputComponent(output); } + schemas['_Meta'] = { + type: 'object', + properties: { + meta: { + type: 'object', + description: 'Meta information about the request or response', + properties: { + serialization: { + description: 'Serialization metadata', + }, + }, + additionalProperties: true, + }, + }, + }; + + schemas['_Error'] = { + type: 'object', + required: ['error'], + properties: { + error: { + type: 'object', + required: ['message'], + properties: { + prisma: { + type: 'boolean', + description: 'Indicates if the error occurred during a Prisma call', + }, + rejectedByPolicy: { + type: 'boolean', + description: 'Indicates if the error was due to rejection by a policy', + }, + code: { + type: 'string', + description: 'Prisma error code. Only available when "prisma" field is true.', + }, + message: { + type: 'string', + description: 'Error message', + }, + reason: { + type: 'string', + description: 'Detailed error reason', + }, + }, + additionalProperties: true, + }, + }, + }; + // misc types schemas['BatchPayload'] = { type: 'object', @@ -682,13 +775,16 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { case 'BigInt': return { type: 'integer' }; case 'Float': - case 'Decimal': return { type: 'number' }; + case 'Decimal': + return this.oneOf({ type: 'string' }, { type: 'number' }); case 'Boolean': case 'True': return { type: 'boolean' }; case 'DateTime': return { type: 'string', format: 'date-time' }; + case 'Bytes': + return { type: 'string', format: 'byte' }; case 'JSON': case 'Json': return {}; @@ -697,10 +793,21 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { } } - private ref(type: string, rooted = true) { + private ref(type: string, rooted = true, description?: string): OAPI.ReferenceObject { if (rooted) { this.usedComponents.add(type); } - return { $ref: `#/components/schemas/${type}` }; + return { $ref: `#/components/schemas/${type}`, description }; + } + + private response(schema: OAPI.SchemaObject): OAPI.SchemaObject { + return { + type: 'object', + required: ['data'], + properties: { + data: { ...schema, description: 'The Prisma response data serialized with superjson' }, + meta: this.ref('_Meta', true, 'The superjson serialization metadata for the "data" field'), + }, + }; } } diff --git a/packages/plugins/openapi/tests/baseline/rest-type-coverage.baseline.yaml b/packages/plugins/openapi/tests/baseline/rest-type-coverage.baseline.yaml new file mode 100644 index 000000000..72f43187f --- /dev/null +++ b/packages/plugins/openapi/tests/baseline/rest-type-coverage.baseline.yaml @@ -0,0 +1,802 @@ +openapi: 3.1.0 +info: + title: ZenStack Generated API + version: 1.0.0 +tags: + - name: foo + description: Foo operations +paths: + /foo: + get: + operationId: list-Foo + description: List "Foo" resources + tags: + - foo + parameters: + - $ref: '#/components/parameters/include' + - $ref: '#/components/parameters/sort' + - $ref: '#/components/parameters/page-offset' + - $ref: '#/components/parameters/page-limit' + - name: filter[id] + required: false + description: Id filter + in: query + style: form + explode: false + schema: + type: string + - name: filter[string] + required: false + description: Equality filter for "string" + in: query + style: form + explode: false + schema: + type: string + - name: filter[string$contains] + required: false + description: String contains filter for "string" + in: query + style: form + explode: false + schema: + type: string + - name: filter[string$icontains] + required: false + description: String case-insensitive contains filter for "string" + in: query + style: form + explode: false + schema: + type: string + - name: filter[string$search] + required: false + description: String full-text search filter for "string" + in: query + style: form + explode: false + schema: + type: string + - name: filter[string$startsWith] + required: false + description: String startsWith filter for "string" + in: query + style: form + explode: false + schema: + type: string + - name: filter[string$endsWith] + required: false + description: String endsWith filter for "string" + in: query + style: form + explode: false + schema: + type: string + - name: filter[int] + required: false + description: Equality filter for "int" + in: query + style: form + explode: false + schema: + type: integer + - name: filter[int$lt] + required: false + description: Less-than filter for "int" + in: query + style: form + explode: false + schema: + type: integer + - name: filter[int$lte] + required: false + description: Less-than or equal filter for "int" + in: query + style: form + explode: false + schema: + type: integer + - name: filter[int$gt] + required: false + description: Greater-than filter for "int" + in: query + style: form + explode: false + schema: + type: integer + - name: filter[int$gte] + required: false + description: Greater-than or equal filter for "int" + in: query + style: form + explode: false + schema: + type: integer + - name: filter[bigInt] + required: false + description: Equality filter for "bigInt" + in: query + style: form + explode: false + schema: + type: integer + - name: filter[bigInt$lt] + required: false + description: Less-than filter for "bigInt" + in: query + style: form + explode: false + schema: + type: integer + - name: filter[bigInt$lte] + required: false + description: Less-than or equal filter for "bigInt" + in: query + style: form + explode: false + schema: + type: integer + - name: filter[bigInt$gt] + required: false + description: Greater-than filter for "bigInt" + in: query + style: form + explode: false + schema: + type: integer + - name: filter[bigInt$gte] + required: false + description: Greater-than or equal filter for "bigInt" + in: query + style: form + explode: false + schema: + type: integer + - name: filter[date] + required: false + description: Equality filter for "date" + in: query + style: form + explode: false + schema: + type: string + format: date-time + - name: filter[date$lt] + required: false + description: Less-than filter for "date" + in: query + style: form + explode: false + schema: + type: string + format: date-time + - name: filter[date$lte] + required: false + description: Less-than or equal filter for "date" + in: query + style: form + explode: false + schema: + type: string + format: date-time + - name: filter[date$gt] + required: false + description: Greater-than filter for "date" + in: query + style: form + explode: false + schema: + type: string + format: date-time + - name: filter[date$gte] + required: false + description: Greater-than or equal filter for "date" + in: query + style: form + explode: false + schema: + type: string + format: date-time + - name: filter[float] + required: false + description: Equality filter for "float" + in: query + style: form + explode: false + schema: + type: number + - name: filter[float$lt] + required: false + description: Less-than filter for "float" + in: query + style: form + explode: false + schema: + type: number + - name: filter[float$lte] + required: false + description: Less-than or equal filter for "float" + in: query + style: form + explode: false + schema: + type: number + - name: filter[float$gt] + required: false + description: Greater-than filter for "float" + in: query + style: form + explode: false + schema: + type: number + - name: filter[float$gte] + required: false + description: Greater-than or equal filter for "float" + in: query + style: form + explode: false + schema: + type: number + - name: filter[decimal] + required: false + description: Equality filter for "decimal" + in: query + style: form + explode: false + schema: + oneOf: + - type: number + - type: string + - name: filter[decimal$lt] + required: false + description: Less-than filter for "decimal" + in: query + style: form + explode: false + schema: + oneOf: + - type: number + - type: string + - name: filter[decimal$lte] + required: false + description: Less-than or equal filter for "decimal" + in: query + style: form + explode: false + schema: + oneOf: + - type: number + - type: string + - name: filter[decimal$gt] + required: false + description: Greater-than filter for "decimal" + in: query + style: form + explode: false + schema: + oneOf: + - type: number + - type: string + - name: filter[decimal$gte] + required: false + description: Greater-than or equal filter for "decimal" + in: query + style: form + explode: false + schema: + oneOf: + - type: number + - type: string + - name: filter[boolean] + required: false + description: Equality filter for "boolean" + in: query + style: form + explode: false + schema: + type: boolean + - name: filter[bytes] + required: false + description: Equality filter for "bytes" + in: query + style: form + explode: false + schema: + type: string + format: byte + description: Base64 encoded byte array + responses: + '200': + description: Successful operation + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/FooListResponse' + '403': + description: Request is forbidden + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/_errorResponse' + security: [] + post: + operationId: create-Foo + description: Create a "Foo" resource + tags: + - foo + requestBody: + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/FooCreateRequest' + responses: + '201': + description: Successful operation + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/FooResponse' + '403': + description: Request is forbidden + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/_errorResponse' + security: [] + '/foo/{id}': + get: + operationId: fetch-Foo + description: Fetch a "Foo" resource + tags: + - foo + parameters: + - $ref: '#/components/parameters/id' + - $ref: '#/components/parameters/include' + responses: + '200': + description: Successful operation + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/FooResponse' + '403': + description: Request is forbidden + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/_errorResponse' + '404': + description: Resource is not found + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/_errorResponse' + security: [] + put: + operationId: update-Foo-put + description: Update a "Foo" resource + tags: + - foo + parameters: + - $ref: '#/components/parameters/id' + requestBody: + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/FooUpdateRequest' + responses: + '200': + description: Successful operation + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/FooResponse' + '403': + description: Request is forbidden + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/_errorResponse' + '404': + description: Resource is not found + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/_errorResponse' + security: [] + patch: + operationId: update-Foo-patch + description: Update a "Foo" resource + tags: + - foo + parameters: + - $ref: '#/components/parameters/id' + requestBody: + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/FooUpdateRequest' + responses: + '200': + description: Successful operation + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/FooResponse' + '403': + description: Request is forbidden + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/_errorResponse' + '404': + description: Resource is not found + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/_errorResponse' + security: [] + delete: + operationId: delete-Foo + description: Delete a "Foo" resource + tags: + - foo + parameters: + - $ref: '#/components/parameters/id' + responses: + '200': + description: Successful operation + '403': + description: Request is forbidden + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/_errorResponse' + '404': + description: Resource is not found + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/_errorResponse' + security: [] +components: + schemas: + _jsonapi: + type: object + description: An object describing the server’s implementation + required: + - version + properties: + version: + type: string + _meta: + type: object + description: Meta information about the request or response + properties: + serialization: + description: Superjson serialization metadata + additionalProperties: true + _resourceIdentifier: + type: object + description: Identifier for a resource + required: + - type + - id + properties: + type: + type: string + description: Resource type + id: + type: string + description: Resource id + _resource: + allOf: + - $ref: '#/components/schemas/_resourceIdentifier' + - type: object + description: A resource with attributes and relationships + properties: + attributes: + type: object + description: Resource attributes + relationships: + type: object + description: Resource relationships + _links: + type: object + required: + - self + description: Links related to the resource + properties: + self: + type: string + description: Link for refetching the curent results + _pagination: + type: object + description: Pagination information + required: + - first + - last + - prev + - next + properties: + first: + oneOf: + - type: string + description: Link to the first page + - type: 'null' + last: + oneOf: + - type: string + description: Link to the last page + - type: 'null' + prev: + oneOf: + - type: string + description: Link to the previous page + - type: 'null' + next: + oneOf: + - type: string + description: Link to the next page + - type: 'null' + _errors: + type: array + description: An array of error objects + items: + type: object + required: + - status + - code + properties: + status: + type: string + description: HTTP status + code: + type: string + description: Error code + prismaCode: + type: string + description: Prisma error code if the error is thrown by Prisma + title: + type: string + description: Error title + detail: + type: string + description: Error detail + _errorResponse: + type: object + required: + - errors + description: An error response + properties: + jsonapi: + $ref: '#/components/schemas/_jsonapi' + errors: + $ref: '#/components/schemas/_errors' + Foo: + type: object + description: The "Foo" model + required: + - id + - type + - attributes + properties: + type: + type: string + id: + type: string + attributes: + type: object + properties: + string: + type: string + int: + type: integer + bigInt: + type: integer + date: + type: string + format: date-time + float: + type: number + decimal: + oneOf: + - type: number + - type: string + boolean: + type: boolean + bytes: + type: string + format: byte + description: Base64 encoded byte array + FooCreateRequest: + type: object + description: Input for creating a "Foo" + required: + - data + properties: + data: + type: object + description: The "Foo" model + required: + - id + - type + - attributes + properties: + type: + type: string + id: + type: string + attributes: + type: object + required: + - string + - int + - bigInt + - date + - float + - decimal + - boolean + - bytes + properties: + string: + type: string + int: + type: integer + bigInt: + type: integer + date: + type: string + format: date-time + float: + type: number + decimal: + oneOf: + - type: number + - type: string + boolean: + type: boolean + bytes: + type: string + format: byte + description: Base64 encoded byte array + meta: + $ref: '#/components/schemas/_meta' + FooUpdateRequest: + type: object + description: Input for updating a "Foo" + required: + - data + properties: + data: + type: object + description: The "Foo" model + required: + - id + - type + - attributes + properties: + type: + type: string + id: + type: string + attributes: + type: object + properties: + string: + type: string + int: + type: integer + bigInt: + type: integer + date: + type: string + format: date-time + float: + type: number + decimal: + oneOf: + - type: number + - type: string + boolean: + type: boolean + bytes: + type: string + format: byte + description: Base64 encoded byte array + meta: + $ref: '#/components/schemas/_meta' + FooResponse: + type: object + description: Response for a "Foo" + required: + - data + properties: + jsonapi: + $ref: '#/components/schemas/_jsonapi' + data: + allOf: + - $ref: '#/components/schemas/Foo' + - type: object + properties: + relationships: + type: object + properties: &a1 {} + meta: + $ref: '#/components/schemas/_meta' + included: + type: array + items: + $ref: '#/components/schemas/_resource' + links: + $ref: '#/components/schemas/_links' + FooListResponse: + type: object + description: Response for a list of "Foo" + required: + - data + - links + properties: + jsonapi: + $ref: '#/components/schemas/_jsonapi' + data: + type: array + items: + allOf: + - $ref: '#/components/schemas/Foo' + - type: object + properties: + relationships: + type: object + properties: *a1 + meta: + $ref: '#/components/schemas/_meta' + included: + type: array + items: + $ref: '#/components/schemas/_resource' + links: + allOf: + - $ref: '#/components/schemas/_links' + - $ref: '#/components/schemas/_pagination' + parameters: + id: + name: id + in: path + description: The resource id + required: true + schema: + type: string + include: + name: include + in: query + description: Relationships to include + required: false + style: form + schema: + type: string + sort: + name: sort + in: query + description: Fields to sort by + required: false + style: form + schema: + type: string + page-offset: + name: page[offset] + in: query + description: Offset for pagination + required: false + style: form + schema: + type: integer + page-limit: + name: page[limit] + in: query + description: Limit for pagination + required: false + style: form + schema: + type: integer diff --git a/packages/plugins/openapi/tests/baseline/rest.baseline.yaml b/packages/plugins/openapi/tests/baseline/rest.baseline.yaml index 1c5542024..466556993 100644 --- a/packages/plugins/openapi/tests/baseline/rest.baseline.yaml +++ b/packages/plugins/openapi/tests/baseline/rest.baseline.yaml @@ -1301,11 +1301,12 @@ components: properties: version: type: string - meta: - $ref: '#/components/schemas/_meta' _meta: type: object - description: Meta information about the response + description: Meta information about the request or response + properties: + serialization: + description: Superjson serialization metadata additionalProperties: true _resourceIdentifier: type: object @@ -1577,6 +1578,8 @@ components: properties: posts: $ref: '#/components/schemas/_toManyRelationship' + meta: + $ref: '#/components/schemas/_meta' UserUpdateRequest: type: object description: Input for updating a "User" @@ -1613,6 +1616,8 @@ components: properties: posts: $ref: '#/components/schemas/_toManyRelationship' + meta: + $ref: '#/components/schemas/_meta' UserResponse: type: object description: Response for a "User" @@ -1631,6 +1636,8 @@ components: properties: &a1 posts: $ref: '#/components/schemas/_toManyRelationship' + meta: + $ref: '#/components/schemas/_meta' included: type: array items: @@ -1656,6 +1663,8 @@ components: relationships: type: object properties: *a1 + meta: + $ref: '#/components/schemas/_meta' included: type: array items: @@ -1741,6 +1750,8 @@ components: properties: author: $ref: '#/components/schemas/_toOneRelationship' + meta: + $ref: '#/components/schemas/_meta' PostUpdateRequest: type: object description: Input for updating a "Post" @@ -1781,6 +1792,8 @@ components: properties: author: $ref: '#/components/schemas/_toOneRelationship' + meta: + $ref: '#/components/schemas/_meta' PostResponse: type: object description: Response for a "Post" @@ -1799,6 +1812,8 @@ components: properties: &a2 author: $ref: '#/components/schemas/_toOneRelationship' + meta: + $ref: '#/components/schemas/_meta' included: type: array items: @@ -1824,6 +1839,8 @@ components: relationships: type: object properties: *a2 + meta: + $ref: '#/components/schemas/_meta' included: type: array items: diff --git a/packages/plugins/openapi/tests/baseline/rpc-type-coverage.baseline.yaml b/packages/plugins/openapi/tests/baseline/rpc-type-coverage.baseline.yaml new file mode 100644 index 000000000..a5cc407bc --- /dev/null +++ b/packages/plugins/openapi/tests/baseline/rpc-type-coverage.baseline.yaml @@ -0,0 +1,2518 @@ +openapi: 3.1.0 +info: + title: ZenStack Generated API + version: 1.0.0 +tags: + - name: foo + description: Foo operations +components: + schemas: + FooScalarFieldEnum: + type: string + enum: + - id + - string + - int + - bigInt + - date + - float + - decimal + - boolean + - bytes + QueryMode: + type: string + enum: + - default + - insensitive + SortOrder: + type: string + enum: + - asc + - desc + Foo: + type: object + properties: + id: + type: string + string: + type: string + int: + type: integer + bigInt: + type: integer + date: + type: string + format: date-time + float: + type: number + decimal: + oneOf: + - type: string + - type: number + boolean: + type: boolean + bytes: + type: string + format: byte + required: + - id + - string + - int + - bigInt + - date + - float + - decimal + - boolean + - bytes + FooWhereInput: + type: object + properties: + AND: + oneOf: + - $ref: '#/components/schemas/FooWhereInput' + - type: array + items: + $ref: '#/components/schemas/FooWhereInput' + OR: + type: array + items: + $ref: '#/components/schemas/FooWhereInput' + NOT: + oneOf: + - $ref: '#/components/schemas/FooWhereInput' + - type: array + items: + $ref: '#/components/schemas/FooWhereInput' + id: + oneOf: + - $ref: '#/components/schemas/StringFilter' + - type: string + string: + oneOf: + - $ref: '#/components/schemas/StringFilter' + - type: string + int: + oneOf: + - $ref: '#/components/schemas/IntFilter' + - type: integer + bigInt: + oneOf: + - $ref: '#/components/schemas/BigIntFilter' + - type: integer + date: + oneOf: + - $ref: '#/components/schemas/DateTimeFilter' + - type: string + format: date-time + float: + oneOf: + - $ref: '#/components/schemas/FloatFilter' + - type: number + decimal: + oneOf: + - $ref: '#/components/schemas/DecimalFilter' + - oneOf: + - type: string + - type: number + boolean: + oneOf: + - $ref: '#/components/schemas/BoolFilter' + - type: boolean + bytes: + oneOf: + - $ref: '#/components/schemas/BytesFilter' + - type: string + format: byte + FooOrderByWithRelationInput: + type: object + properties: + id: + $ref: '#/components/schemas/SortOrder' + string: + $ref: '#/components/schemas/SortOrder' + int: + $ref: '#/components/schemas/SortOrder' + bigInt: + $ref: '#/components/schemas/SortOrder' + date: + $ref: '#/components/schemas/SortOrder' + float: + $ref: '#/components/schemas/SortOrder' + decimal: + $ref: '#/components/schemas/SortOrder' + boolean: + $ref: '#/components/schemas/SortOrder' + bytes: + $ref: '#/components/schemas/SortOrder' + FooWhereUniqueInput: + type: object + properties: + id: + type: string + FooScalarWhereWithAggregatesInput: + type: object + properties: + AND: + oneOf: + - $ref: '#/components/schemas/FooScalarWhereWithAggregatesInput' + - type: array + items: + $ref: '#/components/schemas/FooScalarWhereWithAggregatesInput' + OR: + type: array + items: + $ref: '#/components/schemas/FooScalarWhereWithAggregatesInput' + NOT: + oneOf: + - $ref: '#/components/schemas/FooScalarWhereWithAggregatesInput' + - type: array + items: + $ref: '#/components/schemas/FooScalarWhereWithAggregatesInput' + id: + oneOf: + - $ref: '#/components/schemas/StringWithAggregatesFilter' + - type: string + string: + oneOf: + - $ref: '#/components/schemas/StringWithAggregatesFilter' + - type: string + int: + oneOf: + - $ref: '#/components/schemas/IntWithAggregatesFilter' + - type: integer + bigInt: + oneOf: + - $ref: '#/components/schemas/BigIntWithAggregatesFilter' + - type: integer + date: + oneOf: + - $ref: '#/components/schemas/DateTimeWithAggregatesFilter' + - type: string + format: date-time + float: + oneOf: + - $ref: '#/components/schemas/FloatWithAggregatesFilter' + - type: number + decimal: + oneOf: + - $ref: '#/components/schemas/DecimalWithAggregatesFilter' + - oneOf: + - type: string + - type: number + boolean: + oneOf: + - $ref: '#/components/schemas/BoolWithAggregatesFilter' + - type: boolean + bytes: + oneOf: + - $ref: '#/components/schemas/BytesWithAggregatesFilter' + - type: string + format: byte + FooCreateInput: + type: object + properties: + id: + type: string + string: + type: string + int: + type: integer + bigInt: + type: integer + date: + type: string + format: date-time + float: + type: number + decimal: + oneOf: + - type: string + - type: number + boolean: + type: boolean + bytes: + type: string + format: byte + required: + - string + - int + - bigInt + - date + - float + - decimal + - boolean + - bytes + FooUpdateInput: + type: object + properties: + id: + oneOf: + - type: string + - $ref: '#/components/schemas/StringFieldUpdateOperationsInput' + string: + oneOf: + - type: string + - $ref: '#/components/schemas/StringFieldUpdateOperationsInput' + int: + oneOf: + - type: integer + - $ref: '#/components/schemas/IntFieldUpdateOperationsInput' + bigInt: + oneOf: + - type: integer + - $ref: '#/components/schemas/BigIntFieldUpdateOperationsInput' + date: + oneOf: + - type: string + format: date-time + - $ref: '#/components/schemas/DateTimeFieldUpdateOperationsInput' + float: + oneOf: + - type: number + - $ref: '#/components/schemas/FloatFieldUpdateOperationsInput' + decimal: + oneOf: + - oneOf: + - type: string + - type: number + - $ref: '#/components/schemas/DecimalFieldUpdateOperationsInput' + boolean: + oneOf: + - type: boolean + - $ref: '#/components/schemas/BoolFieldUpdateOperationsInput' + bytes: + oneOf: + - type: string + format: byte + - $ref: '#/components/schemas/BytesFieldUpdateOperationsInput' + FooCreateManyInput: + type: object + properties: + id: + type: string + string: + type: string + int: + type: integer + bigInt: + type: integer + date: + type: string + format: date-time + float: + type: number + decimal: + oneOf: + - type: string + - type: number + boolean: + type: boolean + bytes: + type: string + format: byte + required: + - string + - int + - bigInt + - date + - float + - decimal + - boolean + - bytes + FooUpdateManyMutationInput: + type: object + properties: + id: + oneOf: + - type: string + - $ref: '#/components/schemas/StringFieldUpdateOperationsInput' + string: + oneOf: + - type: string + - $ref: '#/components/schemas/StringFieldUpdateOperationsInput' + int: + oneOf: + - type: integer + - $ref: '#/components/schemas/IntFieldUpdateOperationsInput' + bigInt: + oneOf: + - type: integer + - $ref: '#/components/schemas/BigIntFieldUpdateOperationsInput' + date: + oneOf: + - type: string + format: date-time + - $ref: '#/components/schemas/DateTimeFieldUpdateOperationsInput' + float: + oneOf: + - type: number + - $ref: '#/components/schemas/FloatFieldUpdateOperationsInput' + decimal: + oneOf: + - oneOf: + - type: string + - type: number + - $ref: '#/components/schemas/DecimalFieldUpdateOperationsInput' + boolean: + oneOf: + - type: boolean + - $ref: '#/components/schemas/BoolFieldUpdateOperationsInput' + bytes: + oneOf: + - type: string + format: byte + - $ref: '#/components/schemas/BytesFieldUpdateOperationsInput' + StringFilter: + type: object + properties: + equals: + type: string + in: + type: array + items: + type: string + notIn: + type: array + items: + type: string + lt: + type: string + lte: + type: string + gt: + type: string + gte: + type: string + contains: + type: string + startsWith: + type: string + endsWith: + type: string + mode: + $ref: '#/components/schemas/QueryMode' + not: + oneOf: + - type: string + - $ref: '#/components/schemas/NestedStringFilter' + IntFilter: + type: object + properties: + equals: + type: integer + in: + type: array + items: + type: integer + notIn: + type: array + items: + type: integer + lt: + type: integer + lte: + type: integer + gt: + type: integer + gte: + type: integer + not: + oneOf: + - type: integer + - $ref: '#/components/schemas/NestedIntFilter' + BigIntFilter: + type: object + properties: + equals: + type: integer + in: + type: array + items: + type: integer + notIn: + type: array + items: + type: integer + lt: + type: integer + lte: + type: integer + gt: + type: integer + gte: + type: integer + not: + oneOf: + - type: integer + - $ref: '#/components/schemas/NestedBigIntFilter' + DateTimeFilter: + type: object + properties: + equals: + type: string + format: date-time + in: + type: array + items: + type: string + format: date-time + notIn: + type: array + items: + type: string + format: date-time + lt: + type: string + format: date-time + lte: + type: string + format: date-time + gt: + type: string + format: date-time + gte: + type: string + format: date-time + not: + oneOf: + - type: string + format: date-time + - $ref: '#/components/schemas/NestedDateTimeFilter' + FloatFilter: + type: object + properties: + equals: + type: number + in: + type: array + items: + type: number + notIn: + type: array + items: + type: number + lt: + type: number + lte: + type: number + gt: + type: number + gte: + type: number + not: + oneOf: + - type: number + - $ref: '#/components/schemas/NestedFloatFilter' + DecimalFilter: + type: object + properties: + equals: + oneOf: + - type: string + - type: number + in: + type: array + items: + oneOf: + - type: string + - type: number + notIn: + type: array + items: + oneOf: + - type: string + - type: number + lt: + oneOf: + - type: string + - type: number + lte: + oneOf: + - type: string + - type: number + gt: + oneOf: + - type: string + - type: number + gte: + oneOf: + - type: string + - type: number + not: + oneOf: + - oneOf: + - type: string + - type: number + - $ref: '#/components/schemas/NestedDecimalFilter' + BoolFilter: + type: object + properties: + equals: + type: boolean + not: + oneOf: + - type: boolean + - $ref: '#/components/schemas/NestedBoolFilter' + BytesFilter: + type: object + properties: + equals: + type: string + format: byte + in: + type: array + items: + type: string + format: byte + notIn: + type: array + items: + type: string + format: byte + not: + oneOf: + - type: string + format: byte + - $ref: '#/components/schemas/NestedBytesFilter' + StringWithAggregatesFilter: + type: object + properties: + equals: + type: string + in: + type: array + items: + type: string + notIn: + type: array + items: + type: string + lt: + type: string + lte: + type: string + gt: + type: string + gte: + type: string + contains: + type: string + startsWith: + type: string + endsWith: + type: string + mode: + $ref: '#/components/schemas/QueryMode' + not: + oneOf: + - type: string + - $ref: '#/components/schemas/NestedStringWithAggregatesFilter' + _count: + $ref: '#/components/schemas/NestedIntFilter' + _min: + $ref: '#/components/schemas/NestedStringFilter' + _max: + $ref: '#/components/schemas/NestedStringFilter' + IntWithAggregatesFilter: + type: object + properties: + equals: + type: integer + in: + type: array + items: + type: integer + notIn: + type: array + items: + type: integer + lt: + type: integer + lte: + type: integer + gt: + type: integer + gte: + type: integer + not: + oneOf: + - type: integer + - $ref: '#/components/schemas/NestedIntWithAggregatesFilter' + _count: + $ref: '#/components/schemas/NestedIntFilter' + _avg: + $ref: '#/components/schemas/NestedFloatFilter' + _sum: + $ref: '#/components/schemas/NestedIntFilter' + _min: + $ref: '#/components/schemas/NestedIntFilter' + _max: + $ref: '#/components/schemas/NestedIntFilter' + BigIntWithAggregatesFilter: + type: object + properties: + equals: + type: integer + in: + type: array + items: + type: integer + notIn: + type: array + items: + type: integer + lt: + type: integer + lte: + type: integer + gt: + type: integer + gte: + type: integer + not: + oneOf: + - type: integer + - $ref: '#/components/schemas/NestedBigIntWithAggregatesFilter' + _count: + $ref: '#/components/schemas/NestedIntFilter' + _avg: + $ref: '#/components/schemas/NestedFloatFilter' + _sum: + $ref: '#/components/schemas/NestedBigIntFilter' + _min: + $ref: '#/components/schemas/NestedBigIntFilter' + _max: + $ref: '#/components/schemas/NestedBigIntFilter' + DateTimeWithAggregatesFilter: + type: object + properties: + equals: + type: string + format: date-time + in: + type: array + items: + type: string + format: date-time + notIn: + type: array + items: + type: string + format: date-time + lt: + type: string + format: date-time + lte: + type: string + format: date-time + gt: + type: string + format: date-time + gte: + type: string + format: date-time + not: + oneOf: + - type: string + format: date-time + - $ref: '#/components/schemas/NestedDateTimeWithAggregatesFilter' + _count: + $ref: '#/components/schemas/NestedIntFilter' + _min: + $ref: '#/components/schemas/NestedDateTimeFilter' + _max: + $ref: '#/components/schemas/NestedDateTimeFilter' + FloatWithAggregatesFilter: + type: object + properties: + equals: + type: number + in: + type: array + items: + type: number + notIn: + type: array + items: + type: number + lt: + type: number + lte: + type: number + gt: + type: number + gte: + type: number + not: + oneOf: + - type: number + - $ref: '#/components/schemas/NestedFloatWithAggregatesFilter' + _count: + $ref: '#/components/schemas/NestedIntFilter' + _avg: + $ref: '#/components/schemas/NestedFloatFilter' + _sum: + $ref: '#/components/schemas/NestedFloatFilter' + _min: + $ref: '#/components/schemas/NestedFloatFilter' + _max: + $ref: '#/components/schemas/NestedFloatFilter' + DecimalWithAggregatesFilter: + type: object + properties: + equals: + oneOf: + - type: string + - type: number + in: + type: array + items: + oneOf: + - type: string + - type: number + notIn: + type: array + items: + oneOf: + - type: string + - type: number + lt: + oneOf: + - type: string + - type: number + lte: + oneOf: + - type: string + - type: number + gt: + oneOf: + - type: string + - type: number + gte: + oneOf: + - type: string + - type: number + not: + oneOf: + - oneOf: + - type: string + - type: number + - $ref: '#/components/schemas/NestedDecimalWithAggregatesFilter' + _count: + $ref: '#/components/schemas/NestedIntFilter' + _avg: + $ref: '#/components/schemas/NestedDecimalFilter' + _sum: + $ref: '#/components/schemas/NestedDecimalFilter' + _min: + $ref: '#/components/schemas/NestedDecimalFilter' + _max: + $ref: '#/components/schemas/NestedDecimalFilter' + BoolWithAggregatesFilter: + type: object + properties: + equals: + type: boolean + not: + oneOf: + - type: boolean + - $ref: '#/components/schemas/NestedBoolWithAggregatesFilter' + _count: + $ref: '#/components/schemas/NestedIntFilter' + _min: + $ref: '#/components/schemas/NestedBoolFilter' + _max: + $ref: '#/components/schemas/NestedBoolFilter' + BytesWithAggregatesFilter: + type: object + properties: + equals: + type: string + format: byte + in: + type: array + items: + type: string + format: byte + notIn: + type: array + items: + type: string + format: byte + not: + oneOf: + - type: string + format: byte + - $ref: '#/components/schemas/NestedBytesWithAggregatesFilter' + _count: + $ref: '#/components/schemas/NestedIntFilter' + _min: + $ref: '#/components/schemas/NestedBytesFilter' + _max: + $ref: '#/components/schemas/NestedBytesFilter' + StringFieldUpdateOperationsInput: + type: object + properties: + set: + type: string + IntFieldUpdateOperationsInput: + type: object + properties: + set: + type: integer + increment: + type: integer + decrement: + type: integer + multiply: + type: integer + divide: + type: integer + BigIntFieldUpdateOperationsInput: + type: object + properties: + set: + type: integer + increment: + type: integer + decrement: + type: integer + multiply: + type: integer + divide: + type: integer + DateTimeFieldUpdateOperationsInput: + type: object + properties: + set: + type: string + format: date-time + FloatFieldUpdateOperationsInput: + type: object + properties: + set: + type: number + increment: + type: number + decrement: + type: number + multiply: + type: number + divide: + type: number + DecimalFieldUpdateOperationsInput: + type: object + properties: + set: + oneOf: + - type: string + - type: number + increment: + oneOf: + - type: string + - type: number + decrement: + oneOf: + - type: string + - type: number + multiply: + oneOf: + - type: string + - type: number + divide: + oneOf: + - type: string + - type: number + BoolFieldUpdateOperationsInput: + type: object + properties: + set: + type: boolean + BytesFieldUpdateOperationsInput: + type: object + properties: + set: + type: string + format: byte + NestedStringFilter: + type: object + properties: + equals: + type: string + in: + type: array + items: + type: string + notIn: + type: array + items: + type: string + lt: + type: string + lte: + type: string + gt: + type: string + gte: + type: string + contains: + type: string + startsWith: + type: string + endsWith: + type: string + not: + oneOf: + - type: string + - $ref: '#/components/schemas/NestedStringFilter' + NestedIntFilter: + type: object + properties: + equals: + type: integer + in: + type: array + items: + type: integer + notIn: + type: array + items: + type: integer + lt: + type: integer + lte: + type: integer + gt: + type: integer + gte: + type: integer + not: + oneOf: + - type: integer + - $ref: '#/components/schemas/NestedIntFilter' + NestedBigIntFilter: + type: object + properties: + equals: + type: integer + in: + type: array + items: + type: integer + notIn: + type: array + items: + type: integer + lt: + type: integer + lte: + type: integer + gt: + type: integer + gte: + type: integer + not: + oneOf: + - type: integer + - $ref: '#/components/schemas/NestedBigIntFilter' + NestedDateTimeFilter: + type: object + properties: + equals: + type: string + format: date-time + in: + type: array + items: + type: string + format: date-time + notIn: + type: array + items: + type: string + format: date-time + lt: + type: string + format: date-time + lte: + type: string + format: date-time + gt: + type: string + format: date-time + gte: + type: string + format: date-time + not: + oneOf: + - type: string + format: date-time + - $ref: '#/components/schemas/NestedDateTimeFilter' + NestedFloatFilter: + type: object + properties: + equals: + type: number + in: + type: array + items: + type: number + notIn: + type: array + items: + type: number + lt: + type: number + lte: + type: number + gt: + type: number + gte: + type: number + not: + oneOf: + - type: number + - $ref: '#/components/schemas/NestedFloatFilter' + NestedDecimalFilter: + type: object + properties: + equals: + oneOf: + - type: string + - type: number + in: + type: array + items: + oneOf: + - type: string + - type: number + notIn: + type: array + items: + oneOf: + - type: string + - type: number + lt: + oneOf: + - type: string + - type: number + lte: + oneOf: + - type: string + - type: number + gt: + oneOf: + - type: string + - type: number + gte: + oneOf: + - type: string + - type: number + not: + oneOf: + - oneOf: + - type: string + - type: number + - $ref: '#/components/schemas/NestedDecimalFilter' + NestedBoolFilter: + type: object + properties: + equals: + type: boolean + not: + oneOf: + - type: boolean + - $ref: '#/components/schemas/NestedBoolFilter' + NestedBytesFilter: + type: object + properties: + equals: + type: string + format: byte + in: + type: array + items: + type: string + format: byte + notIn: + type: array + items: + type: string + format: byte + not: + oneOf: + - type: string + format: byte + - $ref: '#/components/schemas/NestedBytesFilter' + NestedStringWithAggregatesFilter: + type: object + properties: + equals: + type: string + in: + type: array + items: + type: string + notIn: + type: array + items: + type: string + lt: + type: string + lte: + type: string + gt: + type: string + gte: + type: string + contains: + type: string + startsWith: + type: string + endsWith: + type: string + not: + oneOf: + - type: string + - $ref: '#/components/schemas/NestedStringWithAggregatesFilter' + _count: + $ref: '#/components/schemas/NestedIntFilter' + _min: + $ref: '#/components/schemas/NestedStringFilter' + _max: + $ref: '#/components/schemas/NestedStringFilter' + NestedIntWithAggregatesFilter: + type: object + properties: + equals: + type: integer + in: + type: array + items: + type: integer + notIn: + type: array + items: + type: integer + lt: + type: integer + lte: + type: integer + gt: + type: integer + gte: + type: integer + not: + oneOf: + - type: integer + - $ref: '#/components/schemas/NestedIntWithAggregatesFilter' + _count: + $ref: '#/components/schemas/NestedIntFilter' + _avg: + $ref: '#/components/schemas/NestedFloatFilter' + _sum: + $ref: '#/components/schemas/NestedIntFilter' + _min: + $ref: '#/components/schemas/NestedIntFilter' + _max: + $ref: '#/components/schemas/NestedIntFilter' + NestedBigIntWithAggregatesFilter: + type: object + properties: + equals: + type: integer + in: + type: array + items: + type: integer + notIn: + type: array + items: + type: integer + lt: + type: integer + lte: + type: integer + gt: + type: integer + gte: + type: integer + not: + oneOf: + - type: integer + - $ref: '#/components/schemas/NestedBigIntWithAggregatesFilter' + _count: + $ref: '#/components/schemas/NestedIntFilter' + _avg: + $ref: '#/components/schemas/NestedFloatFilter' + _sum: + $ref: '#/components/schemas/NestedBigIntFilter' + _min: + $ref: '#/components/schemas/NestedBigIntFilter' + _max: + $ref: '#/components/schemas/NestedBigIntFilter' + NestedDateTimeWithAggregatesFilter: + type: object + properties: + equals: + type: string + format: date-time + in: + type: array + items: + type: string + format: date-time + notIn: + type: array + items: + type: string + format: date-time + lt: + type: string + format: date-time + lte: + type: string + format: date-time + gt: + type: string + format: date-time + gte: + type: string + format: date-time + not: + oneOf: + - type: string + format: date-time + - $ref: '#/components/schemas/NestedDateTimeWithAggregatesFilter' + _count: + $ref: '#/components/schemas/NestedIntFilter' + _min: + $ref: '#/components/schemas/NestedDateTimeFilter' + _max: + $ref: '#/components/schemas/NestedDateTimeFilter' + NestedFloatWithAggregatesFilter: + type: object + properties: + equals: + type: number + in: + type: array + items: + type: number + notIn: + type: array + items: + type: number + lt: + type: number + lte: + type: number + gt: + type: number + gte: + type: number + not: + oneOf: + - type: number + - $ref: '#/components/schemas/NestedFloatWithAggregatesFilter' + _count: + $ref: '#/components/schemas/NestedIntFilter' + _avg: + $ref: '#/components/schemas/NestedFloatFilter' + _sum: + $ref: '#/components/schemas/NestedFloatFilter' + _min: + $ref: '#/components/schemas/NestedFloatFilter' + _max: + $ref: '#/components/schemas/NestedFloatFilter' + NestedDecimalWithAggregatesFilter: + type: object + properties: + equals: + oneOf: + - type: string + - type: number + in: + type: array + items: + oneOf: + - type: string + - type: number + notIn: + type: array + items: + oneOf: + - type: string + - type: number + lt: + oneOf: + - type: string + - type: number + lte: + oneOf: + - type: string + - type: number + gt: + oneOf: + - type: string + - type: number + gte: + oneOf: + - type: string + - type: number + not: + oneOf: + - oneOf: + - type: string + - type: number + - $ref: '#/components/schemas/NestedDecimalWithAggregatesFilter' + _count: + $ref: '#/components/schemas/NestedIntFilter' + _avg: + $ref: '#/components/schemas/NestedDecimalFilter' + _sum: + $ref: '#/components/schemas/NestedDecimalFilter' + _min: + $ref: '#/components/schemas/NestedDecimalFilter' + _max: + $ref: '#/components/schemas/NestedDecimalFilter' + NestedBoolWithAggregatesFilter: + type: object + properties: + equals: + type: boolean + not: + oneOf: + - type: boolean + - $ref: '#/components/schemas/NestedBoolWithAggregatesFilter' + _count: + $ref: '#/components/schemas/NestedIntFilter' + _min: + $ref: '#/components/schemas/NestedBoolFilter' + _max: + $ref: '#/components/schemas/NestedBoolFilter' + NestedBytesWithAggregatesFilter: + type: object + properties: + equals: + type: string + format: byte + in: + type: array + items: + type: string + format: byte + notIn: + type: array + items: + type: string + format: byte + not: + oneOf: + - type: string + format: byte + - $ref: '#/components/schemas/NestedBytesWithAggregatesFilter' + _count: + $ref: '#/components/schemas/NestedIntFilter' + _min: + $ref: '#/components/schemas/NestedBytesFilter' + _max: + $ref: '#/components/schemas/NestedBytesFilter' + FooSelect: + type: object + properties: + id: + type: boolean + string: + type: boolean + int: + type: boolean + bigInt: + type: boolean + date: + type: boolean + float: + type: boolean + decimal: + type: boolean + boolean: + type: boolean + bytes: + type: boolean + FooCountAggregateInput: + type: object + properties: + id: + type: boolean + string: + type: boolean + int: + type: boolean + bigInt: + type: boolean + date: + type: boolean + float: + type: boolean + decimal: + type: boolean + boolean: + type: boolean + bytes: + type: boolean + _all: + type: boolean + FooAvgAggregateInput: + type: object + properties: + int: + type: boolean + bigInt: + type: boolean + float: + type: boolean + decimal: + type: boolean + FooSumAggregateInput: + type: object + properties: + int: + type: boolean + bigInt: + type: boolean + float: + type: boolean + decimal: + type: boolean + FooMinAggregateInput: + type: object + properties: + id: + type: boolean + string: + type: boolean + int: + type: boolean + bigInt: + type: boolean + date: + type: boolean + float: + type: boolean + decimal: + type: boolean + boolean: + type: boolean + bytes: + type: boolean + FooMaxAggregateInput: + type: object + properties: + id: + type: boolean + string: + type: boolean + int: + type: boolean + bigInt: + type: boolean + date: + type: boolean + float: + type: boolean + decimal: + type: boolean + boolean: + type: boolean + bytes: + type: boolean + AggregateFoo: + type: object + properties: + _count: + $ref: '#/components/schemas/FooCountAggregateOutputType' + _avg: + $ref: '#/components/schemas/FooAvgAggregateOutputType' + _sum: + $ref: '#/components/schemas/FooSumAggregateOutputType' + _min: + $ref: '#/components/schemas/FooMinAggregateOutputType' + _max: + $ref: '#/components/schemas/FooMaxAggregateOutputType' + FooGroupByOutputType: + type: object + properties: + id: + type: string + string: + type: string + int: + type: integer + bigInt: + type: integer + date: + type: string + format: date-time + float: + type: number + decimal: + oneOf: + - type: string + - type: number + boolean: + type: boolean + bytes: + type: string + format: byte + _count: + $ref: '#/components/schemas/FooCountAggregateOutputType' + _avg: + $ref: '#/components/schemas/FooAvgAggregateOutputType' + _sum: + $ref: '#/components/schemas/FooSumAggregateOutputType' + _min: + $ref: '#/components/schemas/FooMinAggregateOutputType' + _max: + $ref: '#/components/schemas/FooMaxAggregateOutputType' + required: + - id + - string + - int + - bigInt + - date + - float + - decimal + - boolean + - bytes + FooCountAggregateOutputType: + type: object + properties: + id: + type: integer + string: + type: integer + int: + type: integer + bigInt: + type: integer + date: + type: integer + float: + type: integer + decimal: + type: integer + boolean: + type: integer + bytes: + type: integer + _all: + type: integer + required: + - id + - string + - int + - bigInt + - date + - float + - decimal + - boolean + - bytes + - _all + FooAvgAggregateOutputType: + type: object + properties: + int: + type: number + bigInt: + type: number + float: + type: number + decimal: + oneOf: + - type: string + - type: number + FooSumAggregateOutputType: + type: object + properties: + int: + type: integer + bigInt: + type: integer + float: + type: number + decimal: + oneOf: + - type: string + - type: number + FooMinAggregateOutputType: + type: object + properties: + id: + type: string + string: + type: string + int: + type: integer + bigInt: + type: integer + date: + type: string + format: date-time + float: + type: number + decimal: + oneOf: + - type: string + - type: number + boolean: + type: boolean + bytes: + type: string + format: byte + FooMaxAggregateOutputType: + type: object + properties: + id: + type: string + string: + type: string + int: + type: integer + bigInt: + type: integer + date: + type: string + format: date-time + float: + type: number + decimal: + oneOf: + - type: string + - type: number + boolean: + type: boolean + bytes: + type: string + format: byte + _Meta: + type: object + properties: + meta: + type: object + description: Meta information about the request or response + properties: + serialization: + description: Serialization metadata + additionalProperties: true + _Error: + type: object + required: + - error + properties: + error: + type: object + required: + - message + properties: + prisma: + type: boolean + description: Indicates if the error occurred during a Prisma call + rejectedByPolicy: + type: boolean + description: Indicates if the error was due to rejection by a policy + code: + type: string + description: Prisma error code. Only available when "prisma" field is true. + message: + type: string + description: Error message + reason: + type: string + description: Detailed error reason + additionalProperties: true + BatchPayload: + type: object + properties: + count: + type: integer + FooCreateArgs: + type: object + required: + - data + properties: + select: + $ref: '#/components/schemas/FooSelect' + data: + $ref: '#/components/schemas/FooCreateInput' + meta: + $ref: '#/components/schemas/_Meta' + FooCreateManyArgs: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/FooCreateManyInput' + meta: + $ref: '#/components/schemas/_Meta' + FooFindUniqueArgs: + type: object + required: + - where + properties: + select: + $ref: '#/components/schemas/FooSelect' + where: + $ref: '#/components/schemas/FooWhereUniqueInput' + meta: + $ref: '#/components/schemas/_Meta' + FooFindFirstArgs: + type: object + properties: + select: + $ref: '#/components/schemas/FooSelect' + where: + $ref: '#/components/schemas/FooWhereInput' + meta: + $ref: '#/components/schemas/_Meta' + FooFindManyArgs: + type: object + properties: + select: + $ref: '#/components/schemas/FooSelect' + where: + $ref: '#/components/schemas/FooWhereInput' + meta: + $ref: '#/components/schemas/_Meta' + FooUpdateArgs: + type: object + required: + - where + - data + properties: + select: + $ref: '#/components/schemas/FooSelect' + where: + $ref: '#/components/schemas/FooWhereUniqueInput' + data: + $ref: '#/components/schemas/FooUpdateInput' + meta: + $ref: '#/components/schemas/_Meta' + FooUpdateManyArgs: + type: object + required: + - data + properties: + where: + $ref: '#/components/schemas/FooWhereInput' + data: + $ref: '#/components/schemas/FooUpdateManyMutationInput' + meta: + $ref: '#/components/schemas/_Meta' + FooUpsertArgs: + type: object + required: + - create + - update + - where + properties: + select: + $ref: '#/components/schemas/FooSelect' + where: + $ref: '#/components/schemas/FooWhereUniqueInput' + create: + $ref: '#/components/schemas/FooCreateInput' + update: + $ref: '#/components/schemas/FooUpdateInput' + meta: + $ref: '#/components/schemas/_Meta' + FooDeleteUniqueArgs: + type: object + required: + - where + properties: + select: + $ref: '#/components/schemas/FooSelect' + where: + $ref: '#/components/schemas/FooWhereUniqueInput' + meta: + $ref: '#/components/schemas/_Meta' + FooDeleteManyArgs: + type: object + properties: + where: + $ref: '#/components/schemas/FooWhereInput' + meta: + $ref: '#/components/schemas/_Meta' + FooCountArgs: + type: object + properties: + select: + $ref: '#/components/schemas/FooSelect' + where: + $ref: '#/components/schemas/FooWhereInput' + meta: + $ref: '#/components/schemas/_Meta' + FooAggregateArgs: + type: object + properties: + where: + $ref: '#/components/schemas/FooWhereInput' + orderBy: + $ref: '#/components/schemas/FooOrderByWithRelationInput' + cursor: + $ref: '#/components/schemas/FooWhereUniqueInput' + take: + type: integer + skip: + type: integer + _count: + oneOf: + - type: boolean + - $ref: '#/components/schemas/FooCountAggregateInput' + _min: + $ref: '#/components/schemas/FooMinAggregateInput' + _max: + $ref: '#/components/schemas/FooMaxAggregateInput' + _sum: + $ref: '#/components/schemas/FooSumAggregateInput' + _avg: + $ref: '#/components/schemas/FooAvgAggregateInput' + meta: + $ref: '#/components/schemas/_Meta' + FooGroupByArgs: + type: object + properties: + where: + $ref: '#/components/schemas/FooWhereInput' + orderBy: + $ref: '#/components/schemas/FooOrderByWithRelationInput' + by: + $ref: '#/components/schemas/FooScalarFieldEnum' + having: + $ref: '#/components/schemas/FooScalarWhereWithAggregatesInput' + take: + type: integer + skip: + type: integer + _count: + oneOf: + - type: boolean + - $ref: '#/components/schemas/FooCountAggregateInput' + _min: + $ref: '#/components/schemas/FooMinAggregateInput' + _max: + $ref: '#/components/schemas/FooMaxAggregateInput' + _sum: + $ref: '#/components/schemas/FooSumAggregateInput' + _avg: + $ref: '#/components/schemas/FooAvgAggregateInput' + meta: + $ref: '#/components/schemas/_Meta' +paths: + /foo/create: + post: + operationId: createFoo + description: Create a new Foo + tags: + - foo + security: [] + responses: + '201': + description: Successful operation + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Foo' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Invalid request + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Request is forbidden + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FooCreateArgs' + /foo/createMany: + post: + operationId: createManyFoo + description: Create several Foo + tags: + - foo + security: [] + responses: + '201': + description: Successful operation + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/BatchPayload' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Invalid request + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Request is forbidden + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FooCreateManyArgs' + /foo/findUnique: + get: + operationId: findUniqueFoo + description: Find one unique Foo + tags: + - foo + security: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Foo' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Invalid request + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Request is forbidden + parameters: + - name: q + in: query + required: true + description: Superjson-serialized Prisma query object + content: + application/json: + schema: + $ref: '#/components/schemas/FooFindUniqueArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} + /foo/findFirst: + get: + operationId: findFirstFoo + description: Find the first Foo matching the given condition + tags: + - foo + security: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Foo' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Invalid request + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Request is forbidden + parameters: + - name: q + in: query + required: true + description: Superjson-serialized Prisma query object + content: + application/json: + schema: + $ref: '#/components/schemas/FooFindFirstArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} + /foo/findMany: + get: + operationId: findManyFoo + description: Find a list of Foo + tags: + - foo + security: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: '#/components/schemas/Foo' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Invalid request + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Request is forbidden + parameters: + - name: q + in: query + required: true + description: Superjson-serialized Prisma query object + content: + application/json: + schema: + $ref: '#/components/schemas/FooFindManyArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} + /foo/update: + patch: + operationId: updateFoo + description: Update a Foo + tags: + - foo + security: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Foo' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Invalid request + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Request is forbidden + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FooUpdateArgs' + /foo/updateMany: + patch: + operationId: updateManyFoo + description: Update Foos matching the given condition + tags: + - foo + security: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/BatchPayload' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Invalid request + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Request is forbidden + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FooUpdateManyArgs' + /foo/upsert: + post: + operationId: upsertFoo + description: Upsert a Foo + tags: + - foo + security: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Foo' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Invalid request + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Request is forbidden + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FooUpsertArgs' + /foo/delete: + delete: + operationId: deleteFoo + description: Delete one unique Foo + tags: + - foo + security: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Foo' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Invalid request + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Request is forbidden + parameters: + - name: q + in: query + required: true + description: Superjson-serialized Prisma query object + content: + application/json: + schema: + $ref: '#/components/schemas/FooDeleteUniqueArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} + /foo/deleteMany: + delete: + operationId: deleteManyFoo + description: Delete Foos matching the given condition + tags: + - foo + security: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/BatchPayload' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Invalid request + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Request is forbidden + parameters: + - name: q + in: query + required: true + description: Superjson-serialized Prisma query object + content: + application/json: + schema: + $ref: '#/components/schemas/FooDeleteManyArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} + /foo/count: + get: + operationId: countFoo + description: Find a list of Foo + tags: + - foo + security: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + oneOf: + - type: integer + - $ref: '#/components/schemas/FooCountAggregateOutputType' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Invalid request + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Request is forbidden + parameters: + - name: q + in: query + required: true + description: Superjson-serialized Prisma query object + content: + application/json: + schema: + $ref: '#/components/schemas/FooCountArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} + /foo/aggregate: + get: + operationId: aggregateFoo + description: Aggregate Foos + tags: + - foo + security: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/AggregateFoo' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Invalid request + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Request is forbidden + parameters: + - name: q + in: query + required: true + description: Superjson-serialized Prisma query object + content: + application/json: + schema: + $ref: '#/components/schemas/FooAggregateArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} + /foo/groupBy: + get: + operationId: groupByFoo + description: Group Foos by fields + tags: + - foo + security: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: '#/components/schemas/FooGroupByOutputType' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Invalid request + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' + description: Request is forbidden + parameters: + - name: q + in: query + required: true + description: Superjson-serialized Prisma query object + content: + application/json: + schema: + $ref: '#/components/schemas/FooGroupByArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} diff --git a/packages/plugins/openapi/tests/baseline/rpc.baseline.yaml b/packages/plugins/openapi/tests/baseline/rpc.baseline.yaml index 17f8fa8d4..bc013f02b 100644 --- a/packages/plugins/openapi/tests/baseline/rpc.baseline.yaml +++ b/packages/plugins/openapi/tests/baseline/rpc.baseline.yaml @@ -613,15 +613,6 @@ components: $ref: '#/components/schemas/PostWhereInput' none: $ref: '#/components/schemas/PostWhereInput' - BoolFilter: - type: object - properties: - equals: - type: boolean - not: - oneOf: - - type: boolean - - $ref: '#/components/schemas/NestedBoolFilter' PostOrderByRelationAggregateInput: type: object properties: @@ -728,21 +719,6 @@ components: $ref: '#/components/schemas/NestedEnumRoleFilter' _max: $ref: '#/components/schemas/NestedEnumRoleFilter' - BoolWithAggregatesFilter: - type: object - properties: - equals: - type: boolean - not: - oneOf: - - type: boolean - - $ref: '#/components/schemas/NestedBoolWithAggregatesFilter' - _count: - $ref: '#/components/schemas/NestedIntFilter' - _min: - $ref: '#/components/schemas/NestedBoolFilter' - _max: - $ref: '#/components/schemas/NestedBoolFilter' UserRelationFilter: type: object properties: @@ -783,6 +759,15 @@ components: oneOf: - type: string - $ref: '#/components/schemas/NestedStringNullableFilter' + BoolFilter: + type: object + properties: + equals: + type: boolean + not: + oneOf: + - type: boolean + - $ref: '#/components/schemas/NestedBoolFilter' IntFilter: type: object properties: @@ -847,6 +832,21 @@ components: $ref: '#/components/schemas/NestedStringNullableFilter' _max: $ref: '#/components/schemas/NestedStringNullableFilter' + BoolWithAggregatesFilter: + type: object + properties: + equals: + type: boolean + not: + oneOf: + - type: boolean + - $ref: '#/components/schemas/NestedBoolWithAggregatesFilter' + _count: + $ref: '#/components/schemas/NestedIntFilter' + _min: + $ref: '#/components/schemas/NestedBoolFilter' + _max: + $ref: '#/components/schemas/NestedBoolFilter' IntWithAggregatesFilter: type: object properties: @@ -994,11 +994,6 @@ components: - type: array items: $ref: '#/components/schemas/PostScalarWhereInput' - BoolFieldUpdateOperationsInput: - type: object - properties: - set: - type: boolean UserCreateNestedOneWithoutPostsInput: type: object properties: @@ -1031,6 +1026,11 @@ components: oneOf: - $ref: '#/components/schemas/UserUpdateWithoutPostsInput' - $ref: '#/components/schemas/UserUncheckedUpdateWithoutPostsInput' + BoolFieldUpdateOperationsInput: + type: object + properties: + set: + type: boolean IntFieldUpdateOperationsInput: type: object properties: @@ -1125,15 +1125,6 @@ components: oneOf: - $ref: '#/components/schemas/Role' - $ref: '#/components/schemas/NestedEnumRoleFilter' - NestedBoolFilter: - type: object - properties: - equals: - type: boolean - not: - oneOf: - - type: boolean - - $ref: '#/components/schemas/NestedBoolFilter' NestedStringWithAggregatesFilter: type: object properties: @@ -1258,21 +1249,6 @@ components: $ref: '#/components/schemas/NestedEnumRoleFilter' _max: $ref: '#/components/schemas/NestedEnumRoleFilter' - NestedBoolWithAggregatesFilter: - type: object - properties: - equals: - type: boolean - not: - oneOf: - - type: boolean - - $ref: '#/components/schemas/NestedBoolWithAggregatesFilter' - _count: - $ref: '#/components/schemas/NestedIntFilter' - _min: - $ref: '#/components/schemas/NestedBoolFilter' - _max: - $ref: '#/components/schemas/NestedBoolFilter' NestedStringNullableFilter: type: object properties: @@ -1304,6 +1280,15 @@ components: oneOf: - type: string - $ref: '#/components/schemas/NestedStringNullableFilter' + NestedBoolFilter: + type: object + properties: + equals: + type: boolean + not: + oneOf: + - type: boolean + - $ref: '#/components/schemas/NestedBoolFilter' NestedStringNullableWithAggregatesFilter: type: object properties: @@ -1366,6 +1351,21 @@ components: oneOf: - type: integer - $ref: '#/components/schemas/NestedIntNullableFilter' + NestedBoolWithAggregatesFilter: + type: object + properties: + equals: + type: boolean + not: + oneOf: + - type: boolean + - $ref: '#/components/schemas/NestedBoolWithAggregatesFilter' + _count: + $ref: '#/components/schemas/NestedIntFilter' + _min: + $ref: '#/components/schemas/NestedBoolFilter' + _max: + $ref: '#/components/schemas/NestedBoolFilter' NestedIntWithAggregatesFilter: type: object properties: @@ -2192,6 +2192,42 @@ components: type: boolean viewCount: type: integer + _Meta: + type: object + properties: + meta: + type: object + description: Meta information about the request or response + properties: + serialization: + description: Serialization metadata + additionalProperties: true + _Error: + type: object + required: + - error + properties: + error: + type: object + required: + - message + properties: + prisma: + type: boolean + description: Indicates if the error occurred during a Prisma call + rejectedByPolicy: + type: boolean + description: Indicates if the error was due to rejection by a policy + code: + type: string + description: Prisma error code. Only available when "prisma" field is true. + message: + type: string + description: Error message + reason: + type: string + description: Detailed error reason + additionalProperties: true BatchPayload: type: object properties: @@ -2199,6 +2235,8 @@ components: type: integer UserCreateArgs: type: object + required: + - data properties: select: $ref: '#/components/schemas/UserSelect' @@ -2206,13 +2244,21 @@ components: $ref: '#/components/schemas/UserInclude' data: $ref: '#/components/schemas/UserCreateInput' + meta: + $ref: '#/components/schemas/_Meta' UserCreateManyArgs: type: object + required: + - data properties: data: $ref: '#/components/schemas/UserCreateManyInput' + meta: + $ref: '#/components/schemas/_Meta' UserFindUniqueArgs: type: object + required: + - where properties: select: $ref: '#/components/schemas/UserSelect' @@ -2220,6 +2266,8 @@ components: $ref: '#/components/schemas/UserInclude' where: $ref: '#/components/schemas/UserWhereUniqueInput' + meta: + $ref: '#/components/schemas/_Meta' UserFindFirstArgs: type: object properties: @@ -2229,6 +2277,8 @@ components: $ref: '#/components/schemas/UserInclude' where: $ref: '#/components/schemas/UserWhereInput' + meta: + $ref: '#/components/schemas/_Meta' UserFindManyArgs: type: object properties: @@ -2238,8 +2288,13 @@ components: $ref: '#/components/schemas/UserInclude' where: $ref: '#/components/schemas/UserWhereInput' + meta: + $ref: '#/components/schemas/_Meta' UserUpdateArgs: type: object + required: + - where + - data properties: select: $ref: '#/components/schemas/UserSelect' @@ -2249,15 +2304,25 @@ components: $ref: '#/components/schemas/UserWhereUniqueInput' data: $ref: '#/components/schemas/UserUpdateInput' + meta: + $ref: '#/components/schemas/_Meta' UserUpdateManyArgs: type: object + required: + - data properties: where: $ref: '#/components/schemas/UserWhereInput' data: $ref: '#/components/schemas/UserUpdateManyMutationInput' + meta: + $ref: '#/components/schemas/_Meta' UserUpsertArgs: type: object + required: + - create + - update + - where properties: select: $ref: '#/components/schemas/UserSelect' @@ -2269,8 +2334,12 @@ components: $ref: '#/components/schemas/UserCreateInput' update: $ref: '#/components/schemas/UserUpdateInput' + meta: + $ref: '#/components/schemas/_Meta' UserDeleteUniqueArgs: type: object + required: + - where properties: select: $ref: '#/components/schemas/UserSelect' @@ -2278,11 +2347,15 @@ components: $ref: '#/components/schemas/UserInclude' where: $ref: '#/components/schemas/UserWhereUniqueInput' + meta: + $ref: '#/components/schemas/_Meta' UserDeleteManyArgs: type: object properties: where: $ref: '#/components/schemas/UserWhereInput' + meta: + $ref: '#/components/schemas/_Meta' UserCountArgs: type: object properties: @@ -2290,6 +2363,8 @@ components: $ref: '#/components/schemas/UserSelect' where: $ref: '#/components/schemas/UserWhereInput' + meta: + $ref: '#/components/schemas/_Meta' UserAggregateArgs: type: object properties: @@ -2311,6 +2386,8 @@ components: $ref: '#/components/schemas/UserMinAggregateInput' _max: $ref: '#/components/schemas/UserMaxAggregateInput' + meta: + $ref: '#/components/schemas/_Meta' UserGroupByArgs: type: object properties: @@ -2334,8 +2411,12 @@ components: $ref: '#/components/schemas/UserMinAggregateInput' _max: $ref: '#/components/schemas/UserMaxAggregateInput' + meta: + $ref: '#/components/schemas/_Meta' PostCreateArgs: type: object + required: + - data properties: select: $ref: '#/components/schemas/PostSelect' @@ -2343,13 +2424,21 @@ components: $ref: '#/components/schemas/PostInclude' data: $ref: '#/components/schemas/PostCreateInput' + meta: + $ref: '#/components/schemas/_Meta' PostCreateManyArgs: type: object + required: + - data properties: data: $ref: '#/components/schemas/PostCreateManyInput' + meta: + $ref: '#/components/schemas/_Meta' PostFindUniqueArgs: type: object + required: + - where properties: select: $ref: '#/components/schemas/PostSelect' @@ -2357,6 +2446,8 @@ components: $ref: '#/components/schemas/PostInclude' where: $ref: '#/components/schemas/PostWhereUniqueInput' + meta: + $ref: '#/components/schemas/_Meta' PostFindFirstArgs: type: object properties: @@ -2366,6 +2457,8 @@ components: $ref: '#/components/schemas/PostInclude' where: $ref: '#/components/schemas/PostWhereInput' + meta: + $ref: '#/components/schemas/_Meta' PostFindManyArgs: type: object properties: @@ -2375,8 +2468,13 @@ components: $ref: '#/components/schemas/PostInclude' where: $ref: '#/components/schemas/PostWhereInput' + meta: + $ref: '#/components/schemas/_Meta' PostUpdateArgs: type: object + required: + - where + - data properties: select: $ref: '#/components/schemas/PostSelect' @@ -2386,15 +2484,25 @@ components: $ref: '#/components/schemas/PostWhereUniqueInput' data: $ref: '#/components/schemas/PostUpdateInput' + meta: + $ref: '#/components/schemas/_Meta' PostUpdateManyArgs: type: object + required: + - data properties: where: $ref: '#/components/schemas/PostWhereInput' data: $ref: '#/components/schemas/PostUpdateManyMutationInput' + meta: + $ref: '#/components/schemas/_Meta' PostUpsertArgs: type: object + required: + - create + - update + - where properties: select: $ref: '#/components/schemas/PostSelect' @@ -2406,8 +2514,12 @@ components: $ref: '#/components/schemas/PostCreateInput' update: $ref: '#/components/schemas/PostUpdateInput' + meta: + $ref: '#/components/schemas/_Meta' PostDeleteUniqueArgs: type: object + required: + - where properties: select: $ref: '#/components/schemas/PostSelect' @@ -2415,11 +2527,15 @@ components: $ref: '#/components/schemas/PostInclude' where: $ref: '#/components/schemas/PostWhereUniqueInput' + meta: + $ref: '#/components/schemas/_Meta' PostDeleteManyArgs: type: object properties: where: $ref: '#/components/schemas/PostWhereInput' + meta: + $ref: '#/components/schemas/_Meta' PostCountArgs: type: object properties: @@ -2427,6 +2543,8 @@ components: $ref: '#/components/schemas/PostSelect' where: $ref: '#/components/schemas/PostWhereInput' + meta: + $ref: '#/components/schemas/_Meta' PostAggregateArgs: type: object properties: @@ -2452,6 +2570,8 @@ components: $ref: '#/components/schemas/PostSumAggregateInput' _avg: $ref: '#/components/schemas/PostAvgAggregateInput' + meta: + $ref: '#/components/schemas/_Meta' PostGroupByArgs: type: object properties: @@ -2479,6 +2599,8 @@ components: $ref: '#/components/schemas/PostSumAggregateInput' _avg: $ref: '#/components/schemas/PostAvgAggregateInput' + meta: + $ref: '#/components/schemas/_Meta' paths: /user/create: post: @@ -2492,10 +2614,27 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/User' + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/User' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden requestBody: content: @@ -2514,10 +2653,27 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/BatchPayload' + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/BatchPayload' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden requestBody: content: @@ -2536,19 +2692,43 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/User' + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/User' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden parameters: - name: q in: query required: true + description: Superjson-serialized Prisma query object content: application/json: schema: $ref: '#/components/schemas/UserFindUniqueArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} /user/findFirst: get: operationId: findFirstUser @@ -2561,19 +2741,43 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/User' + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/User' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden parameters: - name: q in: query required: true + description: Superjson-serialized Prisma query object content: application/json: schema: $ref: '#/components/schemas/UserFindFirstArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} /user/findMany: get: operationId: findManyUser @@ -2586,21 +2790,45 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/User' + type: object + required: + - data + properties: + data: + type: array + items: + $ref: '#/components/schemas/User' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden parameters: - name: q in: query required: true + description: Superjson-serialized Prisma query object content: application/json: schema: $ref: '#/components/schemas/UserFindManyArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} /user/update: patch: operationId: updateUser @@ -2613,10 +2841,27 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/User' + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/User' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden requestBody: content: @@ -2635,10 +2880,27 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/BatchPayload' + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/BatchPayload' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden requestBody: content: @@ -2657,10 +2919,27 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/User' + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/User' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden requestBody: content: @@ -2682,10 +2961,27 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/User' + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/User' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden requestBody: content: @@ -2704,19 +3000,43 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/BatchPayload' + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/BatchPayload' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden parameters: - name: q in: query required: true + description: Superjson-serialized Prisma query object content: application/json: schema: $ref: '#/components/schemas/UserDeleteManyArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} /user/count: get: operationId: countUser @@ -2729,21 +3049,45 @@ paths: content: application/json: schema: - oneOf: - - type: integer - - $ref: '#/components/schemas/UserCountAggregateOutputType' + type: object + required: + - data + properties: + data: + oneOf: + - type: integer + - $ref: '#/components/schemas/UserCountAggregateOutputType' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden parameters: - name: q in: query required: true + description: Superjson-serialized Prisma query object content: application/json: schema: $ref: '#/components/schemas/UserCountArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} /user/aggregate: get: operationId: aggregateUser @@ -2756,19 +3100,43 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/AggregateUser' + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/AggregateUser' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden parameters: - name: q in: query required: true + description: Superjson-serialized Prisma query object content: application/json: schema: $ref: '#/components/schemas/UserAggregateArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} /user/groupBy: get: operationId: groupByUser @@ -2781,21 +3149,45 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/UserGroupByOutputType' + type: object + required: + - data + properties: + data: + type: array + items: + $ref: '#/components/schemas/UserGroupByOutputType' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden parameters: - name: q in: query required: true + description: Superjson-serialized Prisma query object content: application/json: schema: $ref: '#/components/schemas/UserGroupByArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} /post/create: post: operationId: createPost @@ -2808,10 +3200,27 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Post' + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Post' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden requestBody: content: @@ -2830,10 +3239,27 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/BatchPayload' + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/BatchPayload' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden requestBody: content: @@ -2852,19 +3278,43 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Post' + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Post' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden parameters: - name: q in: query required: true + description: Superjson-serialized Prisma query object content: application/json: schema: $ref: '#/components/schemas/PostFindUniqueArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} /post/findFirst: get: operationId: findFirstPost @@ -2877,19 +3327,43 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Post' + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Post' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden parameters: - name: q in: query required: true + description: Superjson-serialized Prisma query object content: application/json: schema: $ref: '#/components/schemas/PostFindFirstArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} /post/update: patch: operationId: updatePost @@ -2902,10 +3376,27 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Post' + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Post' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden requestBody: content: @@ -2924,10 +3415,27 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/BatchPayload' + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/BatchPayload' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden requestBody: content: @@ -2946,10 +3454,27 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Post' + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Post' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden requestBody: content: @@ -2968,19 +3493,43 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Post' + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Post' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden parameters: - name: q in: query required: true + description: Superjson-serialized Prisma query object content: application/json: schema: $ref: '#/components/schemas/PostDeleteUniqueArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} /post/deleteMany: delete: operationId: deleteManyPost @@ -2993,19 +3542,43 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/BatchPayload' + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/BatchPayload' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden parameters: - name: q in: query required: true + description: Superjson-serialized Prisma query object content: application/json: schema: $ref: '#/components/schemas/PostDeleteManyArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} /post/count: get: operationId: countPost @@ -3018,21 +3591,45 @@ paths: content: application/json: schema: - oneOf: - - type: integer - - $ref: '#/components/schemas/PostCountAggregateOutputType' + type: object + required: + - data + properties: + data: + oneOf: + - type: integer + - $ref: '#/components/schemas/PostCountAggregateOutputType' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden parameters: - name: q in: query required: true + description: Superjson-serialized Prisma query object content: application/json: schema: $ref: '#/components/schemas/PostCountArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} /post/aggregate: get: operationId: aggregatePost @@ -3045,19 +3642,43 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/AggregatePost' + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/AggregatePost' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden parameters: - name: q in: query required: true + description: Superjson-serialized Prisma query object content: application/json: schema: $ref: '#/components/schemas/PostAggregateArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} /post/groupBy: get: operationId: groupByPost @@ -3070,18 +3691,42 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PostGroupByOutputType' + type: object + required: + - data + properties: + data: + type: array + items: + $ref: '#/components/schemas/PostGroupByOutputType' + description: The Prisma response data serialized with superjson + meta: + $ref: '#/components/schemas/_Meta' + description: The superjson serialization metadata for the "data" field '400': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Invalid request '403': + content: + application/json: + schema: + $ref: '#/components/schemas/_Error' description: Request is forbidden parameters: - name: q in: query required: true + description: Superjson-serialized Prisma query object content: application/json: schema: $ref: '#/components/schemas/PostGroupByArgs' + - name: meta + in: query + description: Superjson serialization metadata for parameter "q" + content: + application/json: + schema: {} diff --git a/packages/plugins/openapi/tests/openapi-restful.test.ts b/packages/plugins/openapi/tests/openapi-restful.test.ts index 50b736bf9..6dc0649d0 100644 --- a/packages/plugins/openapi/tests/openapi-restful.test.ts +++ b/packages/plugins/openapi/tests/openapi-restful.test.ts @@ -263,6 +263,43 @@ model Post { await OpenAPIParser.validate(output); }); + + it('field type coverage', async () => { + const { model, dmmf, modelFile } = await loadZModelAndDmmf(` +plugin openapi { + provider = '${process.cwd()}/dist' +} + +model Foo { + id String @id @default(cuid()) + + string String + int Int + bigInt BigInt + date DateTime + float Float + decimal Decimal + boolean Boolean + bytes Bytes + + @@allow('all', true) +} + `); + + const { name: output } = tmp.fileSync({ postfix: '.yaml' }); + + const options = buildOptions(model, modelFile, output, '3.1.0'); + await generate(model, options, dmmf); + + console.log('OpenAPI specification generated:', output); + + await OpenAPIParser.validate(output); + + const parsed = YAML.parse(fs.readFileSync(output, 'utf-8')); + expect(parsed.openapi).toBe('3.1.0'); + const baseline = YAML.parse(fs.readFileSync(`${__dirname}/baseline/rest-type-coverage.baseline.yaml`, 'utf-8')); + expect(parsed).toMatchObject(baseline); + }); }); function buildOptions(model: Model, modelFile: string, output: string, specVersion = '3.0.0') { diff --git a/packages/plugins/openapi/tests/openapi-rpc.test.ts b/packages/plugins/openapi/tests/openapi-rpc.test.ts index feac55778..8af6bc086 100644 --- a/packages/plugins/openapi/tests/openapi-rpc.test.ts +++ b/packages/plugins/openapi/tests/openapi-rpc.test.ts @@ -339,6 +339,43 @@ model Post { console.log('OpenAPI specification generated:', output); await OpenAPIParser.validate(output); }); + + it('field type coverage', async () => { + const { model, dmmf, modelFile } = await loadZModelAndDmmf(` +plugin openapi { + provider = '${process.cwd()}/dist' +} + +model Foo { + id String @id @default(cuid()) + + string String + int Int + bigInt BigInt + date DateTime + float Float + decimal Decimal + boolean Boolean + bytes Bytes + + @@allow('all', true) +} + `); + + const { name: output } = tmp.fileSync({ postfix: '.yaml' }); + + const options = buildOptions(model, modelFile, output); + await generate(model, options, dmmf); + + console.log('OpenAPI specification generated:', output); + + await OpenAPIParser.validate(output); + + const parsed = YAML.parse(fs.readFileSync(output, 'utf-8')); + expect(parsed.openapi).toBe('3.1.0'); + const baseline = YAML.parse(fs.readFileSync(`${__dirname}/baseline/rpc-type-coverage.baseline.yaml`, 'utf-8')); + expect(parsed).toMatchObject(baseline); + }); }); function buildOptions(model: Model, modelFile: string, output: string) { diff --git a/packages/plugins/swr/package.json b/packages/plugins/swr/package.json index 711ecb694..2f96ebfdd 100644 --- a/packages/plugins/swr/package.json +++ b/packages/plugins/swr/package.json @@ -10,10 +10,10 @@ }, "scripts": { "clean": "rimraf dist", - "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE 'res/**/*' dist && pnpm pack dist --pack-destination '../../../../.build'", + "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE dist && pnpm pack dist --pack-destination '../../../../.build'", "watch": "tsc --watch", "lint": "eslint src --ext ts", - "test": "jest", + "test": "ZENSTACK_TEST=1 jest", "prepublishOnly": "pnpm build", "publish-dev": "pnpm publish --tag dev" }, @@ -26,11 +26,11 @@ "license": "MIT", "dependencies": { "@prisma/generator-helper": "4.10.0", + "@zenstackhq/runtime": "workspace:*", "@zenstackhq/sdk": "workspace:*", "change-case": "^4.1.2", "decimal.js": "^10.4.2", "lower-case-first": "^2.0.2", - "superjson": "^1.11.0", "ts-morph": "^16.0.0", "upper-case-first": "^2.0.2" }, @@ -38,14 +38,13 @@ "@tanstack/react-query": "^4.28.0", "@types/jest": "^29.5.0", "@types/lower-case-first": "^1.0.1", - "@types/react": "^18.0.26", + "@types/react": "18.2.0", "@types/tmp": "^0.2.3", "@types/upper-case-first": "^1.1.2", "@zenstackhq/testtools": "workspace:*", "copyfiles": "^2.4.1", "jest": "^29.5.0", - "react": "^17.0.2 || ^18", - "react-dom": "^17.0.2 || ^18", + "react": "18.2.0", "rimraf": "^3.0.2", "swr": "^2.0.3", "ts-jest": "^29.0.5", diff --git a/packages/plugins/swr/res/marshal-json.ts b/packages/plugins/swr/res/marshal-json.ts deleted file mode 100644 index 1f00abc79..000000000 --- a/packages/plugins/swr/res/marshal-json.ts +++ /dev/null @@ -1,12 +0,0 @@ -function marshal(value: unknown) { - return JSON.stringify(value); -} - -function unmarshal(value: string) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return JSON.parse(value) as any; -} - -function makeUrl(url: string, args: unknown) { - return args ? url + `?q=${encodeURIComponent(JSON.stringify(args))}` : url; -} diff --git a/packages/plugins/swr/res/marshal-superjson.ts b/packages/plugins/swr/res/marshal-superjson.ts deleted file mode 100644 index 559b11b4e..000000000 --- a/packages/plugins/swr/res/marshal-superjson.ts +++ /dev/null @@ -1,20 +0,0 @@ -import superjson from 'superjson'; - -function marshal(value: unknown) { - return superjson.stringify(value); -} - -function unmarshal(value: string) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const j = JSON.parse(value) as any; - if (j?.json) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return superjson.parse(value); - } else { - return j; - } -} - -function makeUrl(url: string, args: unknown) { - return args ? url + `?q=${encodeURIComponent(superjson.stringify(args))}` : url; -} diff --git a/packages/plugins/swr/src/generator.ts b/packages/plugins/swr/src/generator.ts index f9b9f1da2..3a8f08f4e 100644 --- a/packages/plugins/swr/src/generator.ts +++ b/packages/plugins/swr/src/generator.ts @@ -1,6 +1,5 @@ import { DMMF } from '@prisma/generator-helper'; import { - CrudFailureReason, PluginOptions, createProject, getDataModels, @@ -11,7 +10,6 @@ import { } from '@zenstackhq/sdk'; import { DataModel, Model } from '@zenstackhq/sdk/ast'; import { paramCase } from 'change-case'; -import fs from 'fs'; import { lowerCaseFirst } from 'lower-case-first'; import path from 'path'; import { FunctionDeclaration, Project, SourceFile } from 'ts-morph'; @@ -23,10 +21,16 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF. const project = createProject(); const warnings: string[] = []; + + if (options.useSuperJson !== undefined) { + warnings.push( + 'The option "useSuperJson" is deprecated. The generated hooks always use superjson for serialization.' + ); + } + const models = getDataModels(model); generateIndex(project, outDir, models); - generateHelper(project, outDir, options.useSuperJson === true); models.forEach((dataModel) => { const mapping = dmmf.mappings.modelOperations.find((op) => op.model === dataModel.name); @@ -41,19 +45,6 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF. return warnings; } -function wrapReadbackErrorCheck(code: string) { - return `try { - ${code} - } catch (err: any) { - if (err.info?.prisma && err.info?.code === 'P2004' && err.info?.reason === '${CrudFailureReason.RESULT_NOT_READABLE}') { - // unable to readback data - return undefined; - } else { - throw err; - } - }`; -} - function generateModelHooks(project: Project, outDir: string, model: DataModel, mapping: DMMF.ModelMapping) { const fileName = paramCase(model.name); const sf = project.createSourceFile(path.join(outDir, `${fileName}.ts`), undefined, { overwrite: true }); @@ -68,8 +59,8 @@ function generateModelHooks(project: Project, outDir: string, model: DataModel, }); sf.addStatements([ `import { useContext } from 'react';`, - `import { RequestHandlerContext, type RequestOptions } from './_helper';`, - `import * as request from './_helper';`, + `import { RequestHandlerContext, type RequestOptions } from '@zenstackhq/swr/runtime';`, + `import * as request from '@zenstackhq/swr/runtime';`, ]); const prefixesToMutate = ['find', 'aggregate', 'count', 'groupBy']; @@ -92,7 +83,9 @@ function generateModelHooks(project: Project, outDir: string, model: DataModel, const argsType = `Prisma.${model.name}CreateArgs`; const inputType = `Prisma.SelectSubset`; const returnType = `Prisma.CheckSelect>`; - mutationFuncs.push(generateMutation(useMutation, model, 'post', 'create', argsType, inputType, returnType)); + mutationFuncs.push( + generateMutation(useMutation, model, 'post', 'create', argsType, inputType, returnType, true) + ); } // createMany @@ -100,7 +93,9 @@ function generateModelHooks(project: Project, outDir: string, model: DataModel, const argsType = `Prisma.${model.name}CreateManyArgs`; const inputType = `Prisma.SelectSubset`; const returnType = `Prisma.BatchPayload`; - mutationFuncs.push(generateMutation(useMutation, model, 'post', 'createMany', argsType, inputType, returnType)); + mutationFuncs.push( + generateMutation(useMutation, model, 'post', 'createMany', argsType, inputType, returnType, false) + ); } // findMany @@ -134,7 +129,9 @@ function generateModelHooks(project: Project, outDir: string, model: DataModel, const argsType = `Prisma.${model.name}UpdateArgs`; const inputType = `Prisma.SelectSubset`; const returnType = `Prisma.${model.name}GetPayload`; - mutationFuncs.push(generateMutation(useMutation, model, 'put', 'update', argsType, inputType, returnType)); + mutationFuncs.push( + generateMutation(useMutation, model, 'put', 'update', argsType, inputType, returnType, true) + ); } // updateMany @@ -142,7 +139,9 @@ function generateModelHooks(project: Project, outDir: string, model: DataModel, const argsType = `Prisma.${model.name}UpdateManyArgs`; const inputType = `Prisma.SelectSubset`; const returnType = `Prisma.BatchPayload`; - mutationFuncs.push(generateMutation(useMutation, model, 'put', 'updateMany', argsType, inputType, returnType)); + mutationFuncs.push( + generateMutation(useMutation, model, 'put', 'updateMany', argsType, inputType, returnType, false) + ); } // upsert @@ -152,7 +151,9 @@ function generateModelHooks(project: Project, outDir: string, model: DataModel, const argsType = `Prisma.${model.name}UpsertArgs`; const inputType = `Prisma.SelectSubset`; const returnType = `Prisma.${model.name}GetPayload`; - mutationFuncs.push(generateMutation(useMutation, model, 'post', 'upsert', argsType, inputType, returnType)); + mutationFuncs.push( + generateMutation(useMutation, model, 'post', 'upsert', argsType, inputType, returnType, true) + ); } // del @@ -162,7 +163,9 @@ function generateModelHooks(project: Project, outDir: string, model: DataModel, const argsType = `Prisma.${model.name}DeleteArgs`; const inputType = `Prisma.SelectSubset`; const returnType = `Prisma.${model.name}GetPayload`; - mutationFuncs.push(generateMutation(useMutation, model, 'delete', 'delete', argsType, inputType, returnType)); + mutationFuncs.push( + generateMutation(useMutation, model, 'delete', 'delete', argsType, inputType, returnType, true) + ); } // deleteMany @@ -171,7 +174,7 @@ function generateModelHooks(project: Project, outDir: string, model: DataModel, const inputType = `Prisma.SelectSubset`; const returnType = `Prisma.BatchPayload`; mutationFuncs.push( - generateMutation(useMutation, model, 'delete', 'deleteMany', argsType, inputType, returnType) + generateMutation(useMutation, model, 'delete', 'deleteMany', argsType, inputType, returnType, false) ); } @@ -266,7 +269,6 @@ function generateModelHooks(project: Project, outDir: string, model: DataModel, function generateIndex(project: Project, outDir: string, models: DataModel[]) { const sf = project.createSourceFile(path.join(outDir, 'index.ts'), undefined, { overwrite: true }); sf.addStatements(models.map((d) => `export * from './${paramCase(d.name)}';`)); - sf.addStatements(`export * from './_helper';`); } function generateQueryHook( @@ -308,7 +310,8 @@ function generateMutation( operation: string, argsType: string, inputType: string, - returnType: string + returnType: string, + checkReadBack: boolean ) { const modelRouteName = lowerCaseFirst(model.name); const funcName = `${operation}${model.name}`; @@ -326,20 +329,7 @@ function generateMutation( }) .addBody() .addStatements([ - wrapReadbackErrorCheck( - `return await request.${fetcherFunc}<${returnType}>(\`\${endpoint}/${modelRouteName}/${operation}\`, args, mutate, fetch);` - ), + `return await request.${fetcherFunc}<${returnType}, ${checkReadBack}>(\`\${endpoint}/${modelRouteName}/${operation}\`, args, mutate, fetch, ${checkReadBack});`, ]); return funcName; } - -function generateHelper(project: Project, outDir: string, useSuperJson: boolean) { - const helperContent = fs.readFileSync(path.join(__dirname, './res/helper.ts'), 'utf-8'); - const marshalContent = fs.readFileSync( - path.join(__dirname, useSuperJson ? './res/marshal-superjson.ts' : './res/marshal-json.ts'), - 'utf-8' - ); - project.createSourceFile(path.join(outDir, '_helper.ts'), `${helperContent}\n${marshalContent}`, { - overwrite: true, - }); -} diff --git a/packages/plugins/swr/res/helper.ts b/packages/plugins/swr/src/runtime/index.ts similarity index 59% rename from packages/plugins/swr/res/helper.ts rename to packages/plugins/swr/src/runtime/index.ts index 8161c85e0..cad3a7b4f 100644 --- a/packages/plugins/swr/res/helper.ts +++ b/packages/plugins/swr/src/runtime/index.ts @@ -1,5 +1,5 @@ -/* eslint-disable */ - +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { deserialize, serialize } from '@zenstackhq/runtime/browser'; import { createContext } from 'react'; import type { MutatorCallback, MutatorOptions, SWRResponse } from 'swr'; import useSWR, { useSWRConfig } from 'swr'; @@ -61,7 +61,7 @@ export function get( fetch?: FetchFn ): SWRResponse { const reqUrl = options?.disabled ? null : url ? makeUrl(url, args) : null; - return useSWR(reqUrl, (url) => fetcher(url, undefined, fetch), { + return useSWR(reqUrl, (url) => fetcher(url, undefined, fetch, false), { fallbackData: options?.initialData, }); } @@ -73,8 +73,14 @@ export function get( * @param data The request data. * @param mutate Mutator for invalidating cache. */ -export async function post(url: string, data: unknown, mutate: Mutator, fetch?: FetchFn): Promise { - const r: Result = await fetcher( +export async function post( + url: string, + data: unknown, + mutate: Mutator, + fetch?: FetchFn, + checkReadBack?: C +): Promise { + const r = await fetcher( url, { method: 'POST', @@ -83,7 +89,8 @@ export async function post(url: string, data: unknown, mutate: Mutator, }, body: marshal(data), }, - fetch + fetch, + checkReadBack ); mutate(); return r; @@ -96,8 +103,14 @@ export async function post(url: string, data: unknown, mutate: Mutator, * @param data The request data. * @param mutate Mutator for invalidating cache. */ -export async function put(url: string, data: unknown, mutate: Mutator, fetch?: FetchFn): Promise { - const r: Result = await fetcher( +export async function put( + url: string, + data: unknown, + mutate: Mutator, + fetch?: FetchFn, + checkReadBack?: C +): Promise { + const r = await fetcher( url, { method: 'PUT', @@ -106,7 +119,8 @@ export async function put(url: string, data: unknown, mutate: Mutator, f }, body: marshal(data), }, - fetch + fetch, + checkReadBack ); mutate(); return r; @@ -119,14 +133,21 @@ export async function put(url: string, data: unknown, mutate: Mutator, f * @param args The request args object, which will be superjson-stringified and appended as "?q=" parameter * @param mutate Mutator for invalidating cache. */ -export async function del(url: string, args: unknown, mutate: Mutator, fetch?: FetchFn): Promise { +export async function del( + url: string, + args: unknown, + mutate: Mutator, + fetch?: FetchFn, + checkReadBack?: C +): Promise { const reqUrl = makeUrl(url, args); - const r: Result = await fetcher( + const r = await fetcher( reqUrl, { method: 'DELETE', }, - fetch + fetch, + checkReadBack ); const path = url.split('/'); path.pop(); @@ -155,23 +176,70 @@ export function getMutate(prefixes: string[]): Mutator { }; } -export async function fetcher(url: string, options?: RequestInit, fetch?: FetchFn) { +export async function fetcher( + url: string, + options?: RequestInit, + fetch?: FetchFn, + checkReadBack?: C +): Promise { const _fetch = fetch ?? window.fetch; const res = await _fetch(url, options); if (!res.ok) { + const errData = unmarshal(await res.text()); + if ( + checkReadBack !== false && + errData.error?.prisma && + errData.error?.code === 'P2004' && + errData.error?.reason === 'RESULT_NOT_READABLE' + ) { + // policy doesn't allow mutation result to be read back, just return undefined + return undefined as any; + } const error: Error & { info?: unknown; status?: number } = new Error( 'An error occurred while fetching the data.' ); - error.info = unmarshal(await res.text()); + error.info = errData.error; error.status = res.status; throw error; } const textResult = await res.text(); try { - return unmarshal(textResult) as R; + return unmarshal(textResult).data as R; } catch (err) { console.error(`Unable to deserialize data:`, textResult); throw err; } } + +function marshal(value: unknown) { + const { data, meta } = serialize(value); + if (meta) { + return JSON.stringify({ ...(data as any), meta: { serialization: meta } }); + } else { + return JSON.stringify(data); + } +} + +function unmarshal(value: string) { + const parsed = JSON.parse(value); + if (parsed.data && parsed.meta?.serialization) { + const deserializedData = deserialize(parsed.data, parsed.meta.serialization); + return { ...parsed, data: deserializedData }; + } else { + return parsed; + } +} + +function makeUrl(url: string, args: unknown) { + if (!args) { + return url; + } + + const { data, meta } = serialize(args); + let result = `${url}?q=${encodeURIComponent(JSON.stringify(data))}`; + if (meta) { + result += `&meta=${encodeURIComponent(JSON.stringify({ serialization: meta }))}`; + } + return result; +} diff --git a/packages/plugins/swr/tests/swr.test.ts b/packages/plugins/swr/tests/swr.test.ts index 9b68dedfe..0ae8f4f10 100644 --- a/packages/plugins/swr/tests/swr.test.ts +++ b/packages/plugins/swr/tests/swr.test.ts @@ -40,7 +40,7 @@ model Foo { } `; - it('swr generator regular json', async () => { + it('run plugin', async () => { await loadSchema( ` plugin swr { @@ -56,22 +56,4 @@ ${sharedModel} true ); }); - - it('swr generator superjson', async () => { - await loadSchema( - ` -plugin swr { - provider = '${process.cwd()}/dist' - output = '$projectRoot/hooks' - useSuperJson = true -} - -${sharedModel} - `, - true, - false, - [`${origDir}/dist`, 'react', '@types/react', 'swr', 'superjson'], - true - ); - }); }); diff --git a/packages/plugins/tanstack-query/package.json b/packages/plugins/tanstack-query/package.json index 84ab46573..eb3d416f8 100644 --- a/packages/plugins/tanstack-query/package.json +++ b/packages/plugins/tanstack-query/package.json @@ -4,16 +4,33 @@ "version": "1.0.0-beta.8", "description": "ZenStack plugin for generating tanstack-query hooks", "main": "index.js", + "exports": { + ".": { + "default": "./index.js" + }, + "./runtime/react": { + "import": "./runtime/react.mjs", + "require": "./runtime/react.js", + "default": "./runtime/react.js", + "types": "./runtime/react.d.ts" + }, + "./runtime/svelte": { + "import": "./runtime/svelte.mjs", + "require": "./runtime/svelte.js", + "default": "./runtime/svelte.js", + "types": "./runtime/svelte.d.ts" + } + }, "repository": { "type": "git", "url": "https://github.com/zenstackhq/zenstack" }, "scripts": { "clean": "rimraf dist", - "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE 'res/**/*' dist && pnpm pack dist --pack-destination '../../../../.build'", - "watch": "tsc --watch", + "build": "pnpm lint && pnpm clean && tsc && tsup-node && copyfiles ./package.json ./README.md ./LICENSE dist && pnpm pack dist --pack-destination '../../../../.build'", + "watch": "concurrently \"tsc --watch\" \"tsup-node --watch\"", "lint": "eslint src --ext ts", - "test": "jest", + "test": "ZENSTACK_TEST=1 jest", "prepublishOnly": "pnpm build", "publish-dev": "pnpm publish --tag dev" }, @@ -26,6 +43,7 @@ "license": "MIT", "dependencies": { "@prisma/generator-helper": "4.10.0", + "@zenstackhq/runtime": "workspace:*", "@zenstackhq/sdk": "workspace:*", "change-case": "^4.1.2", "decimal.js": "^10.4.2", @@ -35,18 +53,17 @@ "upper-case-first": "^2.0.2" }, "devDependencies": { - "@tanstack/react-query": "^4.29.7", - "@tanstack/svelte-query": "^4.29.7", + "@tanstack/react-query": "4.29.7", + "@tanstack/svelte-query": "4.29.7", "@types/jest": "^29.5.0", "@types/lower-case-first": "^1.0.1", - "@types/react": "^18.0.26", + "@types/react": "18.2.0", "@types/tmp": "^0.2.3", "@types/upper-case-first": "^1.1.2", "@zenstackhq/testtools": "workspace:*", "copyfiles": "^2.4.1", "jest": "^29.5.0", - "react": "^17.0.2 || ^18", - "react-dom": "^17.0.2 || ^18", + "react": "18.2.0", "rimraf": "^3.0.2", "swr": "^2.0.3", "ts-jest": "^29.0.5", diff --git a/packages/plugins/tanstack-query/res/marshal-json.ts b/packages/plugins/tanstack-query/res/marshal-json.ts deleted file mode 100644 index 1f00abc79..000000000 --- a/packages/plugins/tanstack-query/res/marshal-json.ts +++ /dev/null @@ -1,12 +0,0 @@ -function marshal(value: unknown) { - return JSON.stringify(value); -} - -function unmarshal(value: string) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return JSON.parse(value) as any; -} - -function makeUrl(url: string, args: unknown) { - return args ? url + `?q=${encodeURIComponent(JSON.stringify(args))}` : url; -} diff --git a/packages/plugins/tanstack-query/res/marshal-superjson.ts b/packages/plugins/tanstack-query/res/marshal-superjson.ts deleted file mode 100644 index 559b11b4e..000000000 --- a/packages/plugins/tanstack-query/res/marshal-superjson.ts +++ /dev/null @@ -1,20 +0,0 @@ -import superjson from 'superjson'; - -function marshal(value: unknown) { - return superjson.stringify(value); -} - -function unmarshal(value: string) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const j = JSON.parse(value) as any; - if (j?.json) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return superjson.parse(value); - } else { - return j; - } -} - -function makeUrl(url: string, args: unknown) { - return args ? url + `?q=${encodeURIComponent(superjson.stringify(args))}` : url; -} diff --git a/packages/plugins/tanstack-query/res/shared.ts b/packages/plugins/tanstack-query/res/shared.ts deleted file mode 100644 index 24caff7cb..000000000 --- a/packages/plugins/tanstack-query/res/shared.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * The default query endpoint. - */ -export const DEFAULT_QUERY_ENDPOINT = '/api/model'; - -/** - * Prefix for react-query keys. - */ -export const QUERY_KEY_PREFIX = 'zenstack:'; - -/** - * Function signature for `fetch`. - */ -export type FetchFn = (url: string, options?: RequestInit) => Promise; - -/** - * Context type for configuring the hooks. - */ -export type RequestHandlerContext = { - /** - * The endpoint to use for the queries. - */ - endpoint: string; - - /** - * A custom fetch function for sending the HTTP requests. - */ - fetch?: FetchFn; -}; - -async function fetcher(url: string, options?: RequestInit, fetch?: FetchFn) { - const _fetch = fetch ?? window.fetch; - const res = await _fetch(url, options); - if (!res.ok) { - const error: Error & { info?: unknown; status?: number } = new Error( - 'An error occurred while fetching the data.' - ); - error.info = unmarshal(await res.text()); - error.status = res.status; - throw error; - } - - const textResult = await res.text(); - try { - return unmarshal(textResult) as R; - } catch (err) { - console.error(`Unable to deserialize data:`, textResult); - throw err; - } -} diff --git a/packages/plugins/tanstack-query/src/generator.ts b/packages/plugins/tanstack-query/src/generator.ts index 53ebf0942..9835e3d09 100644 --- a/packages/plugins/tanstack-query/src/generator.ts +++ b/packages/plugins/tanstack-query/src/generator.ts @@ -11,7 +11,6 @@ import { } from '@zenstackhq/sdk'; import { DataModel, Model } from '@zenstackhq/sdk/ast'; import { paramCase } from 'change-case'; -import fs from 'fs'; import { lowerCaseFirst } from 'lower-case-first'; import path from 'path'; import { Project, SourceFile, VariableDeclarationKind } from 'ts-morph'; @@ -38,7 +37,6 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF. } generateIndex(project, outDir, models); - generateHelper(target, project, outDir, options.useSuperJson === true); models.forEach((dataModel) => { const mapping = dmmf.mappings.modelOperations.find((op) => op.model === dataModel.name); @@ -102,16 +100,20 @@ function generateMutationHook( model: string, operation: string, httpVerb: 'post' | 'put' | 'delete', + checkReadBack: boolean, overrideReturnType?: string ) { const capOperation = upperCaseFirst(operation); const argsType = `Prisma.${model}${capOperation}Args`; const inputType = `Prisma.SelectSubset`; - const returnType = overrideReturnType ?? `Prisma.CheckSelect>`; + let returnType = overrideReturnType ?? `Prisma.CheckSelect>`; + if (checkReadBack) { + returnType = `(${returnType} | undefined )`; + } const nonGenericOptionsType = `Omit<${makeMutationOptions( target, - overrideReturnType ?? model, + checkReadBack ? `(${overrideReturnType ?? model} | undefined)` : overrideReturnType ?? model, argsType )}, 'mutationFn'>`; const optionsType = `Omit<${makeMutationOptions(target, returnType, inputType)}, 'mutationFn'>`; @@ -143,9 +145,9 @@ function generateMutationHook( initializer: ` ${httpVerb}Mutation<${argsType}, ${ overrideReturnType ?? model - }>('${model}', \`\${endpoint}/${lowerCaseFirst( + }, ${checkReadBack}>('${model}', \`\${endpoint}/${lowerCaseFirst( model - )}/${operation}\`, options, fetch, invalidateQueries) + )}/${operation}\`, options, fetch, invalidateQueries, ${checkReadBack}) `, }, ], @@ -232,12 +234,12 @@ function generateModelHooks( // create is somehow named "createOne" in the DMMF // eslint-disable-next-line @typescript-eslint/no-explicit-any if (mapping.create || (mapping as any).createOne) { - generateMutationHook(target, sf, model.name, 'create', 'post'); + generateMutationHook(target, sf, model.name, 'create', 'post', true); } // createMany if (mapping.createMany) { - generateMutationHook(target, sf, model.name, 'createMany', 'post', 'Prisma.BatchPayload'); + generateMutationHook(target, sf, model.name, 'createMany', 'post', false, 'Prisma.BatchPayload'); } // findMany @@ -259,31 +261,31 @@ function generateModelHooks( // update is somehow named "updateOne" in the DMMF // eslint-disable-next-line @typescript-eslint/no-explicit-any if (mapping.update || (mapping as any).updateOne) { - generateMutationHook(target, sf, model.name, 'update', 'put'); + generateMutationHook(target, sf, model.name, 'update', 'put', true); } // updateMany if (mapping.updateMany) { - generateMutationHook(target, sf, model.name, 'updateMany', 'put', 'Prisma.BatchPayload'); + generateMutationHook(target, sf, model.name, 'updateMany', 'put', false, 'Prisma.BatchPayload'); } // upsert // upsert is somehow named "upsertOne" in the DMMF // eslint-disable-next-line @typescript-eslint/no-explicit-any if (mapping.upsert || (mapping as any).upsertOne) { - generateMutationHook(target, sf, model.name, 'upsert', 'post'); + generateMutationHook(target, sf, model.name, 'upsert', 'post', true); } // del // delete is somehow named "deleteOne" in the DMMF // eslint-disable-next-line @typescript-eslint/no-explicit-any if (mapping.delete || (mapping as any).deleteOne) { - generateMutationHook(target, sf, model.name, 'delete', 'delete'); + generateMutationHook(target, sf, model.name, 'delete', 'delete', true); } // deleteMany if (mapping.deleteMany) { - generateMutationHook(target, sf, model.name, 'deleteMany', 'delete', 'Prisma.BatchPayload'); + generateMutationHook(target, sf, model.name, 'deleteMany', 'delete', false, 'Prisma.BatchPayload'); } // aggregate @@ -388,33 +390,6 @@ function generateModelHooks( function generateIndex(project: Project, outDir: string, models: DataModel[]) { const sf = project.createSourceFile(path.join(outDir, 'index.ts'), undefined, { overwrite: true }); sf.addStatements(models.map((d) => `export * from './${paramCase(d.name)}';`)); - sf.addStatements(`export * from './_helper';`); -} - -function generateHelper(target: TargetFramework, project: Project, outDir: string, useSuperJson: boolean) { - let srcFile: string; - switch (target) { - case 'react': - srcFile = path.join(__dirname, './res/react/helper.ts'); - break; - case 'svelte': - srcFile = path.join(__dirname, './res/svelte/helper.ts'); - break; - default: - throw new PluginError(name, `Unsupported target: ${target}`); - } - - // merge content of `shared.ts`, `helper.ts` and `marshal-?.ts` - const sharedContent = fs.readFileSync(path.join(__dirname, './res/shared.ts'), 'utf-8'); - const helperContent = fs.readFileSync(srcFile, 'utf-8'); - const marshalContent = fs.readFileSync( - path.join(__dirname, useSuperJson ? './res/marshal-superjson.ts' : './res/marshal-json.ts'), - 'utf-8' - ); - - project.createSourceFile(path.join(outDir, '_helper.ts'), `${sharedContent}\n${helperContent}\n${marshalContent}`, { - overwrite: true, - }); } function makeGetContext(target: TargetFramework) { @@ -429,13 +404,15 @@ function makeGetContext(target: TargetFramework) { } function makeBaseImports(target: TargetFramework) { - const shared = [`import { query, postMutation, putMutation, deleteMutation } from './_helper';`]; + const shared = [ + `import { query, postMutation, putMutation, deleteMutation } from '@zenstackhq/tanstack-query/runtime/${target}';`, + ]; switch (target) { case 'react': return [ `import { useContext } from 'react';`, `import type { UseMutationOptions, UseQueryOptions } from '@tanstack/react-query';`, - `import { RequestHandlerContext } from './_helper';`, + `import { RequestHandlerContext } from '@zenstackhq/tanstack-query/runtime/${target}';`, ...shared, ]; case 'svelte': @@ -443,7 +420,7 @@ function makeBaseImports(target: TargetFramework) { `import { getContext } from 'svelte';`, `import { derived } from 'svelte/store';`, `import type { MutationOptions, QueryOptions } from '@tanstack/svelte-query';`, - `import { SvelteQueryContextKey, type RequestHandlerContext } from './_helper';`, + `import { SvelteQueryContextKey, type RequestHandlerContext } from '@zenstackhq/tanstack-query/runtime/${target}';`, ...shared, ]; default: diff --git a/packages/plugins/tanstack-query/src/runtime/common.ts b/packages/plugins/tanstack-query/src/runtime/common.ts new file mode 100644 index 000000000..f341883a2 --- /dev/null +++ b/packages/plugins/tanstack-query/src/runtime/common.ts @@ -0,0 +1,100 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { serialize, deserialize } from '@zenstackhq/runtime/browser'; + +/** + * The default query endpoint. + */ +export const DEFAULT_QUERY_ENDPOINT = '/api/model'; + +/** + * Prefix for react-query keys. + */ +export const QUERY_KEY_PREFIX = 'zenstack:'; + +/** + * Function signature for `fetch`. + */ +export type FetchFn = (url: string, options?: RequestInit) => Promise; + +/** + * Context type for configuring the hooks. + */ +export type APIContext = { + /** + * The endpoint to use for the queries. + */ + endpoint: string; + + /** + * A custom fetch function for sending the HTTP requests. + */ + fetch?: FetchFn; +}; + +export async function fetcher( + url: string, + options?: RequestInit, + fetch?: FetchFn, + checkReadBack?: C +): Promise { + const _fetch = fetch ?? window.fetch; + const res = await _fetch(url, options); + if (!res.ok) { + const errData = unmarshal(await res.text()); + if ( + checkReadBack !== false && + errData.error?.prisma && + errData.error?.code === 'P2004' && + errData.error?.reason === 'RESULT_NOT_READABLE' + ) { + // policy doesn't allow mutation result to be read back, just return undefined + return undefined as any; + } + const error: Error & { info?: unknown; status?: number } = new Error( + 'An error occurred while fetching the data.' + ); + error.info = errData.error; + error.status = res.status; + throw error; + } + + const textResult = await res.text(); + try { + return unmarshal(textResult).data as R; + } catch (err) { + console.error(`Unable to deserialize data:`, textResult); + throw err; + } +} + +export function marshal(value: unknown) { + const { data, meta } = serialize(value); + if (meta) { + return JSON.stringify({ ...(data as any), meta: { serialization: meta } }); + } else { + return JSON.stringify(data); + } +} + +export function unmarshal(value: string) { + const parsed = JSON.parse(value); + if (parsed.data && parsed.meta?.serialization) { + const deserializedData = deserialize(parsed.data, parsed.meta.serialization); + return { ...parsed, data: deserializedData }; + } else { + return parsed; + } +} + +export function makeUrl(url: string, args: unknown) { + if (!args) { + return url; + } + + const { data, meta } = serialize(args); + let result = `${url}?q=${encodeURIComponent(JSON.stringify(data))}`; + if (meta) { + result += `&meta=${encodeURIComponent(JSON.stringify({ serialization: meta }))}`; + } + return result; +} diff --git a/packages/plugins/tanstack-query/res/react/helper.ts b/packages/plugins/tanstack-query/src/runtime/react.ts similarity index 65% rename from packages/plugins/tanstack-query/res/react/helper.ts rename to packages/plugins/tanstack-query/src/runtime/react.ts index 5ead00257..cdbbe1b68 100644 --- a/packages/plugins/tanstack-query/res/react/helper.ts +++ b/packages/plugins/tanstack-query/src/runtime/react.ts @@ -1,5 +1,4 @@ -/* eslint-disable */ - +/* eslint-disable @typescript-eslint/no-explicit-any */ import { useMutation, useQuery, @@ -10,11 +9,20 @@ import { type UseQueryOptions, } from '@tanstack/react-query'; import { createContext } from 'react'; +import { + DEFAULT_QUERY_ENDPOINT, + FetchFn, + QUERY_KEY_PREFIX, + fetcher, + makeUrl, + marshal, + type APIContext, +} from './common'; /** * Context for configuring react hooks. */ -export const RequestHandlerContext = createContext({ +export const RequestHandlerContext = createContext({ endpoint: DEFAULT_QUERY_ENDPOINT, fetch: undefined, }); @@ -38,7 +46,7 @@ export function query(model: string, url: string, args?: unknown, options?: U const reqUrl = makeUrl(url, args); return useQuery({ queryKey: [QUERY_KEY_PREFIX + model, url, args], - queryFn: () => fetcher(reqUrl, undefined, fetch), + queryFn: () => fetcher(reqUrl, undefined, fetch, false), ...options, }); } @@ -52,16 +60,17 @@ export function query(model: string, url: string, args?: unknown, options?: U * @param invalidateQueries Whether to invalidate queries after mutation. * @returns useMutation hooks */ -export function postMutation( +export function postMutation( model: string, url: string, - options?: Omit, 'mutationFn'>, + options?: Omit, 'mutationFn'>, fetch?: FetchFn, - invalidateQueries = true + invalidateQueries = true, + checkReadBack?: C ) { const queryClient = useQueryClient(); const mutationFn = (data: any) => - fetcher( + fetcher( url, { method: 'POST', @@ -70,11 +79,12 @@ export function postMutation( }, body: marshal(data), }, - fetch - ); + fetch, + checkReadBack + ) as Promise; - const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = useMutation(finalOptions); + const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); + const mutation = useMutation(finalOptions); return mutation; } @@ -87,16 +97,17 @@ export function postMutation( * @param invalidateQueries Whether to invalidate queries after mutation. * @returns useMutation hooks */ -export function putMutation( +export function putMutation( model: string, url: string, - options?: Omit, 'mutationFn'>, + options?: Omit, 'mutationFn'>, fetch?: FetchFn, - invalidateQueries = true + invalidateQueries = true, + checkReadBack?: C ) { const queryClient = useQueryClient(); const mutationFn = (data: any) => - fetcher( + fetcher( url, { method: 'PUT', @@ -105,11 +116,12 @@ export function putMutation( }, body: marshal(data), }, - fetch - ); + fetch, + checkReadBack + ) as Promise; - const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = useMutation(finalOptions); + const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); + const mutation = useMutation(finalOptions); return mutation; } @@ -122,25 +134,27 @@ export function putMutation( * @param invalidateQueries Whether to invalidate queries after mutation. * @returns useMutation hooks */ -export function deleteMutation( +export function deleteMutation( model: string, url: string, - options?: Omit, 'mutationFn'>, + options?: Omit, 'mutationFn'>, fetch?: FetchFn, - invalidateQueries = true + invalidateQueries = true, + checkReadBack?: C ) { const queryClient = useQueryClient(); const mutationFn = (data: any) => - fetcher( + fetcher( makeUrl(url, data), { method: 'DELETE', }, - fetch - ); + fetch, + checkReadBack + ) as Promise; - const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = useMutation(finalOptions); + const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); + const mutation = useMutation(finalOptions); return mutation; } diff --git a/packages/plugins/tanstack-query/res/svelte/helper.ts b/packages/plugins/tanstack-query/src/runtime/svelte.ts similarity index 65% rename from packages/plugins/tanstack-query/res/svelte/helper.ts rename to packages/plugins/tanstack-query/src/runtime/svelte.ts index d40d8cee5..7edab8edc 100644 --- a/packages/plugins/tanstack-query/res/svelte/helper.ts +++ b/packages/plugins/tanstack-query/src/runtime/svelte.ts @@ -1,5 +1,4 @@ -/* eslint-disable */ - +/* eslint-disable @typescript-eslint/no-explicit-any */ import { createMutation, createQuery, @@ -9,6 +8,9 @@ import { type QueryClient, type QueryOptions, } from '@tanstack/svelte-query'; +import { FetchFn, QUERY_KEY_PREFIX, fetcher, makeUrl, marshal } from './common'; + +export { APIContext as RequestHandlerContext } from './common'; /** * Key for setting and getting the global query context. @@ -29,7 +31,7 @@ export function query(model: string, url: string, args?: unknown, options?: Q const reqUrl = makeUrl(url, args); return createQuery({ queryKey: [QUERY_KEY_PREFIX + model, url, args], - queryFn: () => fetcher(reqUrl, undefined, fetch), + queryFn: () => fetcher(reqUrl, undefined, fetch, false), ...options, }); } @@ -43,16 +45,17 @@ export function query(model: string, url: string, args?: unknown, options?: Q * @param invalidateQueries Whether to invalidate queries after mutation. * @returns useMutation hooks */ -export function postMutation( +export function postMutation( model: string, url: string, - options?: Omit, 'mutationFn'>, + options?: Omit, 'mutationFn'>, fetch?: FetchFn, - invalidateQueries = true + invalidateQueries = true, + checkReadBack?: C ) { const queryClient = useQueryClient(); const mutationFn = (data: any) => - fetcher( + fetcher( url, { method: 'POST', @@ -61,11 +64,12 @@ export function postMutation( }, body: marshal(data), }, - fetch - ); + fetch, + checkReadBack + ) as Promise; - const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = createMutation(finalOptions); + const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); + const mutation = createMutation(finalOptions); return mutation; } @@ -78,16 +82,17 @@ export function postMutation( * @param invalidateQueries Whether to invalidate queries after mutation. * @returns useMutation hooks */ -export function putMutation( +export function putMutation( model: string, url: string, - options?: Omit, 'mutationFn'>, + options?: Omit, 'mutationFn'>, fetch?: FetchFn, - invalidateQueries = true + invalidateQueries = true, + checkReadBack?: C ) { const queryClient = useQueryClient(); const mutationFn = (data: any) => - fetcher( + fetcher( url, { method: 'PUT', @@ -96,11 +101,12 @@ export function putMutation( }, body: marshal(data), }, - fetch - ); + fetch, + checkReadBack + ) as Promise; - const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = createMutation(finalOptions); + const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); + const mutation = createMutation(finalOptions); return mutation; } @@ -113,25 +119,27 @@ export function putMutation( * @param invalidateQueries Whether to invalidate queries after mutation. * @returns useMutation hooks */ -export function deleteMutation( +export function deleteMutation( model: string, url: string, - options?: Omit, 'mutationFn'>, + options?: Omit, 'mutationFn'>, fetch?: FetchFn, - invalidateQueries = true + invalidateQueries = true, + checkReadBack?: C ) { const queryClient = useQueryClient(); const mutationFn = (data: any) => - fetcher( + fetcher( makeUrl(url, data), { method: 'DELETE', }, - fetch - ); + fetch, + checkReadBack + ) as Promise; - const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = createMutation(finalOptions); + const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); + const mutation = createMutation(finalOptions); return mutation; } diff --git a/packages/plugins/tanstack-query/tests/plugin.test.ts b/packages/plugins/tanstack-query/tests/plugin.test.ts index fd2f300fa..03798ea08 100644 --- a/packages/plugins/tanstack-query/tests/plugin.test.ts +++ b/packages/plugins/tanstack-query/tests/plugin.test.ts @@ -40,7 +40,7 @@ model Foo { } `; - it('react-query generator regular json', async () => { + it('react-query run plugin', async () => { await loadSchema( ` plugin tanstack { @@ -53,63 +53,25 @@ ${sharedModel} `, true, false, - [`${origDir}/dist`, 'react', '@types/react', '@tanstack/react-query'], + [`${origDir}/dist`, 'react@18.2.0', '@types/react@18.2.0', '@tanstack/react-query@4.29.7'], true ); }); - it('react-query generator superjson', async () => { - await loadSchema( - ` -plugin tanstack { - provider = '${process.cwd()}/dist' - output = '$projectRoot/hooks' - target = 'react' - useSuperJson = true -} - -${sharedModel} - `, - true, - false, - [`${origDir}/dist`, 'react', '@types/react', '@tanstack/react-query', 'superjson'], - true - ); - }); - - it('svelte-query generator regular json', async () => { - await loadSchema( - ` -plugin tanstack { - provider = '${process.cwd()}/dist' - output = '$projectRoot/hooks' - target = 'svelte' -} - -${sharedModel} - `, - true, - false, - [`${origDir}/dist`, 'svelte@^3.0.0', '@types/react', '@tanstack/svelte-query'], - true - ); - }); - - it('svelte-query generator superjson', async () => { + it('svelte-query run plugin', async () => { await loadSchema( ` plugin tanstack { provider = '${process.cwd()}/dist' output = '$projectRoot/hooks' target = 'svelte' - useSuperJson = true } ${sharedModel} `, true, false, - [`${origDir}/dist`, 'svelte@^3.0.0', '@types/react', '@tanstack/svelte-query', 'superjson'], + [`${origDir}/dist`, 'svelte@^3.0.0', '@tanstack/svelte-query@4.29.7'], true ); }); diff --git a/packages/plugins/tanstack-query/tsconfig.json b/packages/plugins/tanstack-query/tsconfig.json index e2c5bd0f3..d27927629 100644 --- a/packages/plugins/tanstack-query/tsconfig.json +++ b/packages/plugins/tanstack-query/tsconfig.json @@ -17,5 +17,6 @@ "strictPropertyInitialization": false, "paths": {} }, - "include": ["src/**/*.ts"] + "include": ["src/**/*.ts"], + "exclude": ["src/runtime"] } diff --git a/packages/plugins/tanstack-query/tsup.config.ts b/packages/plugins/tanstack-query/tsup.config.ts new file mode 100644 index 000000000..012a11c95 --- /dev/null +++ b/packages/plugins/tanstack-query/tsup.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/runtime/react.ts', 'src/runtime/svelte.ts'], + outDir: 'dist/runtime', + splitting: false, + sourcemap: true, + clean: true, + dts: true, + format: ['cjs', 'esm'], +}); diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 85e5bb5ea..c1e0ddb1a 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -9,14 +9,34 @@ }, "scripts": { "clean": "rimraf dist", - "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./README.md ../../LICENSE dist && copyfiles -u1 'res/**/*' dist && pnpm pack dist --pack-destination '../../../.build'", - "watch": "tsc --watch", + "build": "pnpm lint && pnpm clean && tsc && tsup-node && copyfiles ./package.json ./README.md ../../LICENSE dist && copyfiles -u1 'res/**/*' dist && pnpm pack dist --pack-destination '../../../.build'", + "watch": "concurrently \"tsc --watch\" \"tsup-node --watch\"", "lint": "eslint src --ext ts", "prepublishOnly": "pnpm build", "publish-dev": "pnpm publish --tag dev" }, "main": "index.js", "types": "index.d.ts", + "exports": { + ".": { + "default": "./index.js" + }, + "./zod": { + "default": "./zod/index.js" + }, + "./zod/input": { + "default": "./zod/input.js" + }, + "./zod/models": { + "default": "./zod/models.js" + }, + "./browser": { + "import": "./browser/index.mjs", + "require": "./browser/index.js", + "default": "./browser/index.js", + "types": "./browser/index.d.ts" + } + }, "publishConfig": { "directory": "dist", "linkDirectory": true @@ -25,6 +45,7 @@ "@paralleldrive/cuid2": "^2.2.0", "@types/bcryptjs": "^2.4.2", "bcryptjs": "^2.4.3", + "buffer": "^6.0.3", "change-case": "^4.1.2", "colors": "1.4.0", "decimal.js": "^10.4.2", diff --git a/packages/runtime/src/browser/index.ts b/packages/runtime/src/browser/index.ts new file mode 100644 index 000000000..90e875c0a --- /dev/null +++ b/packages/runtime/src/browser/index.ts @@ -0,0 +1 @@ +export * from './serialization'; diff --git a/packages/runtime/src/browser/serialization.ts b/packages/runtime/src/browser/serialization.ts new file mode 100644 index 000000000..a0184e0a2 --- /dev/null +++ b/packages/runtime/src/browser/serialization.ts @@ -0,0 +1,36 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import Decimal from 'decimal.js'; +import SuperJSON from 'superjson'; + +SuperJSON.registerCustom( + { + isApplicable: (v): v is Decimal => Decimal.isDecimal(v), + serialize: (v) => v.toJSON(), + deserialize: (v) => new Decimal(v), + }, + 'Decimal' +); + +SuperJSON.registerCustom( + { + isApplicable: (v): v is Buffer => Buffer.isBuffer(v), + serialize: (v) => v.toString('base64'), + deserialize: (v) => Buffer.from(v, 'base64'), + }, + 'Bytes' +); + +/** + * Serialize the given value with superjson + */ +export function serialize(value: unknown): { data: unknown; meta: unknown } { + const { json, meta } = SuperJSON.serialize(value); + return { data: json, meta }; +} + +/** + * Deserialize the given value with superjson using the given metadata + */ +export function deserialize(value: unknown, meta: any): unknown { + return SuperJSON.deserialize({ json: value as any, meta }); +} diff --git a/packages/runtime/src/enhancements/index.ts b/packages/runtime/src/enhancements/index.ts index 4f223bc63..c1ae7c70e 100644 --- a/packages/runtime/src/enhancements/index.ts +++ b/packages/runtime/src/enhancements/index.ts @@ -1,5 +1,9 @@ +export * from './model-meta'; +export * from './nested-write-vistor'; export * from './omit'; export * from './password'; export * from './policy'; export * from './preset'; +export * from './types'; export * from './utils'; +export * from './where-visitor'; diff --git a/packages/runtime/src/enhancements/nested-write-vistor.ts b/packages/runtime/src/enhancements/nested-write-vistor.ts index 5dd13c0a3..5b696bbd0 100644 --- a/packages/runtime/src/enhancements/nested-write-vistor.ts +++ b/packages/runtime/src/enhancements/nested-write-vistor.ts @@ -11,7 +11,7 @@ type NestingPathItem = { field?: FieldInfo; model: string; where: any; unique: b /** * Context for visiting */ -export type VisitorContext = { +export type NestedWriteVisitorContext = { /** * Parent data, can be used to replace fields */ @@ -32,29 +32,42 @@ export type VisitorContext = { * NestedWriteVisitor's callback actions */ export type NestedWriterVisitorCallback = { - create?: (model: string, args: any[], context: VisitorContext) => Promise; + create?: (model: string, args: any[], context: NestedWriteVisitorContext) => Promise; - connectOrCreate?: (model: string, args: { where: object; create: any }, context: VisitorContext) => Promise; + connectOrCreate?: ( + model: string, + args: { where: object; create: any }, + context: NestedWriteVisitorContext + ) => Promise; - connect?: (model: string, args: object, context: VisitorContext) => Promise; + connect?: (model: string, args: object, context: NestedWriteVisitorContext) => Promise; - disconnect?: (model: string, args: object, context: VisitorContext) => Promise; + disconnect?: (model: string, args: object, context: NestedWriteVisitorContext) => Promise; - update?: (model: string, args: { where: object; data: any }, context: VisitorContext) => Promise; + update?: (model: string, args: { where: object; data: any }, context: NestedWriteVisitorContext) => Promise; - updateMany?: (model: string, args: { where?: object; data: any }, context: VisitorContext) => Promise; + updateMany?: ( + model: string, + args: { where?: object; data: any }, + context: NestedWriteVisitorContext + ) => Promise; upsert?: ( model: string, args: { where: object; create: any; update: any }, - context: VisitorContext + context: NestedWriteVisitorContext ) => Promise; - delete?: (model: string, args: object | boolean, context: VisitorContext) => Promise; + delete?: (model: string, args: object | boolean, context: NestedWriteVisitorContext) => Promise; - deleteMany?: (model: string, args: any | object, context: VisitorContext) => Promise; + deleteMany?: (model: string, args: any | object, context: NestedWriteVisitorContext) => Promise; - field?: (field: FieldInfo, action: PrismaWriteActionType, data: any, context: VisitorContext) => Promise; + field?: ( + field: FieldInfo, + action: PrismaWriteActionType, + data: any, + context: NestedWriteVisitorContext + ) => Promise; }; /** diff --git a/packages/runtime/src/enhancements/policy/policy-utils.ts b/packages/runtime/src/enhancements/policy/policy-utils.ts index c97fa1537..d7b592233 100644 --- a/packages/runtime/src/enhancements/policy/policy-utils.ts +++ b/packages/runtime/src/enhancements/policy/policy-utils.ts @@ -17,7 +17,7 @@ import { } from '../../types'; import { getVersion } from '../../version'; import { getFields, resolveField } from '../model-meta'; -import { NestedWriteVisitor, type VisitorContext } from '../nested-write-vistor'; +import { NestedWriteVisitor, type NestedWriteVisitorContext } from '../nested-write-vistor'; import type { ModelMeta, PolicyDef, PolicyFunc, ZodSchemas } from '../types'; import { enumerate, @@ -469,7 +469,7 @@ export class PolicyUtil { }; // build a reversed query for fetching entities affected by nested updates - const buildReversedQuery = async (context: VisitorContext) => { + const buildReversedQuery = async (context: NestedWriteVisitorContext) => { let result, currQuery: any; let currField: FieldInfo | undefined; @@ -514,7 +514,7 @@ export class PolicyUtil { }; // args processor for update/upsert - const processUpdate = async (model: string, where: any, context: VisitorContext) => { + const processUpdate = async (model: string, where: any, context: NestedWriteVisitorContext) => { const preGuard = this.getAuthGuard(model, 'update'); if (preGuard === false) { throw this.deniedByPolicy(model, 'update'); @@ -566,7 +566,7 @@ export class PolicyUtil { }; // args processor for updateMany - const processUpdateMany = async (model: string, args: any, context: VisitorContext) => { + const processUpdateMany = async (model: string, args: any, context: NestedWriteVisitorContext) => { const guard = this.getAuthGuard(model, 'update'); if (guard === false) { throw this.deniedByPolicy(model, 'update'); @@ -580,7 +580,7 @@ export class PolicyUtil { // for models with post-update rules, we need to read and store // entity values before the update for post-update check - const preparePostUpdateCheck = async (model: string, context: VisitorContext) => { + const preparePostUpdateCheck = async (model: string, context: NestedWriteVisitorContext) => { const postGuard = this.getAuthGuard(model, 'postUpdate'); const schema = this.getModelSchema(model); @@ -616,7 +616,7 @@ export class PolicyUtil { }; // args processor for delete - const processDelete = async (model: string, args: any, context: VisitorContext) => { + const processDelete = async (model: string, args: any, context: NestedWriteVisitorContext) => { const guard = this.getAuthGuard(model, 'delete'); if (guard === false) { throw this.deniedByPolicy(model, 'delete'); @@ -632,7 +632,7 @@ export class PolicyUtil { }; // process relation updates: connect, connectOrCreate, and disconnect - const processRelationUpdate = async (model: string, args: any, context: VisitorContext) => { + const processRelationUpdate = async (model: string, args: any, context: NestedWriteVisitorContext) => { // CHECK ME: equire the entity being connected readable? // await this.checkPolicyForFilter(model, args, 'read', this.db); diff --git a/packages/runtime/src/enhancements/where-visitor.ts b/packages/runtime/src/enhancements/where-visitor.ts new file mode 100644 index 000000000..bb806c29d --- /dev/null +++ b/packages/runtime/src/enhancements/where-visitor.ts @@ -0,0 +1,102 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { FieldInfo } from '../types'; +import { resolveField } from './model-meta'; +import { ModelMeta } from './types'; +import { enumerate } from './utils'; + +/** + * Context for visiting + */ +export type WhereVisitorContext = { + /** + * Parent data, can be used to replace fields + */ + parent: any; + + /** + * Current field, undefined if toplevel + */ + field?: FieldInfo; +}; + +/** + * WhereVisitor's callback actions + */ +export type WhereVisitorCallback = { + field?: (field: FieldInfo, data: any) => Promise; +}; + +const FILTER_OPERATORS = [ + 'equals', + 'not', + 'in', + 'notIn', + 'lt', + 'lte', + 'gt', + 'gte', + 'contains', + 'search', + 'startsWith', + 'endsWith', +]; + +const RELATION_FILTER_OPERATORS = ['is', 'isNot', 'some', 'every', 'none']; + +/** + * Recursive visitor for where payload + */ +export class WhereVisitor { + constructor(private readonly modelMeta: ModelMeta, private readonly callback: WhereVisitorCallback) {} + + /** + * Start visiting + */ + async visit(model: string, where: any): Promise { + if (!where) { + return; + } + + for (const [k, v] of Object.entries(where)) { + if (['AND', 'OR', 'NOT'].includes(k)) { + for (const item of enumerate(v)) { + await this.visit(model, item); + } + continue; + } + + if (RELATION_FILTER_OPERATORS.includes(k)) { + // visit into filter body + await this.visit(model, v); + continue; + } + + const field = resolveField(this.modelMeta, model, k); + if (!field) { + continue; + } + + if (typeof v === 'object') { + const filterOp = Object.keys(v).find((f) => FILTER_OPERATORS.includes(f)); + if (filterOp) { + // reach into filter value + const newValue = await this.callback.field?.(field, v[filterOp]); + v[filterOp] = newValue; + continue; + } + + if (Object.keys(v).some((f) => RELATION_FILTER_OPERATORS.includes(f))) { + // filter payload + await this.visit(field.type, v); + continue; + } + } + + // scalar field + const newValue = await this.callback.field?.(field, v); + where[k] = newValue; + } + } +} diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index a3554d2d5..080d229a4 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -1,5 +1,5 @@ +export * from './constants'; export * from './enhancements'; -export * from './enhancements/policy'; +export * from './error'; export * from './types'; export * from './validation'; -export * from './error'; diff --git a/packages/runtime/src/utils/serialization-utils.ts b/packages/runtime/src/utils/serialization-utils.ts deleted file mode 100644 index 15442900b..000000000 --- a/packages/runtime/src/utils/serialization-utils.ts +++ /dev/null @@ -1,21 +0,0 @@ -import Decimal from 'decimal.js'; -import superjson from 'superjson'; - -export function registerSerializers() { - superjson.registerCustom( - { - isApplicable: (v): v is Buffer => Buffer.isBuffer(v), - serialize: (v) => JSON.stringify(v.toJSON().data), - deserialize: (v) => Buffer.from(JSON.parse(v)), - }, - 'Buffer' - ); - superjson.registerCustom( - { - isApplicable: (v): v is Decimal => Decimal.isDecimal(v), - serialize: (v) => v.toJSON(), - deserialize: (v) => new Decimal(v), - }, - 'decimal.js' - ); -} diff --git a/packages/runtime/tsconfig.json b/packages/runtime/tsconfig.json index f6dff1942..acc4bc58e 100644 --- a/packages/runtime/tsconfig.json +++ b/packages/runtime/tsconfig.json @@ -18,5 +18,5 @@ "paths": {} }, "include": ["src/**/*.ts"], - "exclude": ["dist", "node_modules"] + "exclude": ["src/browser"] } diff --git a/packages/runtime/tsup.config.ts b/packages/runtime/tsup.config.ts new file mode 100644 index 000000000..9689884ca --- /dev/null +++ b/packages/runtime/tsup.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/browser/index.ts'], + outDir: 'dist/browser', + splitting: false, + sourcemap: true, + clean: true, + dts: true, + format: ['cjs', 'esm'], +}); diff --git a/packages/schema/src/plugins/zod/generator.ts b/packages/schema/src/plugins/zod/generator.ts index 08734dc0c..78e15f6d2 100644 --- a/packages/schema/src/plugins/zod/generator.ts +++ b/packages/schema/src/plugins/zod/generator.ts @@ -53,6 +53,8 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF. const project = createProject(); + await generateCommonSchemas(project, output); + await generateEnumSchemas( prismaClientDmmf.schema.enumTypes.prisma, prismaClientDmmf.schema.enumTypes.model ?? [], @@ -97,6 +99,18 @@ async function handleGeneratorOutputValue(output: string) { Transformer.setOutputPath(output); } +async function generateCommonSchemas(project: Project, output: string) { + // Decimal + project.createSourceFile( + path.join(output, 'common', 'Decimal.schema.ts'), + ` +import { z } from 'zod'; +export const DecimalSchema = z.union([z.number(), z.string(), z.object({d: z.number().array(), e: z.number(), s: z.number()})]); +`, + { overwrite: true } + ); +} + async function generateEnumSchemas( prismaSchemaEnum: DMMF.SchemaEnum[], modelSchemaEnum: DMMF.SchemaEnum[], @@ -214,6 +228,11 @@ async function generateModelSchema(model: DataModel, project: Project, output: s } } + // import Decimal + if (fields.some((field) => field.type.type === 'Decimal')) { + writer.writeLine(`import { DecimalSchema } from '../common/Decimal.schema';`); + } + // create base schema writer.write(`const baseSchema = z.object(`); writer.inlineBlock(() => { diff --git a/packages/schema/src/plugins/zod/transformer.ts b/packages/schema/src/plugins/zod/transformer.ts index 5017adbb6..3ffa55c7d 100644 --- a/packages/schema/src/plugins/zod/transformer.ts +++ b/packages/schema/src/plugins/zod/transformer.ts @@ -23,6 +23,7 @@ export default class Transformer { static provider: string; private static outputPath = './generated'; private hasJson = false; + private hasDecimal = false; private project: Project; private zmodel: Model; @@ -110,8 +111,11 @@ export default class Transformer { let alternatives = lines.reduce((result, inputType) => { if (inputType.type === 'String') { result.push(this.wrapWithZodValidators('z.string()', field, inputType)); - } else if (inputType.type === 'Int' || inputType.type === 'Float' || inputType.type === 'Decimal') { + } else if (inputType.type === 'Int' || inputType.type === 'Float') { result.push(this.wrapWithZodValidators('z.number()', field, inputType)); + } else if (inputType.type === 'Decimal') { + this.hasDecimal = true; + result.push(this.wrapWithZodValidators('DecimalSchema', field, inputType)); } else if (inputType.type === 'BigInt') { result.push(this.wrapWithZodValidators('z.bigint()', field, inputType)); } else if (inputType.type === 'Boolean') { @@ -119,7 +123,7 @@ export default class Transformer { } else if (inputType.type === 'DateTime') { result.push(this.wrapWithZodValidators(['z.date()', 'z.string().datetime()'], field, inputType)); } else if (inputType.type === 'Bytes') { - result.push(this.wrapWithZodValidators('z.number().array()', field, inputType)); + result.push(this.wrapWithZodValidators(`z.instanceof(Uint8Array)`, field, inputType)); } else if (inputType.type === 'Json') { this.hasJson = true; result.push(this.wrapWithZodValidators('jsonSchema', field, inputType)); @@ -253,9 +257,9 @@ export default class Transformer { if (isAggregateInputType(name)) { name = `${name}Type`; } - return `export const ${this.name}ObjectSchema: z.ZodType "'" + f + "'" - ).join('|')}>> = ${schema};`; + const outType = `z.ZodType "'" + f + "'").join('|')}>>`; + return `type SchemaType = ${outType}; +export const ${this.name}ObjectSchema: SchemaType = ${schema} as SchemaType;`; } addFinalWrappers({ zodStringFields }: { zodStringFields: string[] }) { @@ -289,6 +293,9 @@ export default class Transformer { generateObjectSchemaImportStatements() { let generatedImports = this.generateImportZodStatement(); generatedImports += this.generateSchemaImports(); + if (this.hasDecimal) { + generatedImports += this.generateDecimalImport(); + } generatedImports += '\n\n'; return generatedImports; } @@ -298,14 +305,18 @@ export default class Transformer { .map((name) => { const { isModelQueryType, modelName } = this.checkIsModelQueryType(name); if (isModelQueryType) { - return `import { ${modelName}InputSchema } from '../input/${modelName}Input.schema'`; + return `import { ${modelName}InputSchema } from '../input/${modelName}Input.schema';`; } else if (Transformer.enumNames.includes(name)) { - return `import { ${name}Schema } from '../enums/${name}.schema'`; + return `import { ${name}Schema } from '../enums/${name}.schema';`; } else { - return `import { ${name}ObjectSchema } from './${name}.schema'`; + return `import { ${name}ObjectSchema } from './${name}.schema';`; } }) - .join(';\r\n'); + .join('\n'); + } + + private generateDecimalImport() { + return `import { DecimalSchema } from '../common/Decimal.schema';\n\n`; } checkIsModelQueryType(type: string) { diff --git a/packages/schema/src/plugins/zod/utils/schema-gen.ts b/packages/schema/src/plugins/zod/utils/schema-gen.ts index 78a720549..34f53083d 100644 --- a/packages/schema/src/plugins/zod/utils/schema-gen.ts +++ b/packages/schema/src/plugins/zod/utils/schema-gen.ts @@ -114,9 +114,11 @@ function makeZodSchema(field: DataModelField) { switch (field.type.type) { case 'Int': case 'Float': - case 'Decimal': schema = 'z.number()'; break; + case 'Decimal': + schema = 'DecimalSchema'; + break; case 'BigInt': schema = 'z.bigint()'; break; @@ -130,7 +132,7 @@ function makeZodSchema(field: DataModelField) { schema = 'z.date()'; break; case 'Bytes': - schema = 'z.number().array()'; + schema = 'z.union([z.string(), z.instanceof(Uint8Array)])'; break; default: schema = 'z.any()'; diff --git a/packages/sdk/src/constants.ts b/packages/sdk/src/constants.ts index b1d8d7693..bba0ab93b 100644 --- a/packages/sdk/src/constants.ts +++ b/packages/sdk/src/constants.ts @@ -3,12 +3,7 @@ */ export const RUNTIME_PACKAGE = '@zenstackhq/runtime'; -export { - AUXILIARY_FIELDS, - GUARD_FIELD_NAME, - TRANSACTION_FIELD_NAME, - CrudFailureReason, -} from '@zenstackhq/runtime/constants'; +export { AUXILIARY_FIELDS, GUARD_FIELD_NAME, TRANSACTION_FIELD_NAME, CrudFailureReason } from '@zenstackhq/runtime'; /** * Expression context diff --git a/packages/server/package.json b/packages/server/package.json index 27c0c7939..e4e550f67 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -9,7 +9,7 @@ "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE dist && pnpm pack dist --pack-destination '../../../.build'", "watch": "tsc --watch", "lint": "eslint src --ext ts", - "test": "jest", + "test": "ZENSTACK_TEST=1 jest", "prepublishOnly": "pnpm build", "publish-dev": "pnpm publish --tag dev" }, @@ -46,6 +46,7 @@ "@zenstackhq/testtools": "workspace:*", "body-parser": "^1.20.2", "copyfiles": "^2.4.1", + "decimal.js": "^10.4.2", "express": "^4.18.2", "fastify": "^4.14.1", "fastify-plugin": "^4.5.0", diff --git a/packages/server/src/api/base.ts b/packages/server/src/api/base.ts new file mode 100644 index 000000000..d7e986b3d --- /dev/null +++ b/packages/server/src/api/base.ts @@ -0,0 +1,17 @@ +import { ModelMeta, getDefaultModelMeta } from '@zenstackhq/runtime'; + +/** + * Base class for API handlers + */ +export abstract class APIHandlerBase { + // model meta loaded from default location + protected readonly defaultModelMeta: ModelMeta | undefined; + + constructor() { + try { + this.defaultModelMeta = getDefaultModelMeta(); + } catch { + // noop + } + } +} diff --git a/packages/server/src/api/rest/index.ts b/packages/server/src/api/rest/index.ts index c800fc093..765f93159 100644 --- a/packages/server/src/api/rest/index.ts +++ b/packages/server/src/api/rest/index.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import type { ModelMeta, ZodSchemas } from '@zenstackhq/runtime'; import { DbClientContract, FieldInfo, @@ -6,16 +7,17 @@ import { getIdFields, isPrismaClientKnownRequestError, } from '@zenstackhq/runtime'; -import { getDefaultModelMeta } from '@zenstackhq/runtime/enhancements/model-meta'; -import type { ModelMeta } from '@zenstackhq/runtime/enhancements/types'; import { paramCase } from 'change-case'; import { lowerCaseFirst } from 'lower-case-first'; -import { DataDocument, Linker, Paginator, Relator, Serializer, SerializerOptions } from 'ts-japi'; +import SuperJSON from 'superjson'; +import { Linker, Paginator, Relator, Serializer, SerializerOptions } from 'ts-japi'; +import { upperCaseFirst } from 'upper-case-first'; import UrlPattern from 'url-pattern'; import z from 'zod'; import { fromZodError } from 'zod-validation-error'; import { LoggerConfig, RequestContext, Response } from '../../types'; -import { logWarning, stripAuxFields } from '../utils'; +import { APIHandlerBase } from '../base'; +import { logWarning, processEntityData, registerCustomSerializers } from '../utils'; const urlPatterns = { // collection operations @@ -88,10 +90,12 @@ const FilterOperations = [ type FilterOperationType = (typeof FilterOperations)[number] | undefined; +registerCustomSerializers(); + /** * RESTful-style API request handler (compliant with JSON:API) */ -class RequestHandler { +class RequestHandler extends APIHandlerBase { // resource serializers private serializers: Map; @@ -186,6 +190,7 @@ class RequestHandler { ) .optional(), }), + meta: z.object({}).passthrough().optional(), }) .strict(); @@ -202,15 +207,8 @@ class RequestHandler { // all known types and their metadata private typeMap: Record; - // model meta loaded from default location - private readonly defaultModelMeta: ModelMeta; - constructor(private readonly options: Options) { - try { - this.defaultModelMeta = getDefaultModelMeta(); - } catch { - // noop - } + super(); } async handleRequest({ @@ -221,14 +219,19 @@ class RequestHandler { requestBody, logger, modelMeta, - }: /* zodSchemas, */ - RequestContext): Promise { + zodSchemas, + }: RequestContext): Promise { + modelMeta = modelMeta ?? this.defaultModelMeta; + if (!modelMeta) { + throw new Error('Model meta is not provided or loaded from default location'); + } + if (!this.serializers) { - this.buildSerializers(modelMeta ?? this.defaultModelMeta); + this.buildSerializers(modelMeta); } if (!this.typeMap) { - this.buildTypeMap(logger, modelMeta ?? this.defaultModelMeta); + this.buildTypeMap(logger, modelMeta); } method = method.toUpperCase(); @@ -260,7 +263,7 @@ class RequestHandler { match.id, match.relationship, query, - modelMeta ?? this.defaultModelMeta + modelMeta ); } @@ -274,10 +277,14 @@ class RequestHandler { } case 'POST': { + if (!requestBody) { + return this.makeError('invalidPayload'); + } + let match = urlPatterns.collection.match(path); if (match) { // resource creation - return await this.processCreate(prisma, match.type, query, requestBody /*, zodSchemas */); + return await this.processCreate(prisma, match.type, query, requestBody, zodSchemas); } match = urlPatterns.relationship.match(path); @@ -300,16 +307,14 @@ class RequestHandler { // TODO: PUT for full update case 'PUT': case 'PATCH': { + if (!requestBody) { + return this.makeError('invalidPayload'); + } + let match = urlPatterns.single.match(path); if (match) { // resource update - return await this.processUpdate( - prisma, - match.type, - match.id, - query, - requestBody /*, zodSchemas */ - ); + return await this.processUpdate(prisma, match.type, match.id, query, requestBody, zodSchemas); } match = urlPatterns.relationship.match(path); @@ -666,41 +671,56 @@ class RequestHandler { })); } + private processRequestBody( + type: string, + requestBody: unknown, + zodSchemas: ZodSchemas | undefined, + mode: 'create' | 'update' + ) { + let body: any = requestBody; + if (body.meta?.serialization) { + // superjson deserialize body if a serialization meta is provided + body = SuperJSON.deserialize({ json: body, meta: body.meta.serialization }); + } + + const parsed = this.createUpdatePayloadSchema.parse(body); + const attributes: any = parsed.data.attributes; + + if (attributes) { + const schemaName = `${upperCaseFirst(type)}${upperCaseFirst(mode)}Schema`; + // zod-parse attributes if a schema is provided + const payloadSchema = zodSchemas?.models?.[schemaName]; + if (payloadSchema) { + const parsed = payloadSchema.safeParse(attributes); + if (!parsed.success) { + return { error: this.makeError('invalidPayload', fromZodError(parsed.error).message) }; + } + } + } + + return { attributes, relationships: parsed.data.relationships }; + } + private async processCreate( prisma: DbClientContract, type: string, _query: Record | undefined, - requestBody: unknown - // zodSchemas?: ModelZodSchema + requestBody: unknown, + zodSchemas?: ZodSchemas ): Promise { const typeInfo = this.typeMap[type]; if (!typeInfo) { return this.makeUnsupportedModelError(type); } - // zod-parse payload - const parsed = this.createUpdatePayloadSchema.safeParse(requestBody); - if (!parsed.success) { - return this.makeError('invalidPayload', fromZodError(parsed.error).message); + const { error, attributes, relationships } = this.processRequestBody(type, requestBody, zodSchemas, 'create'); + if (error) { + return error; } - const parsedPayload = parsed.data; - const createPayload: any = { data: { ...parsedPayload.data?.attributes } }; - - // TODO: we need to somehow exclude relation fields from the zod schema because relations are - // not part of "data" in the payload - // - // // zod-parse attributes if a schema is provided - // const dataSchema = zodSchemas ? getZodSchema(zodSchemas, type, 'create') : undefined; - // if (dataSchema) { - // const dataParsed = dataSchema.safeParse(createPayload); - // if (!dataParsed.success) { - // return this.makeError('invalidPayload', fromZodError(dataParsed.error).message); - // } - // } + const createPayload: any = { data: { ...attributes } }; // turn relashionship payload into Prisma connect objects - const relationships = parsedPayload.data?.relationships; if (relationships) { for (const [key, data] of Object.entries(relationships)) { if (!data?.data) { @@ -836,26 +856,18 @@ class RequestHandler { prisma: DbClientContract, type: any, resourceId: string, - query: Record | undefined, - requestBody: unknown - // zodSchemas?: ModelZodSchema + _query: Record | undefined, + requestBody: unknown, + zodSchemas?: ZodSchemas ): Promise { const typeInfo = this.typeMap[type]; if (!typeInfo) { return this.makeUnsupportedModelError(type); } - // zod-parse payload - const parsed = this.createUpdatePayloadSchema.safeParse(requestBody); - if (!parsed.success) { - return this.makeError('invalidPayload', fromZodError(parsed.error).message); - } - - const parsedPayload = parsed.data; - - const attributes = parsedPayload.data?.attributes; - if (!attributes) { - return this.makeError('invalidPayload'); + const { error, attributes, relationships } = this.processRequestBody(type, requestBody, zodSchemas, 'update'); + if (error) { + return error; } const updatePayload: any = { @@ -863,20 +875,7 @@ class RequestHandler { data: { ...attributes }, }; - // TODO: we need to somehow exclude relation fields from the zod schema because relations are - // not part of "data" in the payload - // - // // zod-parse attributes if a schema is provided - // const dataSchema = zodSchemas ? getZodSchema(zodSchemas, type, 'update') : undefined; - // if (dataSchema) { - // const dataParsed = dataSchema.safeParse(updatePayload); - // if (!dataParsed.success) { - // return this.makeError('invalidPayload', fromZodError(dataParsed.error).message); - // } - // } - // turn relationships into prisma payload - const relationships = parsedPayload.data?.relationships; if (relationships) { for (const [key, data] of Object.entries(relationships)) { if (!data?.data) { @@ -1089,20 +1088,62 @@ class RequestHandler { } } - private async serializeItems( - model: string, - items: unknown, - options?: Partial> - ): Promise>> { + private async serializeItems(model: string, items: unknown, options?: Partial>) { model = lowerCaseFirst(model); const serializer = this.serializers.get(model); if (!serializer) { throw new Error(`serializer not found for model ${model}`); } - stripAuxFields(items); + processEntityData(items); + + // serialize to JSON:API strcuture + const serialized = await serializer.serialize(items, options); + + // convert the serialization result to plain object otherwise SuperJSON won't work + const plainResult = this.toPlainObject(serialized); + + // superjson serialize the result + const { json, meta } = SuperJSON.serialize(plainResult); + + const result: any = json; + if (meta) { + result.meta = { ...result.meta, serialization: meta }; + } + + return result; + } + + private toPlainObject(data: any): any { + if (data === undefined || data === null) { + return data; + } + + if (Array.isArray(data)) { + return data.map((item: any) => this.toPlainObject(item)); + } + + if (typeof data === 'object') { + if (typeof data.toJSON === 'function') { + // custom toJSON function + return data.toJSON(); + } + const result: any = {}; + for (const [field, value] of Object.entries(data)) { + if (value === undefined || typeof value === 'function') { + // trim undefined and functions + continue; + } else if (field === 'attributes') { + // don't visit into entity data + result[field] = value; + } else { + result[field] = this.toPlainObject(value); + } + } + return result; + } - return JSON.parse(JSON.stringify(await serializer.serialize(items, options))); + return data; } private replaceURLSearchParams(url: string, params: Record) { diff --git a/packages/server/src/api/rpc/index.ts b/packages/server/src/api/rpc/index.ts index 06050ceb6..e03815376 100644 --- a/packages/server/src/api/rpc/index.ts +++ b/packages/server/src/api/rpc/index.ts @@ -1,31 +1,44 @@ import { DbOperations, + ZodSchemas, isPrismaClientKnownRequestError, isPrismaClientUnknownRequestError, isPrismaClientValidationError, } from '@zenstackhq/runtime'; +import SuperJSON from 'superjson'; +import { upperCaseFirst } from 'upper-case-first'; +import { fromZodError } from 'zod-validation-error'; import { RequestContext, Response } from '../../types'; -import { logError, stripAuxFields, zodValidate } from '../utils'; +import { APIHandlerBase } from '../base'; +import { logError, processEntityData, registerCustomSerializers } from '../utils'; + +registerCustomSerializers(); /** * Prisma RPC style API request handler that mirrors the Prisma Client API */ -class RequestHandler { +class RequestHandler extends APIHandlerBase { async handleRequest({ prisma, method, path, query, requestBody, + modelMeta, zodSchemas, logger, }: RequestContext): Promise { + modelMeta = modelMeta ?? this.defaultModelMeta; + if (!modelMeta) { + throw new Error('Model meta is not provided or loaded from default location'); + } + const parts = path.split('/').filter((p) => !!p); const op = parts.pop(); const model = parts.pop(); if (parts.length !== 0 || !op || !model) { - return { status: 400, body: { message: 'invalid request path' } }; + return { status: 400, body: this.makeError('invalid request path') }; } method = method.toUpperCase(); @@ -38,10 +51,13 @@ class RequestHandler { case 'createMany': case 'upsert': if (method !== 'POST') { - return { status: 400, body: { message: 'invalid request method, only POST is supported' } }; + return { + status: 400, + body: this.makeError('invalid request method, only POST is supported'), + }; } if (!requestBody) { - return { status: 400, body: { message: 'missing request body' } }; + return { status: 400, body: this.makeError('missing request body') }; } args = requestBody; @@ -57,12 +73,15 @@ class RequestHandler { case 'groupBy': case 'count': if (method !== 'GET') { - return { status: 400, body: { message: 'invalid request method, only GET is supported' } }; + return { + status: 400, + body: this.makeError('invalid request method, only GET is supported'), + }; } try { - args = query?.q ? this.unmarshal(query.q as string) : {}; + args = query?.q ? this.unmarshalQ(query.q as string, query.meta as string | undefined) : {}; } catch { - return { status: 400, body: { message: 'query param must contain valid JSON' } }; + return { status: 400, body: this.makeError('invalid "q" query parameter') }; } break; @@ -71,11 +90,11 @@ class RequestHandler { if (method !== 'PUT' && method !== 'PATCH') { return { status: 400, - body: { message: 'invalid request method, only PUT AND PATCH are supported' }, + body: this.makeError('invalid request method, only PUT AND PATCH are supported'), }; } if (!requestBody) { - return { status: 400, body: { message: 'missing request body' } }; + return { status: 400, body: this.makeError('missing request body') }; } args = requestBody; @@ -84,35 +103,48 @@ class RequestHandler { case 'delete': case 'deleteMany': if (method !== 'DELETE') { - return { status: 400, body: { message: 'invalid request method, only DELETE is supported' } }; + return { + status: 400, + body: this.makeError('invalid request method, only DELETE is supported'), + }; } try { - args = query?.q ? this.unmarshal(query.q as string) : {}; + args = query?.q ? this.unmarshalQ(query.q as string, query.meta as string | undefined) : {}; } catch { - return { status: 400, body: { message: 'query param must contain valid JSON' } }; + return { status: 400, body: this.makeError('invalid "q" query parameter') }; } break; default: - return { status: 400, body: { message: 'invalid operation: ' + op } }; + return { status: 400, body: this.makeError('invalid operation: ' + op) }; } - if (zodSchemas) { - const { data, error } = zodValidate(zodSchemas, model, dbOp, args); - if (error) { - return { status: 400, body: { message: error } }; - } else { - args = data; - } + const { error, data: parsedArgs } = await this.processRequestPayload(args, model, dbOp, zodSchemas); + if (error) { + return { status: 400, body: this.makeError(error) }; } try { if (!prisma[model]) { - return { status: 400, body: { message: `unknown model name: ${model}` } }; + return { status: 400, body: this.makeError(`unknown model name: ${model}`) }; + } + + const result = await prisma[model][dbOp](parsedArgs); + processEntityData(result); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let response: any = { data: result }; + + // superjson serialize response + if (result) { + const { json, meta } = SuperJSON.serialize(result); + response = { data: json }; + if (meta) { + response.meta = { serialization: meta }; + } } - const result = await prisma[model][dbOp](args); - stripAuxFields(result); - return { status: resCode, body: result }; + + return { status: resCode, body: response }; } catch (err) { if (isPrismaClientKnownRequestError(err)) { logError(logger, err.code, err.message); @@ -121,21 +153,25 @@ class RequestHandler { return { status: 403, body: { - prisma: true, - rejectedByPolicy: true, - code: err.code, - message: err.message, - reason: err.meta?.reason, + error: { + prisma: true, + rejectedByPolicy: true, + code: err.code, + message: err.message, + reason: err.meta?.reason, + }, }, }; } else { return { status: 400, body: { - prisma: true, - code: err.code, - message: err.message, - reason: err.meta?.reason, + error: { + prisma: true, + code: err.code, + message: err.message, + reason: err.meta?.reason, + }, }, }; } @@ -144,8 +180,10 @@ class RequestHandler { return { status: 400, body: { - prisma: true, - message: err.message, + error: { + prisma: true, + message: err.message, + }, }, }; } else { @@ -153,16 +191,79 @@ class RequestHandler { logError(logger, _err.message + (_err.stack ? '\n' + _err.stack : '')); return { status: 400, - body: { - message: (err as Error).message, - }, + body: this.makeError((err as Error).message), }; } } } - private unmarshal(value: string) { - return JSON.parse(value); + private makeError(message: string) { + return { error: { message: message } }; + } + + private async processRequestPayload( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + args: any, + model: string, + dbOp: string, + zodSchemas: ZodSchemas | undefined + ) { + const { meta, ...rest } = args; + if (meta?.serialization) { + // superjson deserialization + args = SuperJSON.deserialize({ json: rest, meta: meta.serialization }); + } + return this.zodValidate(zodSchemas, model, dbOp as keyof DbOperations, args); + } + + private getZodSchema(zodSchemas: ZodSchemas, model: string, operation: keyof DbOperations) { + // e.g.: UserInputSchema { findUnique: [schema] } + return zodSchemas.input?.[`${upperCaseFirst(model)}InputSchema`]?.[operation]; + } + + private zodValidate( + zodSchemas: ZodSchemas | undefined, + model: string, + operation: keyof DbOperations, + args: unknown + ) { + const zodSchema = zodSchemas && this.getZodSchema(zodSchemas, model, operation); + if (zodSchema) { + const parseResult = zodSchema.safeParse(args); + if (parseResult.success) { + return { data: args, error: undefined }; + } else { + return { data: undefined, error: fromZodError(parseResult.error).message }; + } + } else { + return { data: args, error: undefined }; + } + } + + private unmarshalQ(value: string, meta: string | undefined) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let parsedValue: any; + try { + parsedValue = JSON.parse(value); + } catch { + throw new Error('invalid "q" query parameter'); + } + + if (meta) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let parsedMeta: any; + try { + parsedMeta = JSON.parse(meta); + } catch { + throw new Error('invalid "meta" query parameter'); + } + + if (parsedMeta.serialization) { + return SuperJSON.deserialize({ json: parsedValue, meta: parsedMeta.serialization }); + } + } + + return parsedValue; } } diff --git a/packages/server/src/api/utils.ts b/packages/server/src/api/utils.ts index b3cd138f8..ba74dd2d3 100644 --- a/packages/server/src/api/utils.ts +++ b/packages/server/src/api/utils.ts @@ -1,34 +1,9 @@ -import { DbOperations } from '@zenstackhq/runtime'; -import { AUXILIARY_FIELDS } from '@zenstackhq/runtime/constants'; -import type { ZodSchemas } from '@zenstackhq/runtime/enhancements/types'; -import { upperCaseFirst } from 'upper-case-first'; -import { fromZodError } from 'zod-validation-error'; +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { AUXILIARY_FIELDS } from '@zenstackhq/runtime'; +import { Decimal } from 'decimal.js'; +import SuperJSON from 'superjson'; import { LoggerConfig } from '../types'; -export function getZodSchema(zodSchemas: ZodSchemas, model: string, operation: keyof DbOperations) { - // e.g.: UserInputSchema { findUnique: [schema] } - return zodSchemas.input?.[`${upperCaseFirst(model)}InputSchema`]?.[operation]; -} - -export function zodValidate( - zodSchemas: ZodSchemas | undefined, - model: string, - operation: keyof DbOperations, - args: unknown -) { - const zodSchema = zodSchemas && getZodSchema(zodSchemas, model, operation); - if (zodSchema) { - const parseResult = zodSchema.safeParse(args); - if (parseResult.success) { - return { data: parseResult.data, error: undefined }; - } else { - return { data: undefined, error: fromZodError(parseResult.error).message }; - } - } else { - return { data: args, error: undefined }; - } -} - export function logError(logger: LoggerConfig | undefined | null, message: string, code?: string) { if (logger === undefined) { console.error(`@zenstackhq/server: error ${code ? '[' + code + ']' : ''}, ${message}`); @@ -53,10 +28,7 @@ export function logInfo(logger: LoggerConfig | undefined | null, message: string } } -/** - * Recursively strip auxiliary fields from the given data. - */ -export function stripAuxFields(data: unknown) { +function stripAuxFields(data: unknown) { if (Array.isArray(data)) { return data.forEach(stripAuxFields); } else if (data && typeof data === 'object') { @@ -70,3 +42,35 @@ export function stripAuxFields(data: unknown) { } } } + +/** + * Processes entity data returned from Prisma call. + */ +export function processEntityData(data: any) { + if (data) { + stripAuxFields(data); + } +} + +/** + * Registers custom superjson serializers. + */ +export function registerCustomSerializers() { + SuperJSON.registerCustom( + { + isApplicable: (v): v is Decimal => Decimal.isDecimal(v), + serialize: (v) => v.toJSON(), + deserialize: (v) => new Decimal(v), + }, + 'Decimal' + ); + + SuperJSON.registerCustom( + { + isApplicable: (v): v is Buffer => Buffer.isBuffer(v), + serialize: (v) => v.toString('base64'), + deserialize: (v) => Buffer.from(v, 'base64'), + }, + 'Bytes' + ); +} diff --git a/packages/server/src/express/middleware.ts b/packages/server/src/express/middleware.ts index 492004a66..b17d3f030 100644 --- a/packages/server/src/express/middleware.ts +++ b/packages/server/src/express/middleware.ts @@ -1,10 +1,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import type { ZodSchemas } from '@zenstackhq/runtime'; import { DbClientContract } from '@zenstackhq/runtime'; import type { Handler, Request, Response } from 'express'; import RPCAPIHandler from '../api/rpc'; import { AdapterBaseOptions } from '../types'; -import { buildUrlQuery, marshalToObject, unmarshalFromObject } from '../utils'; -import type { ZodSchemas } from '@zenstackhq/runtime/enhancements/types'; /** * Express middleware options @@ -35,14 +34,19 @@ const factory = (options: MiddlewareOptions): Handler => { if (typeof options.zodSchemas === 'object') { zodSchemas = options.zodSchemas; } else if (options.zodSchemas === true) { - zodSchemas = require('@zenstackhq/runtime/zod'); - if (!zodSchemas) { + try { + zodSchemas = require('@zenstackhq/runtime/zod'); + } catch { throw new Error('Unable to load zod schemas from default location'); } } const requestHandler = options.handler || RPCAPIHandler(); - const useSuperJson = options.useSuperJson === true; + if (options.useSuperJson !== undefined) { + console.warn( + 'The option "useSuperJson" is deprecated. The server APIs automatically use superjson for serialization.' + ); + } return async (request, response, next) => { const prisma = (await options.getPrisma(request, response)) as DbClientContract; @@ -53,37 +57,22 @@ const factory = (options: MiddlewareOptions): Handler => { } if (!prisma) { - return response - .status(500) - .json(marshalToObject({ message: 'unable to get prisma from request context' }, useSuperJson)); + return response.status(500).json({ message: 'unable to get prisma from request context' }); } - let query: Record = {}; - try { - // express converts query parameters with square brackets into object - // e.g.: filter[foo]=bar is parsed to { filter: { foo: 'bar' } } - // we need to revert this behavior and reconstruct params from original URL - const url = request.protocol + '://' + request.get('host') + request.originalUrl; - const searchParams = new URL(url).searchParams; - const rawQuery: Record = {}; - for (const key of searchParams.keys()) { - const values = searchParams.getAll(key); - rawQuery[key] = values.length === 1 ? values[0] : values; - } - query = buildUrlQuery(rawQuery, useSuperJson); - } catch { - if (sendResponse === false) { - throw new Error('invalid query parameters'); - } - return response.status(400).json(marshalToObject({ message: 'invalid query parameters' }, useSuperJson)); - } + // express converts query parameters with square brackets into object + // e.g.: filter[foo]=bar is parsed to { filter: { foo: 'bar' } } + // we need to revert this behavior and reconstruct params from original URL + const url = request.protocol + '://' + request.get('host') + request.originalUrl; + const searchParams = new URL(url).searchParams; + const query = Object.fromEntries(searchParams); try { const r = await requestHandler({ method: request.method, path: request.path, query, - requestBody: unmarshalFromObject(request.body, useSuperJson), + requestBody: request.body, prisma, modelMeta: options.modelMeta, zodSchemas, @@ -97,14 +86,12 @@ const factory = (options: MiddlewareOptions): Handler => { }; return next(); } - return response.status(r.status).json(marshalToObject(r.body, useSuperJson)); + return response.status(r.status).json(r.body); } catch (err) { if (sendResponse === false) { throw err; } - return response - .status(500) - .json(marshalToObject({ message: `An unhandled error occurred: ${err}` }, useSuperJson)); + return response.status(500).json({ message: `An unhandled error occurred: ${err}` }); } }; }; diff --git a/packages/server/src/fastify/plugin.ts b/packages/server/src/fastify/plugin.ts index 7406bf944..4b86eec4b 100644 --- a/packages/server/src/fastify/plugin.ts +++ b/packages/server/src/fastify/plugin.ts @@ -1,12 +1,11 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import type { ZodSchemas } from '@zenstackhq/runtime'; import { DbClientContract } from '@zenstackhq/runtime'; import { FastifyPluginCallback, FastifyReply, FastifyRequest } from 'fastify'; import fp from 'fastify-plugin'; import RPCApiHandler from '../api/rpc'; import { logInfo } from '../api/utils'; import { AdapterBaseOptions } from '../types'; -import { buildUrlQuery, marshalToObject, unmarshalFromObject } from '../utils'; -import type { ZodSchemas } from '@zenstackhq/runtime/enhancements/types'; /** * Fastify plugin options @@ -41,22 +40,16 @@ const pluginHandler: FastifyPluginCallback = (fastify, options, d } const requestHanler = options.handler ?? RPCApiHandler(); - const useSuperJson = options.useSuperJson === true; + if (options.useSuperJson !== undefined) { + console.warn( + 'The option "useSuperJson" is deprecated. The server APIs automatically use superjson for serialization.' + ); + } fastify.all(`${prefix}/*`, async (request, reply) => { const prisma = (await options.getPrisma(request, reply)) as DbClientContract; if (!prisma) { - reply - .status(500) - .send(marshalToObject({ message: 'unable to get prisma from request context' }, useSuperJson)); - return; - } - - let query: Record = {}; - try { - query = buildUrlQuery(request.query, useSuperJson); - } catch { - reply.status(400).send(marshalToObject({ message: 'invalid query parameters' }, useSuperJson)); + reply.status(500).send({ message: 'unable to get prisma from request context' }); return; } @@ -64,16 +57,16 @@ const pluginHandler: FastifyPluginCallback = (fastify, options, d const response = await requestHanler({ method: request.method, path: (request.params as any)['*'], - query, - requestBody: unmarshalFromObject(request.body, useSuperJson), + query: request.query as Record, + requestBody: request.body, prisma, modelMeta: options.modelMeta, zodSchemas, logger: options.logger, }); - reply.status(response.status).send(marshalToObject(response.body, useSuperJson)); + reply.status(response.status).send(response.body); } catch (err) { - reply.status(500).send(marshalToObject({ message: `An unhandled error occurred: ${err}` }, useSuperJson)); + reply.status(500).send({ message: `An unhandled error occurred: ${err}` }); } }); diff --git a/packages/server/src/next/app-route-handler.ts b/packages/server/src/next/app-route-handler.ts index f40cae80e..58f78244f 100644 --- a/packages/server/src/next/app-route-handler.ts +++ b/packages/server/src/next/app-route-handler.ts @@ -1,11 +1,10 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ +import type { ZodSchemas } from '@zenstackhq/runtime'; import { DbClientContract } from '@zenstackhq/runtime'; -import type { ZodSchemas } from '@zenstackhq/runtime/enhancements/types'; import { NextRequest, NextResponse } from 'next/server'; import { AppRouteRequestHandlerOptions } from '.'; import RPCAPIHandler from '../api/rpc'; -import { buildUrlQuery, marshalToObject, unmarshalFromObject } from '../utils'; type Context = { params: { path: string[] } }; @@ -29,31 +28,28 @@ export default function factory( } const requestHandler = options.handler || RPCAPIHandler(); - const useSuperJson = options.useSuperJson === true; + if (options.useSuperJson !== undefined) { + console.warn( + 'The option "useSuperJson" is deprecated. The server APIs automatically use superjson for serialization.' + ); + } return async (req: NextRequest, context: Context) => { const prisma = (await options.getPrisma(req)) as DbClientContract; if (!prisma) { - return NextResponse.json( - marshalToObject({ message: 'unable to get prisma from request context' }, useSuperJson), - { status: 500 } - ); + return NextResponse.json({ message: 'unable to get prisma from request context' }, { status: 500 }); } const url = new URL(req.url); - let query: Record = Object.fromEntries(url.searchParams); - try { - query = buildUrlQuery(query, useSuperJson); - } catch { - return NextResponse.json(marshalToObject({ message: 'invalid query parameters' }, useSuperJson), { - status: 400, - }); - } + const query = Object.fromEntries(url.searchParams); if (!context.params.path) { - return NextResponse.json(marshalToObject({ message: 'missing path parameter' }, useSuperJson), { - status: 400, - }); + return NextResponse.json( + { message: 'missing path parameter' }, + { + status: 400, + } + ); } const path = context.params.path.join('/'); @@ -71,18 +67,15 @@ export default function factory( method: req.method!, path, query, - requestBody: unmarshalFromObject(requestBody, useSuperJson), + requestBody, prisma, modelMeta: options.modelMeta, zodSchemas, logger: options.logger, }); - return NextResponse.json(marshalToObject(r.body, useSuperJson), { status: r.status }); + return NextResponse.json(r.body, { status: r.status }); } catch (err) { - return NextResponse.json( - marshalToObject({ message: `An unhandled error occurred: ${err}` }, useSuperJson), - { status: 500 } - ); + return NextResponse.json({ message: `An unhandled error occurred: ${err}` }, { status: 500 }); } }; } diff --git a/packages/server/src/next/pages-route-handler.ts b/packages/server/src/next/pages-route-handler.ts index 4b980ff72..2baf0f1a4 100644 --- a/packages/server/src/next/pages-route-handler.ts +++ b/packages/server/src/next/pages-route-handler.ts @@ -1,11 +1,10 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ +import type { ZodSchemas } from '@zenstackhq/runtime'; import { DbClientContract } from '@zenstackhq/runtime'; -import type { ZodSchemas } from '@zenstackhq/runtime/enhancements/types'; import { NextApiRequest, NextApiResponse } from 'next'; import { PagesRouteRequestHandlerOptions } from '.'; import RPCAPIHandler from '../api/rpc'; -import { buildUrlQuery, marshalToObject, unmarshalFromObject } from '../utils'; /** * Creates a Next.js API endpoint (traditional "pages" route) request handler which encapsulates Prisma CRUD operations. @@ -27,27 +26,21 @@ export default function factory( } const requestHandler = options.handler || RPCAPIHandler(); - const useSuperJson = options.useSuperJson === true; + if (options.useSuperJson !== undefined) { + console.warn( + 'The option "useSuperJson" is deprecated. The server APIs automatically use superjson for serialization.' + ); + } return async (req: NextApiRequest, res: NextApiResponse) => { const prisma = (await options.getPrisma(req, res)) as DbClientContract; if (!prisma) { - res.status(500).json( - marshalToObject({ message: 'unable to get prisma from request context' }, useSuperJson) - ); - return; - } - - let query: Record = {}; - try { - query = buildUrlQuery(req.query, useSuperJson); - } catch { - res.status(400).json(marshalToObject({ message: 'invalid query parameters' }, useSuperJson)); + res.status(500).json({ message: 'unable to get prisma from request context' }); return; } if (!req.query.path) { - res.status(400).json(marshalToObject({ message: 'missing path parameter' }, useSuperJson)); + res.status(400).json({ message: 'missing path parameter' }); return; } const path = (req.query.path as string[]).join('/'); @@ -56,16 +49,16 @@ export default function factory( const r = await requestHandler({ method: req.method!, path, - query, - requestBody: unmarshalFromObject(req.body, useSuperJson), + query: req.query as Record, + requestBody: req.body, prisma, modelMeta: options.modelMeta, zodSchemas, logger: options.logger, }); - res.status(r.status).send(marshalToObject(r.body, useSuperJson)); + res.status(r.status).send(r.body); } catch (err) { - res.status(500).send(marshalToObject({ message: `An unhandled error occurred: ${err}` }, useSuperJson)); + res.status(500).send({ message: `An unhandled error occurred: ${err}` }); } }; } diff --git a/packages/server/src/sveltekit/handler.ts b/packages/server/src/sveltekit/handler.ts index b4224c46a..2dbdf7e1d 100644 --- a/packages/server/src/sveltekit/handler.ts +++ b/packages/server/src/sveltekit/handler.ts @@ -1,10 +1,9 @@ import type { Handle, RequestEvent } from '@sveltejs/kit'; +import type { ZodSchemas } from '@zenstackhq/runtime'; import { DbClientContract } from '@zenstackhq/runtime'; import RPCApiHandler from '../api/rpc'; import { logInfo } from '../api/utils'; import { AdapterBaseOptions } from '../types'; -import { buildUrlQuery, marshalToString, unmarshalFromString } from '../utils'; -import type { ZodSchemas } from '@zenstackhq/runtime/enhancements/types'; /** * SvelteKit request handler options @@ -38,34 +37,17 @@ export default function createHandler(options: HandlerOptions): Handle { } const requestHanler = options.handler ?? RPCApiHandler(); - const useSuperJson = options.useSuperJson === true; + if (options.useSuperJson !== undefined) { + console.warn( + 'The option "useSuperJson" is deprecated. The server APIs automatically use superjson for serialization.' + ); + } return async ({ event, resolve }) => { if (event.url.pathname.startsWith(options.prefix)) { const prisma = (await options.getPrisma(event)) as DbClientContract; if (!prisma) { - return new Response( - marshalToString({ message: 'unable to get prisma from request context' }, useSuperJson), - { - status: 400, - headers: { - 'content-type': 'application/json', - }, - } - ); - } - - const queryObj: Record = {}; - for (const key of event.url.searchParams.keys()) { - const values = event.url.searchParams.getAll(key); - queryObj[key] = values; - } - - let query: Record = {}; - try { - query = buildUrlQuery(queryObj, useSuperJson); - } catch { - return new Response(marshalToString({ message: 'invalid query parameters' }, useSuperJson), { + return new Response(JSON.stringify({ message: 'unable to get prisma from request context' }), { status: 400, headers: { 'content-type': 'application/json', @@ -73,11 +55,12 @@ export default function createHandler(options: HandlerOptions): Handle { }); } + const query = Object.fromEntries(event.url.searchParams); let requestBody: unknown; if (event.request.body) { const text = await event.request.text(); if (text) { - requestBody = unmarshalFromString(text, useSuperJson); + requestBody = JSON.parse(text); } } @@ -94,14 +77,14 @@ export default function createHandler(options: HandlerOptions): Handle { modelMeta: options.modelMeta, }); - return new Response(marshalToString(r.body, useSuperJson), { + return new Response(JSON.stringify(r.body), { status: r.status, headers: { 'content-type': 'application/json', }, }); } catch (err) { - return new Response(marshalToString({ message: `An unhandled error occurred: ${err}` }, useSuperJson), { + return new Response(JSON.stringify({ message: `An unhandled error occurred: ${err}` }), { status: 500, headers: { 'content-type': 'application/json', diff --git a/packages/server/src/types.ts b/packages/server/src/types.ts index 58de799a9..ed307ab2e 100644 --- a/packages/server/src/types.ts +++ b/packages/server/src/types.ts @@ -1,5 +1,5 @@ +import type { ModelMeta, ZodSchemas } from '@zenstackhq/runtime'; import { DbClientContract } from '@zenstackhq/runtime'; -import type { ModelMeta, ZodSchemas } from '@zenstackhq/runtime/enhancements/types'; type LoggerMethod = (message: string, code?: string) => void; diff --git a/packages/server/src/utils.ts b/packages/server/src/utils.ts deleted file mode 100644 index c68d161d3..000000000 --- a/packages/server/src/utils.ts +++ /dev/null @@ -1,87 +0,0 @@ -import superjson from 'superjson'; - -/** - * Marshal an object to string - */ -export function marshalToString(value: unknown, useSuperJson = false) { - return useSuperJson ? superjson.stringify(value) : JSON.stringify(value); -} - -/** - * Marshals an object - */ -export function marshalToObject(value: unknown, useSuperJson = false) { - return useSuperJson ? JSON.parse(superjson.stringify(value)) : value; -} - -/** - * Unmarshal a string to object - */ -export function unmarshalFromString(value: string, useSuperJson = false) { - if (value === undefined || value === null) { - return value; - } - - const j = JSON.parse(value); - if (useSuperJson) { - if (j?.json) { - // parse with superjson - return superjson.parse(value); - } else { - // parse as regular json - return j; - } - } else { - return j; - } -} - -/** - * Unmarshal an object - */ -export function unmarshalFromObject(value: unknown, useSuperJson = false) { - if (value === undefined || value === null) { - return value; - } - - if (useSuperJson) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if ((value as any).json) { - // parse with superjson - return superjson.parse(JSON.stringify(value)); - } else { - // parse as regular json - return value; - } - } else { - return value; - } -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function buildUrlQuery(query: unknown, useSuperJson: boolean) { - const result: Record = {}; - - if (typeof query !== 'object' || query === null) { - return result; - } - - for (const [key, v] of Object.entries(query)) { - if (typeof v !== 'string' && !Array.isArray(v)) { - continue; - } - - let value = v as string | string[]; - - if (key === 'q') { - // handle parameter marshalling (potentially using superjson) - if (Array.isArray(value)) { - value = value.map((v) => JSON.stringify(unmarshalFromString(v as string, useSuperJson))); - } else { - value = JSON.stringify(unmarshalFromString(value as string, useSuperJson)); - } - } - result[key] = value; - } - return result; -} diff --git a/packages/server/tests/adapter/express.test.ts b/packages/server/tests/adapter/express.test.ts index 7121edcf9..83647c5ab 100644 --- a/packages/server/tests/adapter/express.test.ts +++ b/packages/server/tests/adapter/express.test.ts @@ -4,7 +4,6 @@ import { loadSchema } from '@zenstackhq/testtools'; import bodyParser from 'body-parser'; import express from 'express'; -import superjson from 'superjson'; import request from 'supertest'; import RESTAPIHandler from '../../src/api/rest'; import { ZenStackMiddleware } from '../../src/express'; @@ -20,7 +19,7 @@ describe('Express adapter tests - rpc handler', () => { let r = await request(app).get(makeUrl('/api/post/findMany', { where: { id: { equals: '1' } } })); expect(r.status).toBe(200); - expect(r.body).toHaveLength(0); + expect(r.body.data).toHaveLength(0); r = await request(app) .post('/api/user/create') @@ -39,7 +38,8 @@ describe('Express adapter tests - rpc handler', () => { }); expect(r.status).toBe(201); - expect(r.body).toEqual( + const data = r.body.data; + expect(data).toEqual( expect.objectContaining({ email: 'user1@abc.com', posts: expect.arrayContaining([ @@ -49,7 +49,6 @@ describe('Express adapter tests - rpc handler', () => { }) ); // aux fields should have been removed - const data = r.body; expect(data.zenstack_guard).toBeUndefined(); expect(data.zenstack_transaction).toBeUndefined(); expect(data.posts[0].zenstack_guard).toBeUndefined(); @@ -57,29 +56,29 @@ describe('Express adapter tests - rpc handler', () => { r = await request(app).get(makeUrl('/api/post/findMany')); expect(r.status).toBe(200); - expect(r.body).toHaveLength(2); + expect(r.body.data).toHaveLength(2); r = await request(app).get(makeUrl('/api/post/findMany', { where: { viewCount: { gt: 1 } } })); expect(r.status).toBe(200); - expect(r.body).toHaveLength(1); + expect(r.body.data).toHaveLength(1); r = await request(app) .put('/api/user/update') .send({ where: { id: 'user1' }, data: { email: 'user1@def.com' } }); expect(r.status).toBe(200); - expect(r.body.email).toBe('user1@def.com'); + expect(r.body.data.email).toBe('user1@def.com'); r = await request(app).get(makeUrl('/api/post/count', { where: { viewCount: { gt: 1 } } })); expect(r.status).toBe(200); - expect(r.body).toBe(1); + expect(r.body.data).toBe(1); r = await request(app).get(makeUrl('/api/post/aggregate', { _sum: { viewCount: true } })); expect(r.status).toBe(200); - expect(r.body._sum.viewCount).toBe(3); + expect(r.body.data._sum.viewCount).toBe(3); r = await request(app).get(makeUrl('/api/post/groupBy', { by: ['published'], _sum: { viewCount: true } })); expect(r.status).toBe(200); - expect(r.body).toEqual( + expect(r.body.data).toEqual( expect.arrayContaining([ expect.objectContaining({ published: true, _sum: { viewCount: 1 } }), expect.objectContaining({ published: false, _sum: { viewCount: 2 } }), @@ -88,7 +87,7 @@ describe('Express adapter tests - rpc handler', () => { r = await request(app).delete(makeUrl('/api/user/deleteMany', { where: { id: 'user1' } })); expect(r.status).toBe(200); - expect(r.body.count).toBe(1); + expect(r.body.data.count).toBe(1); }); it('invalid path or args', async () => { @@ -107,94 +106,8 @@ describe('Express adapter tests - rpc handler', () => { r = await request(app).get('/api/post/findMany?q=abc'); expect(r.status).toBe(400); }); - - it('run plugin superjson', async () => { - const { prisma, zodSchemas } = await loadSchema(schema); - - const app = express(); - app.use(bodyParser.json()); - app.use('/api', ZenStackMiddleware({ getPrisma: () => prisma, zodSchemas, useSuperJson: true })); - - let r = await request(app).get(makeUrl('/api/post/findMany', { where: { id: { equals: '1' } } }, true)); - expect(r.status).toBe(200); - expect(unmarshal(r.body)).toHaveLength(0); - - r = await request(app) - .post('/api/user/create') - .send({ - include: { posts: true }, - data: { - id: 'user1', - email: 'user1@abc.com', - posts: { - create: [ - { title: 'post1', published: true, viewCount: 1 }, - { title: 'post2', published: false, viewCount: 2 }, - ], - }, - }, - }); - - expect(r.status).toBe(201); - expect(unmarshal(r.body)).toEqual( - expect.objectContaining({ - email: 'user1@abc.com', - posts: expect.arrayContaining([ - expect.objectContaining({ title: 'post1' }), - expect.objectContaining({ title: 'post2' }), - ]), - }) - ); - // aux fields should have been removed - const data = unmarshal(r.body); - expect(data.zenstack_guard).toBeUndefined(); - expect(data.zenstack_transaction).toBeUndefined(); - expect(data.posts[0].zenstack_guard).toBeUndefined(); - expect(data.posts[0].zenstack_transaction).toBeUndefined(); - - r = await request(app).get(makeUrl('/api/post/findMany')); - expect(r.status).toBe(200); - expect(unmarshal(r.body)).toHaveLength(2); - - r = await request(app).get(makeUrl('/api/post/findMany', { where: { viewCount: { gt: 1 } } }, true)); - expect(r.status).toBe(200); - expect(unmarshal(r.body)).toHaveLength(1); - - r = await request(app) - .put('/api/user/update') - .send({ where: { id: 'user1' }, data: { email: 'user1@def.com' } }); - expect(r.status).toBe(200); - expect(unmarshal(r.body).email).toBe('user1@def.com'); - - r = await request(app).get(makeUrl('/api/post/count', { where: { viewCount: { gt: 1 } } }, true)); - expect(r.status).toBe(200); - expect(unmarshal(r.body)).toBe(1); - - r = await request(app).get(makeUrl('/api/post/aggregate', { _sum: { viewCount: true } }, true)); - expect(r.status).toBe(200); - expect(unmarshal(r.body)._sum.viewCount).toBe(3); - - r = await request(app).get( - makeUrl('/api/post/groupBy', { by: ['published'], _sum: { viewCount: true } }, true) - ); - expect(r.status).toBe(200); - expect(unmarshal(r.body)).toEqual( - expect.arrayContaining([ - expect.objectContaining({ published: true, _sum: { viewCount: 1 } }), - expect.objectContaining({ published: false, _sum: { viewCount: 2 } }), - ]) - ); - - r = await request(app).delete(makeUrl('/api/user/deleteMany', { where: { id: 'user1' } }, true)); - expect(r.status).toBe(200); - expect(unmarshal(r.body).count).toBe(1); - }); }); -function unmarshal(value: any) { - return superjson.parse(JSON.stringify(value)) as any; -} - describe('Express adapter tests - rest handler', () => { it('run middleware', async () => { const { prisma, zodSchemas, modelMeta } = await loadSchema(schema); diff --git a/packages/server/tests/adapter/fastify.test.ts b/packages/server/tests/adapter/fastify.test.ts index 3e0c12a6a..89aee54cf 100644 --- a/packages/server/tests/adapter/fastify.test.ts +++ b/packages/server/tests/adapter/fastify.test.ts @@ -3,7 +3,6 @@ import { loadSchema } from '@zenstackhq/testtools'; import fastify from 'fastify'; -import superjson from 'superjson'; import Rest from '../../src/api/rest'; import RPC from '../../src/api/rpc'; import { ZenStackFastifyPlugin } from '../../src/fastify'; @@ -26,7 +25,7 @@ describe('Fastify adapter tests - rpc handler', () => { url: makeUrl('/api/post/findMany', { where: { id: { equals: '1' } } }), }); expect(r.statusCode).toBe(200); - expect(r.json()).toHaveLength(0); + expect(r.json().data).toHaveLength(0); r = await app.inject({ method: 'POST', @@ -46,7 +45,8 @@ describe('Fastify adapter tests - rpc handler', () => { }, }); expect(r.statusCode).toBe(201); - expect(r.json()).toEqual( + const data = r.json().data; + expect(data).toEqual( expect.objectContaining({ email: 'user1@abc.com', posts: expect.arrayContaining([ @@ -56,7 +56,6 @@ describe('Fastify adapter tests - rpc handler', () => { }) ); // aux fields should have been removed - const data = r.json(); expect(data.zenstack_guard).toBeUndefined(); expect(data.zenstack_transaction).toBeUndefined(); expect(data.posts[0].zenstack_guard).toBeUndefined(); @@ -67,14 +66,14 @@ describe('Fastify adapter tests - rpc handler', () => { url: makeUrl('/api/post/findMany'), }); expect(r.statusCode).toBe(200); - expect(r.json()).toHaveLength(2); + expect(r.json().data).toHaveLength(2); r = await app.inject({ method: 'GET', url: makeUrl('/api/post/findMany', { where: { viewCount: { gt: 1 } } }), }); expect(r.statusCode).toBe(200); - expect(r.json()).toHaveLength(1); + expect(r.json().data).toHaveLength(1); r = await app.inject({ method: 'PUT', @@ -82,28 +81,28 @@ describe('Fastify adapter tests - rpc handler', () => { payload: { where: { id: 'user1' }, data: { email: 'user1@def.com' } }, }); expect(r.statusCode).toBe(200); - expect(r.json().email).toBe('user1@def.com'); + expect(r.json().data.email).toBe('user1@def.com'); r = await app.inject({ method: 'GET', url: makeUrl('/api/post/count', { where: { viewCount: { gt: 1 } } }), }); expect(r.statusCode).toBe(200); - expect(r.json()).toBe(1); + expect(r.json().data).toBe(1); r = await app.inject({ method: 'GET', url: makeUrl('/api/post/aggregate', { _sum: { viewCount: true } }), }); expect(r.statusCode).toBe(200); - expect(r.json()._sum.viewCount).toBe(3); + expect(r.json().data._sum.viewCount).toBe(3); r = await app.inject({ method: 'GET', url: makeUrl('/api/post/groupBy', { by: ['published'], _sum: { viewCount: true } }), }); expect(r.statusCode).toBe(200); - expect(r.json()).toEqual( + expect(r.json().data).toEqual( expect.arrayContaining([ expect.objectContaining({ published: true, _sum: { viewCount: 1 } }), expect.objectContaining({ published: false, _sum: { viewCount: 2 } }), @@ -115,7 +114,7 @@ describe('Fastify adapter tests - rpc handler', () => { url: makeUrl('/api/user/deleteMany', { where: { id: 'user1' } }), }); expect(r.statusCode).toBe(200); - expect(r.json().count).toBe(1); + expect(r.json().data.count).toBe(1); }); it('invalid path or args', async () => { @@ -147,121 +146,8 @@ describe('Fastify adapter tests - rpc handler', () => { }); expect(r.statusCode).toBe(400); }); - - it('run plugin superjson', async () => { - const { prisma, zodSchemas } = await loadSchema(schema); - - const app = fastify(); - app.register(ZenStackFastifyPlugin, { - prefix: '/api', - getPrisma: () => prisma, - zodSchemas, - handler: RPC(), - useSuperJson: true, - }); - - let r = await app.inject({ - method: 'GET', - url: makeUrl('/api/post/findMany', { where: { id: { equals: '1' } } }, true), - }); - expect(r.statusCode).toBe(200); - expect(unmarshal(r.json())).toHaveLength(0); - - r = await app.inject({ - method: 'POST', - url: '/api/user/create', - payload: { - include: { posts: true }, - data: { - id: 'user1', - email: 'user1@abc.com', - posts: { - create: [ - { title: 'post1', published: true, viewCount: 1 }, - { title: 'post2', published: false, viewCount: 2 }, - ], - }, - }, - }, - }); - expect(r.statusCode).toBe(201); - expect(unmarshal(r.json())).toEqual( - expect.objectContaining({ - email: 'user1@abc.com', - posts: expect.arrayContaining([ - expect.objectContaining({ title: 'post1' }), - expect.objectContaining({ title: 'post2' }), - ]), - }) - ); - // aux fields should have been removed - const data = unmarshal(r.json()); - expect(data.zenstack_guard).toBeUndefined(); - expect(data.zenstack_transaction).toBeUndefined(); - expect(data.posts[0].zenstack_guard).toBeUndefined(); - expect(data.posts[0].zenstack_transaction).toBeUndefined(); - - r = await app.inject({ - method: 'GET', - url: makeUrl('/api/post/findMany'), - }); - expect(r.statusCode).toBe(200); - expect(unmarshal(r.json())).toHaveLength(2); - - r = await app.inject({ - method: 'GET', - url: makeUrl('/api/post/findMany', { where: { viewCount: { gt: 1 } } }, true), - }); - expect(r.statusCode).toBe(200); - expect(unmarshal(r.json())).toHaveLength(1); - - r = await app.inject({ - method: 'PUT', - url: '/api/user/update', - payload: { where: { id: 'user1' }, data: { email: 'user1@def.com' } }, - }); - expect(r.statusCode).toBe(200); - expect(unmarshal(r.json()).email).toBe('user1@def.com'); - - r = await app.inject({ - method: 'GET', - url: makeUrl('/api/post/count', { where: { viewCount: { gt: 1 } } }, true), - }); - expect(r.statusCode).toBe(200); - expect(unmarshal(r.json())).toBe(1); - - r = await app.inject({ - method: 'GET', - url: makeUrl('/api/post/aggregate', { _sum: { viewCount: true } }, true), - }); - expect(r.statusCode).toBe(200); - expect(unmarshal(r.json())._sum.viewCount).toBe(3); - - r = await app.inject({ - method: 'GET', - url: makeUrl('/api/post/groupBy', { by: ['published'], _sum: { viewCount: true } }, true), - }); - expect(r.statusCode).toBe(200); - expect(unmarshal(r.json())).toEqual( - expect.arrayContaining([ - expect.objectContaining({ published: true, _sum: { viewCount: 1 } }), - expect.objectContaining({ published: false, _sum: { viewCount: 2 } }), - ]) - ); - - r = await app.inject({ - method: 'DELETE', - url: makeUrl('/api/user/deleteMany', { where: { id: 'user1' } }, true), - }); - expect(r.statusCode).toBe(200); - expect(unmarshal(r.json()).count).toBe(1); - }); }); -function unmarshal(value: any) { - return superjson.parse(JSON.stringify(value)) as any; -} - describe('Fastify adapter tests - rest handler', () => { it('run plugin regular json', async () => { const { prisma, zodSchemas, modelMeta } = await loadSchema(schema); diff --git a/packages/server/tests/adapter/next.test.ts b/packages/server/tests/adapter/next.test.ts index 013468403..46db1de78 100644 --- a/packages/server/tests/adapter/next.test.ts +++ b/packages/server/tests/adapter/next.test.ts @@ -2,7 +2,6 @@ import { loadSchema } from '@zenstackhq/testtools'; import { createServer, RequestListener } from 'http'; import { apiResolver } from 'next/dist/server/api-utils/node'; -import superjson from 'superjson'; import request from 'supertest'; import { NextRequestHandler, RequestHandlerOptions } from '../../src/next'; import Rest from '../../src/api/rest'; @@ -12,7 +11,7 @@ function makeTestClient(apiPath: string, options: RequestHandlerOptions, qArg?: const query = { path: pathParts, - ...(qArg ? { q: superjson.stringify(qArg) } : {}), + ...(qArg ? { q: JSON.stringify(qArg) } : {}), ...otherArgs, }; @@ -47,7 +46,7 @@ describe('Next.js adapter tests - rpc handler', () => { process.chdir(origDir); }); - it('simple crud regular json', async () => { + it('simple crud', async () => { const model = ` model M { id String @id @default(cuid()) @@ -57,157 +56,112 @@ model M { const { prisma } = await loadSchema(model); - await makeTestClient('/m/create', { getPrisma: () => prisma, useSuperJson: true }) + await makeTestClient('/m/create', { getPrisma: () => prisma }) .post('/') .send({ data: { id: '1', value: 1 } }) .expect(201) .expect((resp) => { - expect(resp.body.json.value).toBe(1); + expect(resp.body.data.value).toBe(1); }); - await makeTestClient('/m/findUnique', { getPrisma: () => prisma, useSuperJson: true }, { where: { id: '1' } }) + await makeTestClient('/m/findUnique', { getPrisma: () => prisma }, { where: { id: '1' } }) .get('/') .expect(200) .expect((resp) => { - expect(resp.body.json.value).toBe(1); + expect(resp.body.data.value).toBe(1); }); - await makeTestClient('/m/findFirst', { getPrisma: () => prisma, useSuperJson: true }, { where: { id: '1' } }) + await makeTestClient('/m/findFirst', { getPrisma: () => prisma }, { where: { id: '1' } }) .get('/') .expect(200) .expect((resp) => { - expect(resp.body.json.value).toBe(1); + expect(resp.body.data.value).toBe(1); }); - await makeTestClient('/m/findMany', { getPrisma: () => prisma, useSuperJson: true }, {}) + await makeTestClient('/m/findMany', { getPrisma: () => prisma }, {}) .get('/') .expect(200) .expect((resp) => { - expect(resp.body.json).toHaveLength(1); + expect(resp.body.data).toHaveLength(1); }); - await makeTestClient('/m/update', { getPrisma: () => prisma, useSuperJson: true }) + await makeTestClient('/m/update', { getPrisma: () => prisma }) .put('/') .send({ where: { id: '1' }, data: { value: 2 } }) .expect(200) .expect((resp) => { - expect(resp.body.json.value).toBe(2); + expect(resp.body.data.value).toBe(2); }); - await makeTestClient('/m/updateMany', { getPrisma: () => prisma, useSuperJson: true }) + await makeTestClient('/m/updateMany', { getPrisma: () => prisma }) .put('/') .send({ data: { value: 4 } }) .expect(200) .expect((resp) => { - expect(resp.body.json.count).toBe(1); + expect(resp.body.data.count).toBe(1); }); - await makeTestClient('/m/upsert', { getPrisma: () => prisma, useSuperJson: true }) + await makeTestClient('/m/upsert', { getPrisma: () => prisma }) .post('/') .send({ where: { id: '2' }, create: { id: '2', value: 2 }, update: { value: 3 } }) .expect(201) .expect((resp) => { - expect(resp.body.json.value).toBe(2); + expect(resp.body.data.value).toBe(2); }); - await makeTestClient('/m/upsert', { getPrisma: () => prisma, useSuperJson: true }) + await makeTestClient('/m/upsert', { getPrisma: () => prisma }) .post('/') .send({ where: { id: '2' }, create: { id: '2', value: 2 }, update: { value: 3 } }) .expect(201) .expect((resp) => { - expect(resp.body.json.value).toBe(3); + expect(resp.body.data.value).toBe(3); }); - await makeTestClient('/m/count', { getPrisma: () => prisma, useSuperJson: true }, { where: { id: '1' } }) + await makeTestClient('/m/count', { getPrisma: () => prisma }, { where: { id: '1' } }) .get('/') .expect(200) .expect((resp) => { - expect(resp.body.json).toBe(1); + expect(resp.body.data).toBe(1); }); - await makeTestClient('/m/count', { getPrisma: () => prisma, useSuperJson: true }, {}) + await makeTestClient('/m/count', { getPrisma: () => prisma }, {}) .get('/') .expect(200) .expect((resp) => { - expect(resp.body.json).toBe(2); + expect(resp.body.data).toBe(2); }); - await makeTestClient('/m/aggregate', { getPrisma: () => prisma, useSuperJson: true }, { _sum: { value: true } }) + await makeTestClient('/m/aggregate', { getPrisma: () => prisma }, { _sum: { value: true } }) .get('/') .expect(200) .expect((resp) => { - expect(resp.body.json._sum.value).toBe(7); + expect(resp.body.data._sum.value).toBe(7); }); - await makeTestClient( - '/m/groupBy', - { getPrisma: () => prisma, useSuperJson: true }, - { by: ['id'], _sum: { value: true } } - ) + await makeTestClient('/m/groupBy', { getPrisma: () => prisma }, { by: ['id'], _sum: { value: true } }) .get('/') .expect(200) .expect((resp) => { - const data = resp.body.json; + const data = resp.body.data; expect(data).toHaveLength(2); expect(data.find((item: any) => item.id === '1')._sum.value).toBe(4); expect(data.find((item: any) => item.id === '2')._sum.value).toBe(3); }); - await makeTestClient('/m/delete', { getPrisma: () => prisma, useSuperJson: true }, { where: { id: '1' } }) + await makeTestClient('/m/delete', { getPrisma: () => prisma }, { where: { id: '1' } }) .del('/') .expect(200); expect(await prisma.m.count()).toBe(1); - await makeTestClient('/m/deleteMany', { getPrisma: () => prisma, useSuperJson: true }, {}) + await makeTestClient('/m/deleteMany', { getPrisma: () => prisma }, {}) .del('/') .expect(200) .expect((resp) => { - expect(resp.body.json.count).toBe(1); + expect(resp.body.data.count).toBe(1); }); expect(await prisma.m.count()).toBe(0); }); - it('simple crud superjson', async () => { - const model = ` -model M { - id String @id @default(cuid()) - value Int -} - `; - - const { prisma } = await loadSchema(model); - - await makeTestClient('/m/create', { getPrisma: () => prisma, useSuperJson: true }) - .post('/') - .send(marshal({ data: { id: '1', value: 1 } })) - .expect(201) - .expect((resp) => { - expect(resp.body.json.value).toBe(1); - }); - - await makeTestClient('/m/findUnique', { getPrisma: () => prisma, useSuperJson: true }, { where: { id: '1' } }) - .get('/') - .expect(200) - .expect((resp) => { - expect(resp.body.json.value).toBe(1); - }); - - await makeTestClient('/m/findMany', { getPrisma: () => prisma, useSuperJson: true }, {}) - .get('/') - .expect(200) - .expect((resp) => { - expect(resp.body.json).toHaveLength(1); - }); - - await makeTestClient('/m/update', { getPrisma: () => prisma, useSuperJson: true }) - .put('/') - .send(marshal({ where: { id: '1' }, data: { value: 2 } })) - .expect(200) - .expect((resp) => { - expect(resp.body.json.value).toBe(2); - }); - }); - it('access policy crud', async () => { const model = ` model M { @@ -223,54 +177,46 @@ model M { const { withPresets } = await loadSchema(model); - await makeTestClient('/m/create', { getPrisma: () => withPresets(), useSuperJson: true }) + await makeTestClient('/m/create', { getPrisma: () => withPresets() }) .post('/m/create') .send({ data: { value: 0 } }) .expect(403) .expect((resp) => { - expect(resp.body.json.reason).toBe('RESULT_NOT_READABLE'); + expect(resp.body.error.reason).toBe('RESULT_NOT_READABLE'); }); - await makeTestClient('/m/create', { getPrisma: () => withPresets(), useSuperJson: true }) + await makeTestClient('/m/create', { getPrisma: () => withPresets() }) .post('/') .send({ data: { id: '1', value: 1 } }) .expect(201); - await makeTestClient('/m/findMany', { getPrisma: () => withPresets(), useSuperJson: true }) + await makeTestClient('/m/findMany', { getPrisma: () => withPresets() }) .get('/') .expect(200) .expect((resp) => { - expect(resp.body.json).toHaveLength(1); + expect(resp.body.data).toHaveLength(1); }); - await makeTestClient('/m/update', { getPrisma: () => withPresets(), useSuperJson: true }) + await makeTestClient('/m/update', { getPrisma: () => withPresets() }) .put('/') .send({ where: { id: '1' }, data: { value: 0 } }) .expect(403); - await makeTestClient('/m/update', { getPrisma: () => withPresets(), useSuperJson: true }) + await makeTestClient('/m/update', { getPrisma: () => withPresets() }) .put('/') .send({ where: { id: '1' }, data: { value: 2 } }) .expect(200); - await makeTestClient( - '/m/delete', - { getPrisma: () => withPresets(), useSuperJson: true }, - { where: { id: '1' } } - ) + await makeTestClient('/m/delete', { getPrisma: () => withPresets() }, { where: { id: '1' } }) .del('/') .expect(403); - await makeTestClient('/m/update', { getPrisma: () => withPresets(), useSuperJson: true }) + await makeTestClient('/m/update', { getPrisma: () => withPresets() }) .put('/') .send({ where: { id: '1' }, data: { value: 3 } }) .expect(200); - await makeTestClient( - '/m/delete', - { getPrisma: () => withPresets(), useSuperJson: true }, - { where: { id: '1' } } - ) + await makeTestClient('/m/delete', { getPrisma: () => withPresets() }, { where: { id: '1' } }) .del('/') .expect(200); }); @@ -340,7 +286,3 @@ model M { expect(await prisma.m.count()).toBe(0); }); }); - -function marshal(data: unknown) { - return JSON.parse(superjson.stringify(data)); -} diff --git a/packages/server/tests/adapter/sveltekit.test.ts b/packages/server/tests/adapter/sveltekit.test.ts index ae7a9e20b..c56128498 100644 --- a/packages/server/tests/adapter/sveltekit.test.ts +++ b/packages/server/tests/adapter/sveltekit.test.ts @@ -15,7 +15,7 @@ describe('SvelteKit adapter tests - rpc handler', () => { let r = await handler(makeRequest('GET', makeUrl('/api/post/findMany', { where: { id: { equals: '1' } } }))); expect(r.status).toBe(200); - expect(await unmarshal(r)).toHaveLength(0); + expect((await unmarshal(r)).data).toHaveLength(0); r = await handler( makeRequest('POST', '/api/user/create', { @@ -33,7 +33,7 @@ describe('SvelteKit adapter tests - rpc handler', () => { }) ); expect(r.status).toBe(201); - expect(await unmarshal(r)).toMatchObject({ + expect((await unmarshal(r)).data).toMatchObject({ email: 'user1@abc.com', posts: expect.arrayContaining([ expect.objectContaining({ title: 'post1' }), @@ -43,31 +43,31 @@ describe('SvelteKit adapter tests - rpc handler', () => { r = await handler(makeRequest('GET', makeUrl('/api/post/findMany'))); expect(r.status).toBe(200); - expect(await unmarshal(r)).toHaveLength(2); + expect((await unmarshal(r)).data).toHaveLength(2); r = await handler(makeRequest('GET', makeUrl('/api/post/findMany', { where: { viewCount: { gt: 1 } } }))); expect(r.status).toBe(200); - expect(await unmarshal(r)).toHaveLength(1); + expect((await unmarshal(r)).data).toHaveLength(1); r = await handler( makeRequest('PUT', '/api/user/update', { where: { id: 'user1' }, data: { email: 'user1@def.com' } }) ); expect(r.status).toBe(200); - expect((await unmarshal(r)).email).toBe('user1@def.com'); + expect((await unmarshal(r)).data.email).toBe('user1@def.com'); r = await handler(makeRequest('GET', makeUrl('/api/post/count', { where: { viewCount: { gt: 1 } } }))); expect(r.status).toBe(200); - expect(await unmarshal(r)).toBe(1); + expect((await unmarshal(r)).data).toBe(1); r = await handler(makeRequest('GET', makeUrl('/api/post/aggregate', { _sum: { viewCount: true } }))); expect(r.status).toBe(200); - expect((await unmarshal(r))._sum.viewCount).toBe(3); + expect((await unmarshal(r)).data._sum.viewCount).toBe(3); r = await handler( makeRequest('GET', makeUrl('/api/post/groupBy', { by: ['published'], _sum: { viewCount: true } })) ); expect(r.status).toBe(200); - expect(await unmarshal(r)).toEqual( + expect((await unmarshal(r)).data).toEqual( expect.arrayContaining([ expect.objectContaining({ published: true, _sum: { viewCount: 1 } }), expect.objectContaining({ published: false, _sum: { viewCount: 2 } }), @@ -76,80 +76,7 @@ describe('SvelteKit adapter tests - rpc handler', () => { r = await handler(makeRequest('DELETE', makeUrl('/api/user/deleteMany', { where: { id: 'user1' } }))); expect(r.status).toBe(200); - expect((await unmarshal(r)).count).toBe(1); - }); - - it('run hooks superjson', async () => { - const { prisma, zodSchemas } = await loadSchema(schema); - - const handler = SvelteKitHandler({ prefix: '/api', getPrisma: () => prisma, zodSchemas, useSuperJson: true }); - - let r = await handler( - makeRequest('GET', makeUrl('/api/post/findMany', { where: { id: { equals: '1' } } }, true)) - ); - expect(r.status).toBe(200); - expect(await unmarshal(r, true)).toHaveLength(0); - - r = await handler( - makeRequest('POST', '/api/user/create', { - include: { posts: true }, - data: { - id: 'user1', - email: 'user1@abc.com', - posts: { - create: [ - { title: 'post1', published: true, viewCount: 1 }, - { title: 'post2', published: false, viewCount: 2 }, - ], - }, - }, - }) - ); - expect(r.status).toBe(201); - expect(await unmarshal(r, true)).toMatchObject({ - email: 'user1@abc.com', - posts: expect.arrayContaining([ - expect.objectContaining({ title: 'post1' }), - expect.objectContaining({ title: 'post2' }), - ]), - }); - - r = await handler(makeRequest('GET', makeUrl('/api/post/findMany', undefined))); - expect(r.status).toBe(200); - expect(await unmarshal(r, true)).toHaveLength(2); - - r = await handler(makeRequest('GET', makeUrl('/api/post/findMany', { where: { viewCount: { gt: 1 } } }, true))); - expect(r.status).toBe(200); - expect(await unmarshal(r, true)).toHaveLength(1); - - r = await handler( - makeRequest('PUT', '/api/user/update', { where: { id: 'user1' }, data: { email: 'user1@def.com' } }) - ); - expect(r.status).toBe(200); - expect((await unmarshal(r, true)).email).toBe('user1@def.com'); - - r = await handler(makeRequest('GET', makeUrl('/api/post/count', { where: { viewCount: { gt: 1 } } }, true))); - expect(r.status).toBe(200); - expect(await unmarshal(r, true)).toBe(1); - - r = await handler(makeRequest('GET', makeUrl('/api/post/aggregate', { _sum: { viewCount: true } }, true))); - expect(r.status).toBe(200); - expect((await unmarshal(r, true))._sum.viewCount).toBe(3); - - r = await handler( - makeRequest('GET', makeUrl('/api/post/groupBy', { by: ['published'], _sum: { viewCount: true } }, true)) - ); - expect(r.status).toBe(200); - expect(await unmarshal(r, true)).toEqual( - expect.arrayContaining([ - expect.objectContaining({ published: true, _sum: { viewCount: 1 } }), - expect.objectContaining({ published: false, _sum: { viewCount: 2 } }), - ]) - ); - - r = await handler(makeRequest('DELETE', makeUrl('/api/user/deleteMany', { where: { id: 'user1' } }, true))); - expect(r.status).toBe(200); - expect((await unmarshal(r, true)).count).toBe(1); + expect((await unmarshal(r)).data.count).toBe(1); }); }); diff --git a/packages/server/tests/api/rest-petstore.test.ts b/packages/server/tests/api/rest-petstore.test.ts index 69c9075df..4c4d477a0 100644 --- a/packages/server/tests/api/rest-petstore.test.ts +++ b/packages/server/tests/api/rest-petstore.test.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /// +import { ModelMeta } from '@zenstackhq/runtime'; import { loadSchema, run } from '@zenstackhq/testtools'; -import { ModelMeta } from '@zenstackhq/runtime/enhancements/types'; import makeHandler from '../../src/api/rest'; import { Response } from '../../src/types'; diff --git a/packages/server/tests/api/rest.test.ts b/packages/server/tests/api/rest.test.ts index 1f8e12e59..277a5d35e 100644 --- a/packages/server/tests/api/rest.test.ts +++ b/packages/server/tests/api/rest.test.ts @@ -1,17 +1,16 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /// -import { withPolicy } from '@zenstackhq/runtime'; -import { CrudFailureReason } from '@zenstackhq/runtime/constants'; -import { ModelMeta } from '@zenstackhq/runtime/enhancements/types'; +import { CrudFailureReason, ModelMeta, withPolicy } from '@zenstackhq/runtime'; import { loadSchema, run } from '@zenstackhq/testtools'; +import { Decimal } from 'decimal.js'; +import SuperJSON from 'superjson'; import makeHandler from '../../src/api/rest'; -import { Response } from '../../src/types'; let prisma: any; let zodSchemas: any; let modelMeta: ModelMeta; -let handler: (any: any) => Promise; +let handler: (any: any) => Promise<{ status: number; body: any }>; describe('REST server tests - regular prisma', () => { const schema = ` @@ -363,8 +362,8 @@ describe('REST server tests - regular prisma', () => { prisma, }); expect(r.status).toBe(200); - expect((r.body as any).data).toHaveLength(1); - expect((r.body as any).data[0]).toMatchObject({ id: 'user2' }); + expect(r.body.data).toHaveLength(1); + expect(r.body.data[0]).toMatchObject({ id: 'user2' }); // String filter r = await handler({ @@ -373,8 +372,8 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[email]']: 'user1@abc.com' }, prisma, }); - expect((r.body as any).data).toHaveLength(1); - expect((r.body as any).data[0]).toMatchObject({ id: 'user1' }); + expect(r.body.data).toHaveLength(1); + expect(r.body.data[0]).toMatchObject({ id: 'user1' }); r = await handler({ method: 'get', @@ -382,8 +381,8 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[email$contains]']: '1@abc' }, prisma, }); - expect((r.body as any).data).toHaveLength(1); - expect((r.body as any).data[0]).toMatchObject({ id: 'user1' }); + expect(r.body.data).toHaveLength(1); + expect(r.body.data[0]).toMatchObject({ id: 'user1' }); r = await handler({ method: 'get', @@ -391,7 +390,7 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[email$contains]']: '1@bc' }, prisma, }); - expect((r.body as any).data).toHaveLength(0); + expect(r.body.data).toHaveLength(0); r = await handler({ method: 'get', @@ -399,8 +398,8 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[email$startsWith]']: 'user1' }, prisma, }); - expect((r.body as any).data).toHaveLength(1); - expect((r.body as any).data[0]).toMatchObject({ id: 'user1' }); + expect(r.body.data).toHaveLength(1); + expect(r.body.data[0]).toMatchObject({ id: 'user1' }); r = await handler({ method: 'get', @@ -408,7 +407,7 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[email$startsWith]']: 'ser1' }, prisma, }); - expect((r.body as any).data).toHaveLength(0); + expect(r.body.data).toHaveLength(0); r = await handler({ method: 'get', @@ -416,8 +415,8 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[email$endsWith]']: '1@abc.com' }, prisma, }); - expect((r.body as any).data).toHaveLength(1); - expect((r.body as any).data[0]).toMatchObject({ id: 'user1' }); + expect(r.body.data).toHaveLength(1); + expect(r.body.data[0]).toMatchObject({ id: 'user1' }); r = await handler({ method: 'get', @@ -425,7 +424,7 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[email$endsWith]']: '1@abc' }, prisma, }); - expect((r.body as any).data).toHaveLength(0); + expect(r.body.data).toHaveLength(0); // Int filter r = await handler({ @@ -434,8 +433,8 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[viewCount]']: '1' }, prisma, }); - expect((r.body as any).data).toHaveLength(1); - expect((r.body as any).data[0]).toMatchObject({ id: 2 }); + expect(r.body.data).toHaveLength(1); + expect(r.body.data[0]).toMatchObject({ id: 2 }); r = await handler({ method: 'get', @@ -443,8 +442,8 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[viewCount$gt]']: '0' }, prisma, }); - expect((r.body as any).data).toHaveLength(1); - expect((r.body as any).data[0]).toMatchObject({ id: 2 }); + expect(r.body.data).toHaveLength(1); + expect(r.body.data[0]).toMatchObject({ id: 2 }); r = await handler({ method: 'get', @@ -452,8 +451,8 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[viewCount$gte]']: '1' }, prisma, }); - expect((r.body as any).data).toHaveLength(1); - expect((r.body as any).data[0]).toMatchObject({ id: 2 }); + expect(r.body.data).toHaveLength(1); + expect(r.body.data[0]).toMatchObject({ id: 2 }); r = await handler({ method: 'get', @@ -461,7 +460,7 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[viewCount$lt]']: '0' }, prisma, }); - expect((r.body as any).data).toHaveLength(0); + expect(r.body.data).toHaveLength(0); r = await handler({ method: 'get', @@ -469,8 +468,8 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[viewCount$lte]']: '0' }, prisma, }); - expect((r.body as any).data).toHaveLength(1); - expect((r.body as any).data[0]).toMatchObject({ id: 1 }); + expect(r.body.data).toHaveLength(1); + expect(r.body.data[0]).toMatchObject({ id: 1 }); // Boolean filter r = await handler({ @@ -479,8 +478,8 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[published]']: 'true' }, prisma, }); - expect((r.body as any).data).toHaveLength(1); - expect((r.body as any).data[0]).toMatchObject({ id: 2 }); + expect(r.body.data).toHaveLength(1); + expect(r.body.data[0]).toMatchObject({ id: 2 }); // deep to-one filter r = await handler({ @@ -489,7 +488,7 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[author][email]']: 'user1@abc.com' }, prisma, }); - expect((r.body as any).data).toHaveLength(1); + expect(r.body.data).toHaveLength(1); // deep to-many filter r = await handler({ @@ -498,7 +497,7 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[posts][published]']: 'true' }, prisma, }); - expect((r.body as any).data).toHaveLength(1); + expect(r.body.data).toHaveLength(1); // filter to empty r = await handler({ @@ -507,7 +506,7 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[id]']: 'user3' }, prisma, }); - expect((r.body as any).data).toHaveLength(0); + expect(r.body.data).toHaveLength(0); // to-many relation collection filter r = await handler({ @@ -516,8 +515,8 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[posts]']: '2' }, prisma, }); - expect((r.body as any).data).toHaveLength(1); - expect((r.body as any).data[0]).toMatchObject({ id: 'user2' }); + expect(r.body.data).toHaveLength(1); + expect(r.body.data[0]).toMatchObject({ id: 'user2' }); r = await handler({ method: 'get', @@ -525,7 +524,7 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[posts]']: '1,2,3' }, prisma, }); - expect((r.body as any).data).toHaveLength(2); + expect(r.body.data).toHaveLength(2); // multi filter r = await handler({ @@ -534,7 +533,7 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[id]']: 'user1', ['filter[posts]']: '2' }, prisma, }); - expect((r.body as any).data).toHaveLength(0); + expect(r.body.data).toHaveLength(0); // to-one relation filter r = await handler({ @@ -543,8 +542,8 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[author]']: 'user1' }, prisma, }); - expect((r.body as any).data).toHaveLength(1); - expect((r.body as any).data[0]).toMatchObject({ id: 1 }); + expect(r.body.data).toHaveLength(1); + expect(r.body.data[0]).toMatchObject({ id: 1 }); // invalid filter field r = await handler({ @@ -624,7 +623,7 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[viewCount]']: '1' }, prisma, }); - expect((r.body as any).data).toHaveLength(0); + expect(r.body.data).toHaveLength(0); r = await handler({ method: 'get', @@ -632,7 +631,7 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[viewCount]']: '1' }, prisma, }); - expect((r.body as any).data).toHaveLength(1); + expect(r.body.data).toHaveLength(1); }); it('relationship filtering', async () => { @@ -661,7 +660,7 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[viewCount]']: '1' }, prisma, }); - expect((r.body as any).data).toHaveLength(0); + expect(r.body.data).toHaveLength(0); r = await handler({ method: 'get', @@ -669,7 +668,7 @@ describe('REST server tests - regular prisma', () => { query: { ['filter[viewCount]']: '1' }, prisma, }); - expect((r.body as any).data).toHaveLength(1); + expect(r.body.data).toHaveLength(1); }); it('toplevel sorting', async () => { @@ -700,7 +699,7 @@ describe('REST server tests - regular prisma', () => { prisma, }); expect(r.status).toBe(200); - expect((r.body as any).data[0]).toMatchObject({ id: 1 }); + expect(r.body.data[0]).toMatchObject({ id: 1 }); // basic sorting desc r = await handler({ @@ -710,7 +709,7 @@ describe('REST server tests - regular prisma', () => { prisma, }); expect(r.status).toBe(200); - expect((r.body as any).data[0]).toMatchObject({ id: 2 }); + expect(r.body.data[0]).toMatchObject({ id: 2 }); // by relation id r = await handler({ @@ -720,7 +719,7 @@ describe('REST server tests - regular prisma', () => { prisma, }); expect(r.status).toBe(200); - expect((r.body as any).data[0]).toMatchObject({ id: 2 }); + expect(r.body.data[0]).toMatchObject({ id: 2 }); // by relation field r = await handler({ @@ -730,7 +729,7 @@ describe('REST server tests - regular prisma', () => { prisma, }); expect(r.status).toBe(200); - expect((r.body as any).data[0]).toMatchObject({ id: 2 }); + expect(r.body.data[0]).toMatchObject({ id: 2 }); // multi-field sorting r = await handler({ @@ -740,7 +739,7 @@ describe('REST server tests - regular prisma', () => { prisma, }); expect(r.status).toBe(200); - expect((r.body as any).data[0]).toMatchObject({ id: 2 }); + expect(r.body.data[0]).toMatchObject({ id: 2 }); r = await handler({ method: 'get', @@ -749,7 +748,7 @@ describe('REST server tests - regular prisma', () => { prisma, }); expect(r.status).toBe(200); - expect((r.body as any).data[0]).toMatchObject({ id: 1 }); + expect(r.body.data[0]).toMatchObject({ id: 1 }); r = await handler({ method: 'get', @@ -758,7 +757,7 @@ describe('REST server tests - regular prisma', () => { prisma, }); expect(r.status).toBe(200); - expect((r.body as any).data[0]).toMatchObject({ id: 2 }); + expect(r.body.data[0]).toMatchObject({ id: 2 }); // invalid field r = await handler({ @@ -846,7 +845,7 @@ describe('REST server tests - regular prisma', () => { prisma, }); expect(r.status).toBe(200); - expect((r.body as any).data[0]).toMatchObject({ id: 1 }); + expect(r.body.data[0]).toMatchObject({ id: 1 }); // desc r = await handler({ @@ -856,7 +855,7 @@ describe('REST server tests - regular prisma', () => { prisma, }); expect(r.status).toBe(200); - expect((r.body as any).data[0]).toMatchObject({ id: 2 }); + expect(r.body.data[0]).toMatchObject({ id: 2 }); // relation field r = await handler({ @@ -866,7 +865,7 @@ describe('REST server tests - regular prisma', () => { prisma, }); expect(r.status).toBe(200); - expect((r.body as any).data[0]).toMatchObject({ id: 2 }); + expect(r.body.data[0]).toMatchObject({ id: 2 }); }); it('relationship sorting', async () => { @@ -903,7 +902,7 @@ describe('REST server tests - regular prisma', () => { prisma, }); expect(r.status).toBe(200); - expect((r.body as any).data[0]).toMatchObject({ id: 1 }); + expect(r.body.data[0]).toMatchObject({ id: 1 }); // desc r = await handler({ @@ -913,7 +912,7 @@ describe('REST server tests - regular prisma', () => { prisma, }); expect(r.status).toBe(200); - expect((r.body as any).data[0]).toMatchObject({ id: 2 }); + expect(r.body.data[0]).toMatchObject({ id: 2 }); // relation field r = await handler({ @@ -923,7 +922,7 @@ describe('REST server tests - regular prisma', () => { prisma, }); expect(r.status).toBe(200); - expect((r.body as any).data[0]).toMatchObject({ id: 2 }); + expect(r.body.data[0]).toMatchObject({ id: 2 }); }); it('including', async () => { @@ -962,8 +961,8 @@ describe('REST server tests - regular prisma', () => { query: { include: 'posts' }, prisma, }); - expect((r.body as any).included).toHaveLength(2); - expect((r.body as any).included[0]).toMatchObject({ + expect(r.body.included).toHaveLength(2); + expect(r.body.included[0]).toMatchObject({ type: 'post', id: 1, attributes: { title: 'Post1' }, @@ -976,8 +975,8 @@ describe('REST server tests - regular prisma', () => { query: { include: 'posts' }, prisma, }); - expect((r.body as any).included).toHaveLength(1); - expect((r.body as any).included[0]).toMatchObject({ + expect(r.body.included).toHaveLength(1); + expect(r.body.included[0]).toMatchObject({ type: 'post', id: 1, attributes: { title: 'Post1' }, @@ -990,8 +989,8 @@ describe('REST server tests - regular prisma', () => { query: { include: 'posts.comments' }, prisma, }); - expect((r.body as any).included).toHaveLength(1); - expect((r.body as any).included[0]).toMatchObject({ + expect(r.body.included).toHaveLength(1); + expect(r.body.included[0]).toMatchObject({ type: 'comment', attributes: { content: 'Comment1' }, }); @@ -1003,7 +1002,7 @@ describe('REST server tests - regular prisma', () => { query: { include: 'posts.comments', ['filter[published]']: 'true' }, prisma, }); - expect((r.body as any).data).toHaveLength(0); + expect(r.body.data).toHaveLength(0); // deep include r = await handler({ @@ -1012,8 +1011,8 @@ describe('REST server tests - regular prisma', () => { query: { include: 'posts.comments' }, prisma, }); - expect((r.body as any).included).toHaveLength(3); - expect((r.body as any).included[2]).toMatchObject({ + expect(r.body.included).toHaveLength(3); + expect(r.body.included[2]).toMatchObject({ type: 'comment', attributes: { content: 'Comment1' }, }); @@ -1025,8 +1024,8 @@ describe('REST server tests - regular prisma', () => { query: { include: 'posts.comments,profile' }, prisma, }); - expect((r.body as any).included).toHaveLength(4); - const profile = (r.body as any).included.find((item: any) => item.type === 'profile'); + expect(r.body.included).toHaveLength(4); + const profile = r.body.included.find((item: any) => item.type === 'profile'); expect(profile).toMatchObject({ type: 'profile', attributes: { gender: 'male' }, @@ -1062,9 +1061,9 @@ describe('REST server tests - regular prisma', () => { query: { ['page[limit]']: '3' }, prisma, }); - expect((r.body as any).data).toHaveLength(3); - expect((r.body as any).meta.total).toBe(5); - expect((r.body as any).links).toMatchObject({ + expect(r.body.data).toHaveLength(3); + expect(r.body.meta.total).toBe(5); + expect(r.body.links).toMatchObject({ first: 'http://localhost/api/user?page%5Blimit%5D=3', last: 'http://localhost/api/user?page%5Boffset%5D=3', prev: null, @@ -1078,9 +1077,9 @@ describe('REST server tests - regular prisma', () => { query: { ['page[limit]']: '3', ['page[offset]']: '3' }, prisma, }); - expect((r.body as any).data).toHaveLength(2); - expect((r.body as any).meta.total).toBe(5); - expect((r.body as any).links).toMatchObject({ + expect(r.body.data).toHaveLength(2); + expect(r.body.meta.total).toBe(5); + expect(r.body.links).toMatchObject({ first: 'http://localhost/api/user?page%5Blimit%5D=3', last: 'http://localhost/api/user?page%5Boffset%5D=3', prev: 'http://localhost/api/user?page%5Boffset%5D=0&page%5Blimit%5D=3', @@ -1094,8 +1093,8 @@ describe('REST server tests - regular prisma', () => { query: { ['page[limit]']: '10' }, prisma, }); - expect((r.body as any).data).toHaveLength(5); - expect((r.body as any).links).toMatchObject({ + expect(r.body.data).toHaveLength(5); + expect(r.body.links).toMatchObject({ first: 'http://localhost/api/user?page%5Blimit%5D=5', last: 'http://localhost/api/user?page%5Boffset%5D=0', prev: null, @@ -1109,8 +1108,8 @@ describe('REST server tests - regular prisma', () => { query: { ['page[offset]']: '10' }, prisma, }); - expect((r.body as any).data).toHaveLength(0); - expect((r.body as any).links).toMatchObject({ + expect(r.body.data).toHaveLength(0); + expect(r.body.links).toMatchObject({ first: 'http://localhost/api/user?page%5Blimit%5D=5', last: 'http://localhost/api/user?page%5Boffset%5D=0', prev: null, @@ -1124,8 +1123,8 @@ describe('REST server tests - regular prisma', () => { query: { ['page[offset]']: '-1' }, prisma, }); - expect((r.body as any).data).toHaveLength(5); - expect((r.body as any).links).toMatchObject({ + expect(r.body.data).toHaveLength(5); + expect(r.body.links).toMatchObject({ first: 'http://localhost/api/user?page%5Blimit%5D=5', last: 'http://localhost/api/user?page%5Boffset%5D=0', prev: null, @@ -1139,8 +1138,8 @@ describe('REST server tests - regular prisma', () => { query: { ['page[limit]']: '0' }, prisma, }); - expect((r.body as any).data).toHaveLength(5); - expect((r.body as any).links).toMatchObject({ + expect(r.body.data).toHaveLength(5); + expect(r.body.links).toMatchObject({ first: 'http://localhost/api/user?page%5Blimit%5D=5', last: 'http://localhost/api/user?page%5Boffset%5D=0', prev: null, @@ -1168,8 +1167,8 @@ describe('REST server tests - regular prisma', () => { path: '/user/user1/posts', prisma, }); - expect((r.body as any).data).toHaveLength(5); - expect((r.body as any).links).toMatchObject({ + expect(r.body.data).toHaveLength(5); + expect(r.body.links).toMatchObject({ self: 'http://localhost/api/user/user1/posts', first: 'http://localhost/api/user/user1/posts?page%5Blimit%5D=5', last: 'http://localhost/api/user/user1/posts?page%5Boffset%5D=5', @@ -1184,8 +1183,8 @@ describe('REST server tests - regular prisma', () => { query: { ['page[limit]']: '3' }, prisma, }); - expect((r.body as any).data).toHaveLength(3); - expect((r.body as any).links).toMatchObject({ + expect(r.body.data).toHaveLength(3); + expect(r.body.links).toMatchObject({ self: 'http://localhost/api/user/user1/posts', first: 'http://localhost/api/user/user1/posts?page%5Blimit%5D=3', last: 'http://localhost/api/user/user1/posts?page%5Boffset%5D=9', @@ -1200,8 +1199,8 @@ describe('REST server tests - regular prisma', () => { query: { ['page[limit]']: '3', ['page[offset]']: '8' }, prisma, }); - expect((r.body as any).data).toHaveLength(2); - expect((r.body as any).links).toMatchObject({ + expect(r.body.data).toHaveLength(2); + expect(r.body.links).toMatchObject({ self: 'http://localhost/api/user/user1/posts', first: 'http://localhost/api/user/user1/posts?page%5Blimit%5D=3', last: 'http://localhost/api/user/user1/posts?page%5Boffset%5D=9', @@ -1230,8 +1229,8 @@ describe('REST server tests - regular prisma', () => { path: '/user/user1/relationships/posts', prisma, }); - expect((r.body as any).data).toHaveLength(5); - expect((r.body as any).links).toMatchObject({ + expect(r.body.data).toHaveLength(5); + expect(r.body.links).toMatchObject({ self: 'http://localhost/api/user/user1/relationships/posts', first: 'http://localhost/api/user/user1/relationships/posts?page%5Blimit%5D=5', last: 'http://localhost/api/user/user1/relationships/posts?page%5Boffset%5D=5', @@ -1246,8 +1245,8 @@ describe('REST server tests - regular prisma', () => { query: { ['page[limit]']: '3' }, prisma, }); - expect((r.body as any).data).toHaveLength(3); - expect((r.body as any).links).toMatchObject({ + expect(r.body.data).toHaveLength(3); + expect(r.body.links).toMatchObject({ self: 'http://localhost/api/user/user1/relationships/posts', first: 'http://localhost/api/user/user1/relationships/posts?page%5Blimit%5D=3', last: 'http://localhost/api/user/user1/relationships/posts?page%5Boffset%5D=9', @@ -1262,8 +1261,8 @@ describe('REST server tests - regular prisma', () => { query: { ['page[limit]']: '3', ['page[offset]']: '8' }, prisma, }); - expect((r.body as any).data).toHaveLength(2); - expect((r.body as any).links).toMatchObject({ + expect(r.body.data).toHaveLength(2); + expect(r.body.links).toMatchObject({ self: 'http://localhost/api/user/user1/relationships/posts', first: 'http://localhost/api/user/user1/relationships/posts?page%5Blimit%5D=3', last: 'http://localhost/api/user/user1/relationships/posts?page%5Boffset%5D=9', @@ -1910,7 +1909,7 @@ describe('REST server tests - enhanced prisma', () => { prisma, }); expect(r.status).toBe(403); - expect((r.body as any).errors[0].reason).toBe(CrudFailureReason.RESULT_NOT_READABLE); + expect(r.body.errors[0].reason).toBe(CrudFailureReason.RESULT_NOT_READABLE); }); }); @@ -1997,7 +1996,7 @@ describe('REST server tests - NextAuth project regression', () => { prisma, }); expect(r.status).toBe(200); - expect((r.body as any).data).toHaveLength(0); + expect(r.body.data).toHaveLength(0); r = await handler({ method: 'post', @@ -2016,7 +2015,7 @@ describe('REST server tests - NextAuth project regression', () => { prisma, }); expect(r.status).toBe(200); - expect((r.body as any).data).toHaveLength(1); + expect(r.body.data).toHaveLength(1); r = await handler({ method: 'post', @@ -2028,6 +2027,158 @@ describe('REST server tests - NextAuth project regression', () => { prisma, }); expect(r.status).toBe(400); - expect((r.body as any).errors[0].prismaCode).toBe('P2002'); + expect(r.body.errors[0].prismaCode).toBe('P2002'); + }); +}); + +describe('REST server tests - field type coverage', () => { + const schema = ` + model Foo { + id Int @id + string String + int Int + bigInt BigInt + date DateTime + float Float + decimal Decimal + boolean Boolean + bytes Bytes + bars Bar[] + } + + model Bar { + id Int @id + bytes Bytes + foo Foo? @relation(fields: [fooId], references: [id]) + fooId Int? @unique + } + `; + + it('field types', async () => { + const { prisma, zodSchemas, modelMeta } = await loadSchema(schema); + + const _handler = makeHandler({ endpoint: 'http://localhost/api', pageSize: 5 }); + handler = (args) => _handler({ ...args, zodSchemas, modelMeta, url: new URL(`http://localhost/${args.path}`) }); + + await prisma.bar.create({ data: { id: 1, bytes: Buffer.from([7, 8, 9]) } }); + + const decimalValue1 = new Decimal('0.046875'); + const decimalValue2 = new Decimal('0.0146875'); + + const createAttrs = { + string: 'string', + int: 123, + bigInt: BigInt(534543543534), + date: new Date(), + float: 1.23, + decimal: decimalValue1, + boolean: true, + bytes: Buffer.from([1, 2, 3, 4]), + }; + + const { json: createPayload, meta: createMeta } = SuperJSON.serialize({ + data: { + type: 'foo', + attributes: { id: 1, ...createAttrs }, + relationships: { + bars: { + data: [{ type: 'bar', id: 1 }], + }, + }, + }, + }); + + let r = await handler({ + method: 'post', + path: '/foo', + query: {}, + requestBody: { + ...(createPayload as any), + meta: { + serialization: createMeta, + }, + }, + prisma, + }); + expect(r.status).toBe(201); + // result is serializable + expect(JSON.stringify(r.body)).toBeTruthy(); + let serializationMeta = r.body.meta.serialization; + expect(serializationMeta).toBeTruthy(); + let deserialized: any = SuperJSON.deserialize({ json: r.body, meta: serializationMeta }); + let data = deserialized.data.attributes; + expect(typeof data.bigInt).toBe('bigint'); + expect(Buffer.isBuffer(data.bytes)).toBeTruthy(); + expect(data.date instanceof Date).toBeTruthy(); + expect(Decimal.isDecimal(data.decimal)).toBeTruthy(); + + const updateAttrs = { + bigInt: BigInt(1534543543534), + date: new Date(), + decimal: decimalValue2, + bytes: Buffer.from([5, 2, 3, 4]), + }; + const { json: updatePayload, meta: updateMeta } = SuperJSON.serialize({ + data: { + type: 'foo', + attributes: updateAttrs, + }, + }); + + r = await handler({ + method: 'put', + path: '/foo/1', + query: {}, + requestBody: { + ...(updatePayload as any), + meta: { + serialization: updateMeta, + }, + }, + prisma, + }); + expect(r.status).toBe(200); + // result is serializable + expect(JSON.stringify(r.body)).toBeTruthy(); + + serializationMeta = r.body.meta.serialization; + expect(serializationMeta).toBeTruthy(); + deserialized = SuperJSON.deserialize({ json: r.body, meta: serializationMeta }); + data = deserialized.data.attributes; + expect(data.bigInt).toEqual(updateAttrs.bigInt); + expect(data.date).toEqual(updateAttrs.date); + expect(data.decimal.equals(updateAttrs.decimal)).toBeTruthy(); + expect(data.bytes.toString('base64')).toEqual(updateAttrs.bytes.toString('base64')); + + r = await handler({ + method: 'get', + path: '/foo/1', + query: {}, + prisma, + }); + // result is serializable + expect(JSON.stringify(r.body)).toBeTruthy(); + serializationMeta = r.body.meta.serialization; + expect(serializationMeta).toBeTruthy(); + deserialized = SuperJSON.deserialize({ json: r.body, meta: serializationMeta }); + data = deserialized.data.attributes; + expect(typeof data.bigInt).toBe('bigint'); + expect(Buffer.isBuffer(data.bytes)).toBeTruthy(); + expect(data.date instanceof Date).toBeTruthy(); + expect(Decimal.isDecimal(data.decimal)).toBeTruthy(); + + r = await handler({ + method: 'get', + path: '/foo', + query: { include: 'bars' }, + prisma, + }); + // result is serializable + expect(JSON.stringify(r.body)).toBeTruthy(); + serializationMeta = r.body.meta.serialization; + expect(serializationMeta).toBeTruthy(); + deserialized = SuperJSON.deserialize({ json: r.body, meta: serializationMeta }); + const included = deserialized.included[0]; + expect(Buffer.isBuffer(included.attributes.bytes)).toBeTruthy(); }); }); diff --git a/packages/server/tests/api/rpc.test.ts b/packages/server/tests/api/rpc.test.ts index dfdc98f4f..8d58806a0 100644 --- a/packages/server/tests/api/rpc.test.ts +++ b/packages/server/tests/api/rpc.test.ts @@ -1,12 +1,14 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /// -import type { ZodSchemas } from '@zenstackhq/runtime/enhancements/types'; +import type { ZodSchemas } from '@zenstackhq/runtime'; import { loadSchema } from '@zenstackhq/testtools'; +import { Decimal } from 'decimal.js'; +import SuperJSON from 'superjson'; import RPCAPIHandler from '../../src/api/rpc'; import { schema } from '../utils'; -describe('OpenAPI server tests', () => { +describe('RPC API Handler Tests', () => { let prisma: any; let zodSchemas: any; @@ -25,7 +27,7 @@ describe('OpenAPI server tests', () => { prisma, }); expect(r.status).toBe(200); - expect(r.body).toHaveLength(0); + expect(r.data).toHaveLength(0); r = await handleRequest({ method: 'post', @@ -47,7 +49,7 @@ describe('OpenAPI server tests', () => { prisma, }); expect(r.status).toBe(201); - expect(r.body).toEqual( + expect(r.data).toEqual( expect.objectContaining({ email: 'user1@abc.com', posts: expect.arrayContaining([ @@ -56,11 +58,10 @@ describe('OpenAPI server tests', () => { ]), }) ); - const data: any = r.body; - expect(data.zenstack_guard).toBeUndefined(); - expect(data.zenstack_transaction).toBeUndefined(); - expect(data.posts[0].zenstack_guard).toBeUndefined(); - expect(data.posts[0].zenstack_transaction).toBeUndefined(); + expect(r.data.zenstack_guard).toBeUndefined(); + expect(r.data.zenstack_transaction).toBeUndefined(); + expect(r.data.posts[0].zenstack_guard).toBeUndefined(); + expect(r.data.posts[0].zenstack_transaction).toBeUndefined(); r = await handleRequest({ method: 'get', @@ -68,7 +69,7 @@ describe('OpenAPI server tests', () => { prisma, }); expect(r.status).toBe(200); - expect(r.body).toHaveLength(2); + expect(r.data).toHaveLength(2); r = await handleRequest({ method: 'get', @@ -77,7 +78,7 @@ describe('OpenAPI server tests', () => { prisma, }); expect(r.status).toBe(200); - expect(r.body).toHaveLength(1); + expect(r.data).toHaveLength(1); r = await handleRequest({ method: 'put', @@ -86,7 +87,7 @@ describe('OpenAPI server tests', () => { prisma, }); expect(r.status).toBe(200); - expect((r.body as any).email).toBe('user1@def.com'); + expect(r.data.email).toBe('user1@def.com'); r = await handleRequest({ method: 'get', @@ -95,7 +96,7 @@ describe('OpenAPI server tests', () => { prisma, }); expect(r.status).toBe(200); - expect(r.body).toBe(1); + expect(r.data).toBe(1); r = await handleRequest({ method: 'get', @@ -104,7 +105,7 @@ describe('OpenAPI server tests', () => { prisma, }); expect(r.status).toBe(200); - expect((r.body as any)._sum.viewCount).toBe(3); + expect(r.data._sum.viewCount).toBe(3); r = await handleRequest({ method: 'get', @@ -113,7 +114,7 @@ describe('OpenAPI server tests', () => { prisma, }); expect(r.status).toBe(200); - expect(r.body).toEqual( + expect(r.data).toEqual( expect.arrayContaining([ expect.objectContaining({ published: true, _sum: { viewCount: 1 } }), expect.objectContaining({ published: false, _sum: { viewCount: 2 } }), @@ -127,7 +128,7 @@ describe('OpenAPI server tests', () => { prisma, }); expect(r.status).toBe(200); - expect((r.body as any).count).toBe(1); + expect(r.data.count).toBe(1); }); it('validation error', async () => { @@ -140,7 +141,7 @@ describe('OpenAPI server tests', () => { prisma, }); expect(r.status).toBe(400); - expect((r.body as any).message).toContain('Argument where is missing'); + expect(r.error.message).toContain('Argument where is missing'); handleRequest = makeHandler(zodSchemas); @@ -151,8 +152,8 @@ describe('OpenAPI server tests', () => { prisma, }); expect(r.status).toBe(400); - expect((r.body as any).message).toContain('Validation error'); - expect((r.body as any).message).toContain('where'); + expect(r.error.message).toContain('Validation error'); + expect(r.error.message).toContain('where'); r = await handleRequest({ method: 'post', @@ -162,8 +163,8 @@ describe('OpenAPI server tests', () => { zodSchemas, }); expect(r.status).toBe(400); - expect((r.body as any).message).toContain('Validation error'); - expect((r.body as any).message).toContain('data'); + expect(r.error.message).toContain('Validation error'); + expect(r.error.message).toContain('data'); }); it('invalid path or args', async () => { @@ -174,7 +175,7 @@ describe('OpenAPI server tests', () => { prisma, }); expect(r.status).toBe(400); - expect((r.body as any).message).toContain('invalid request path'); + expect(r.error.message).toContain('invalid request path'); r = await handleRequest({ method: 'get', @@ -182,7 +183,7 @@ describe('OpenAPI server tests', () => { prisma, }); expect(r.status).toBe(400); - expect((r.body as any).message).toContain('invalid request path'); + expect(r.error.message).toContain('invalid request path'); r = await handleRequest({ method: 'get', @@ -191,7 +192,7 @@ describe('OpenAPI server tests', () => { prisma, }); expect(r.status).toBe(400); - expect((r.body as any).message).toContain('query param must contain valid JSON'); + expect(r.error.message).toContain('invalid "q" query parameter'); r = await handleRequest({ method: 'delete', @@ -200,13 +201,187 @@ describe('OpenAPI server tests', () => { prisma, }); expect(r.status).toBe(400); - expect((r.body as any).message).toContain('query param must contain valid JSON'); + expect(r.error.message).toContain('invalid "q" query parameter'); + }); + + it('field types', async () => { + const schema = ` + model Foo { + id Int @id + + string String + int Int + bigInt BigInt + date DateTime + float Float + decimal Decimal + boolean Boolean + bytes Bytes + bars Bar[] + + @@allow('all', true) + } + + + model Bar { + id Int @id @default(autoincrement()) + bytes Bytes + foo Foo @relation(fields: [fooId], references: [id]) + fooId Int @unique + } + `; + + const handleRequest = makeHandler(); + const { prisma, zodSchemas, modelMeta } = await loadSchema(schema); + + const decimalValue = new Decimal('0.046875'); + const bigIntValue = BigInt(534543543534); + const dateValue = new Date(); + const bufferValue = Buffer.from([1, 2, 3, 4]); + const barBufferValue = Buffer.from([7, 8, 9]); + + const createData = { + string: 'string', + int: 123, + bigInt: bigIntValue, + date: dateValue, + float: 1.23, + decimal: decimalValue, + boolean: true, + bytes: bufferValue, + bars: { + create: { bytes: barBufferValue }, + }, + }; + + const serialized = SuperJSON.serialize({ + include: { bars: true }, + data: { id: 1, ...createData }, + }); + + let r = await handleRequest({ + method: 'post', + path: '/foo/create', + query: {}, + prisma, + zodSchemas, + modelMeta, + requestBody: { + ...(serialized.json as any), + meta: { serialization: serialized.meta }, + }, + }); + expect(r.status).toBe(201); + expect(r.meta).toBeTruthy(); + const data: any = SuperJSON.deserialize({ json: r.data, meta: r.meta.serialization }); + expect(typeof data.bigInt).toBe('bigint'); + expect(Buffer.isBuffer(data.bytes)).toBeTruthy(); + expect(data.date instanceof Date).toBeTruthy(); + expect(Decimal.isDecimal(data.decimal)).toBeTruthy(); + expect(Buffer.isBuffer(data.bars[0].bytes)).toBeTruthy(); + + // find with filter not found + const serializedQ = SuperJSON.serialize({ + where: { + bigInt: { + gt: bigIntValue, + }, + }, + }); + r = await handleRequest({ + method: 'get', + path: '/foo/findFirst', + query: { + q: JSON.stringify(serializedQ.json), + meta: JSON.stringify({ serialization: serializedQ.meta }), + }, + prisma, + zodSchemas, + modelMeta, + }); + expect(r.status).toBe(200); + expect(r.data).toBeNull(); + + // find with filter found + const serializedQ1 = SuperJSON.serialize({ + where: { + bigInt: bigIntValue, + }, + }); + r = await handleRequest({ + method: 'get', + path: '/foo/findFirst', + query: { + q: JSON.stringify(serializedQ1.json), + meta: JSON.stringify({ serialization: serializedQ1.meta }), + }, + prisma, + zodSchemas, + modelMeta, + }); + expect(r.status).toBe(200); + expect(r.data).toBeTruthy(); + + // find with filter found + const serializedQ2 = SuperJSON.serialize({ + where: { + bars: { + some: { + bytes: barBufferValue, + }, + }, + }, + }); + r = await handleRequest({ + method: 'get', + path: '/foo/findFirst', + query: { + q: JSON.stringify(serializedQ2.json), + meta: JSON.stringify({ serialization: serializedQ2.meta }), + }, + prisma, + zodSchemas, + modelMeta, + }); + expect(r.status).toBe(200); + expect(r.data).toBeTruthy(); + + // find with filter not found + const serializedQ3 = SuperJSON.serialize({ + where: { + bars: { + none: { + bytes: barBufferValue, + }, + }, + }, + }); + r = await handleRequest({ + method: 'get', + path: '/foo/findFirst', + query: { + q: JSON.stringify(serializedQ3.json), + meta: JSON.stringify({ serialization: serializedQ3.meta }), + }, + prisma, + zodSchemas, + modelMeta, + }); + expect(r.status).toBe(200); + expect(r.data).toBeNull(); }); }); function makeHandler(zodSchemas?: ZodSchemas) { const _handler = RPCAPIHandler(); return async (args: any) => { - return _handler({ ...args, url: new URL(`http://localhost/${args.path}`), zodSchemas }); + const r = await _handler({ ...args, url: new URL(`http://localhost/${args.path}`), zodSchemas }); + return { + status: r.status, + body: r.body as any, + data: (r.body as any).data, + error: (r.body as any).error, + meta: (r.body as any).meta, + }; }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1d9fde4de..f20044d98 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,12 @@ importers: '@changesets/cli': specifier: ^2.26.0 version: 2.26.0 + concurrently: + specifier: ^7.4.0 + version: 7.4.0 + tsup: + specifier: ^7.1.0 + version: 7.1.0 packages/language: dependencies: @@ -115,7 +121,7 @@ importers: version: 0.2.1 ts-jest: specifier: ^29.0.5 - version: 29.0.5(@babel/core@7.22.5)(jest@29.5.0)(typescript@4.9.5) + version: 29.0.5(@babel/core@7.22.5)(esbuild@0.18.13)(jest@29.5.0)(typescript@4.9.5) typescript: specifier: ^4.9.5 version: 4.9.5 @@ -129,6 +135,9 @@ importers: '@prisma/generator-helper': specifier: 4.10.0 version: 4.10.0 + '@zenstackhq/runtime': + specifier: workspace:* + version: link:../../runtime/dist '@zenstackhq/sdk': specifier: workspace:* version: link:../../sdk/dist @@ -141,9 +150,6 @@ importers: lower-case-first: specifier: ^2.0.2 version: 2.0.2 - superjson: - specifier: ^1.11.0 - version: 1.11.0 ts-morph: specifier: ^16.0.0 version: 16.0.0 @@ -153,7 +159,7 @@ importers: devDependencies: '@tanstack/react-query': specifier: ^4.28.0 - version: 4.28.0(react-dom@17.0.2)(react@17.0.2) + version: 4.28.0(react@18.2.0) '@types/jest': specifier: ^29.5.0 version: 29.5.0 @@ -161,8 +167,8 @@ importers: specifier: ^1.0.1 version: 1.0.1 '@types/react': - specifier: ^18.0.26 - version: 18.0.26 + specifier: 18.2.0 + version: 18.2.0 '@types/tmp': specifier: ^0.2.3 version: 0.2.3 @@ -179,17 +185,14 @@ importers: specifier: ^29.5.0 version: 29.5.0 react: - specifier: ^17.0.2 || ^18 - version: 17.0.2 - react-dom: - specifier: ^17.0.2 || ^18 - version: 17.0.2(react@17.0.2) + specifier: 18.2.0 + version: 18.2.0 rimraf: specifier: ^3.0.2 version: 3.0.2 swr: specifier: ^2.0.3 - version: 2.0.3(react@17.0.2) + version: 2.0.3(react@18.2.0) ts-jest: specifier: ^29.0.5 version: 29.0.5(@babel/core@7.22.5)(jest@29.5.0)(typescript@4.9.4) @@ -203,6 +206,9 @@ importers: '@prisma/generator-helper': specifier: 4.10.0 version: 4.10.0 + '@zenstackhq/runtime': + specifier: workspace:* + version: link:../../runtime/dist '@zenstackhq/sdk': specifier: workspace:* version: link:../../sdk/dist @@ -226,10 +232,10 @@ importers: version: 2.0.2 devDependencies: '@tanstack/react-query': - specifier: ^4.29.7 - version: 4.29.7(react-dom@17.0.2)(react@17.0.2) + specifier: 4.29.7 + version: 4.29.7(react-dom@18.2.0)(react@18.2.0) '@tanstack/svelte-query': - specifier: ^4.29.7 + specifier: 4.29.7 version: 4.29.7(svelte@3.59.2) '@types/jest': specifier: ^29.5.0 @@ -238,8 +244,8 @@ importers: specifier: ^1.0.1 version: 1.0.1 '@types/react': - specifier: ^18.0.26 - version: 18.0.26 + specifier: 18.2.0 + version: 18.2.0 '@types/tmp': specifier: ^0.2.3 version: 0.2.3 @@ -256,20 +262,17 @@ importers: specifier: ^29.5.0 version: 29.5.0 react: - specifier: ^17.0.2 || ^18 - version: 17.0.2 - react-dom: - specifier: ^17.0.2 || ^18 - version: 17.0.2(react@17.0.2) + specifier: 18.2.0 + version: 18.2.0 rimraf: specifier: ^3.0.2 version: 3.0.2 swr: specifier: ^2.0.3 - version: 2.0.3(react@17.0.2) + version: 2.0.3(react@18.2.0) ts-jest: specifier: ^29.0.5 - version: 29.0.5(@babel/core@7.22.5)(jest@29.5.0)(typescript@4.9.4) + version: 29.0.5(@babel/core@7.22.5)(esbuild@0.18.13)(jest@29.5.0)(typescript@4.9.4) typescript: specifier: ^4.9.4 version: 4.9.4 @@ -310,10 +313,10 @@ importers: devDependencies: '@trpc/next': specifier: ^10.32.0 - version: 10.32.0(@tanstack/react-query@4.29.19)(@trpc/client@10.32.0)(@trpc/react-query@10.32.0)(@trpc/server@10.32.0)(next@13.4.7)(react-dom@18.2.0)(react@18.2.0) + version: 10.32.0(@tanstack/react-query@4.29.7)(@trpc/client@10.32.0)(@trpc/react-query@10.32.0)(@trpc/server@10.32.0)(next@13.4.7)(react-dom@18.2.0)(react@18.2.0) '@trpc/react-query': specifier: ^10.32.0 - version: 10.32.0(@tanstack/react-query@4.29.19)(@trpc/client@10.32.0)(@trpc/server@10.32.0)(react-dom@18.2.0)(react@18.2.0) + version: 10.32.0(@tanstack/react-query@4.29.7)(@trpc/client@10.32.0)(@trpc/server@10.32.0)(react-dom@18.2.0)(react@18.2.0) '@trpc/server': specifier: ^10.32.0 version: 10.32.0 @@ -343,7 +346,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^29.0.5 - version: 29.0.5(@babel/core@7.22.5)(jest@29.5.0)(typescript@4.9.4) + version: 29.0.5(@babel/core@7.22.5)(esbuild@0.18.13)(jest@29.5.0)(typescript@4.9.4) typescript: specifier: ^4.9.4 version: 4.9.4 @@ -360,6 +363,9 @@ importers: bcryptjs: specifier: ^2.4.3 version: 2.4.3 + buffer: + specifier: ^6.0.3 + version: 6.0.3 change-case: specifier: ^4.1.2 version: 4.1.2 @@ -706,6 +712,9 @@ importers: copyfiles: specifier: ^2.4.1 version: 2.4.1 + decimal.js: + specifier: ^10.4.2 + version: 10.4.2 express: specifier: ^4.18.2 version: 4.18.2 @@ -732,7 +741,7 @@ importers: version: 6.3.3 ts-jest: specifier: ^29.0.5 - version: 29.0.5(@babel/core@7.22.5)(jest@29.5.0)(typescript@4.9.4) + version: 29.0.5(@babel/core@7.22.5)(esbuild@0.18.13)(jest@29.5.0)(typescript@4.9.4) typescript: specifier: ^4.9.4 version: 4.9.4 @@ -868,7 +877,7 @@ importers: version: 0.2.1 ts-jest: specifier: ^29.0.1 - version: 29.0.1(@babel/core@7.22.5)(jest@29.5.0)(typescript@4.6.2) + version: 29.0.1(@babel/core@7.22.5)(esbuild@0.18.13)(jest@29.5.0)(typescript@4.6.2) ts-node: specifier: ^10.9.1 version: 10.9.1(@types/node@14.18.29)(typescript@4.6.2) @@ -1462,6 +1471,15 @@ packages: dev: true optional: true + /@esbuild/android-arm64@0.18.13: + resolution: {integrity: sha512-j7NhycJUoUAG5kAzGf4fPWfd17N6SM3o1X6MlXVqfHvs2buFraCJzos9vbeWjLxOyBKHyPOnuCuipbhvbYtTAg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + /@esbuild/android-arm@0.15.12: resolution: {integrity: sha512-IC7TqIqiyE0MmvAhWkl/8AEzpOtbhRNDo7aph47We1NbE5w2bt/Q+giAhe0YYeVpYnIhGMcuZY92qDK6dQauvA==} engines: {node: '>=12'} @@ -1480,6 +1498,15 @@ packages: dev: true optional: true + /@esbuild/android-arm@0.18.13: + resolution: {integrity: sha512-KwqFhxRFMKZINHzCqf8eKxE0XqWlAVPRxwy6rc7CbVFxzUWB2sA/s3hbMZeemPdhN3fKBkqOaFhTbS8xJXYIWQ==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + /@esbuild/android-x64@0.17.19: resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} engines: {node: '>=12'} @@ -1489,6 +1516,15 @@ packages: dev: true optional: true + /@esbuild/android-x64@0.18.13: + resolution: {integrity: sha512-M2eZkRxR6WnWfVELHmv6MUoHbOqnzoTVSIxgtsyhm/NsgmL+uTmag/VVzdXvmahak1I6sOb1K/2movco5ikDJg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + /@esbuild/darwin-arm64@0.17.19: resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} engines: {node: '>=12'} @@ -1498,6 +1534,15 @@ packages: dev: true optional: true + /@esbuild/darwin-arm64@0.18.13: + resolution: {integrity: sha512-f5goG30YgR1GU+fxtaBRdSW3SBG9pZW834Mmhxa6terzcboz7P2R0k4lDxlkP7NYRIIdBbWp+VgwQbmMH4yV7w==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + /@esbuild/darwin-x64@0.17.19: resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} engines: {node: '>=12'} @@ -1507,6 +1552,15 @@ packages: dev: true optional: true + /@esbuild/darwin-x64@0.18.13: + resolution: {integrity: sha512-RIrxoKH5Eo+yE5BtaAIMZaiKutPhZjw+j0OCh8WdvKEKJQteacq0myZvBDLU+hOzQOZWJeDnuQ2xgSScKf1Ovw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + /@esbuild/freebsd-arm64@0.17.19: resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} engines: {node: '>=12'} @@ -1516,6 +1570,15 @@ packages: dev: true optional: true + /@esbuild/freebsd-arm64@0.18.13: + resolution: {integrity: sha512-AfRPhHWmj9jGyLgW/2FkYERKmYR+IjYxf2rtSLmhOrPGFh0KCETFzSjx/JX/HJnvIqHt/DRQD/KAaVsUKoI3Xg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + /@esbuild/freebsd-x64@0.17.19: resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} engines: {node: '>=12'} @@ -1525,6 +1588,15 @@ packages: dev: true optional: true + /@esbuild/freebsd-x64@0.18.13: + resolution: {integrity: sha512-pGzWWZJBInhIgdEwzn8VHUBang8UvFKsvjDkeJ2oyY5gZtAM6BaxK0QLCuZY+qoj/nx/lIaItH425rm/hloETA==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-arm64@0.17.19: resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} engines: {node: '>=12'} @@ -1534,6 +1606,15 @@ packages: dev: true optional: true + /@esbuild/linux-arm64@0.18.13: + resolution: {integrity: sha512-hCzZbVJEHV7QM77fHPv2qgBcWxgglGFGCxk6KfQx6PsVIdi1u09X7IvgE9QKqm38OpkzaAkPnnPqwRsltvLkIQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-arm@0.17.19: resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} engines: {node: '>=12'} @@ -1543,6 +1624,15 @@ packages: dev: true optional: true + /@esbuild/linux-arm@0.18.13: + resolution: {integrity: sha512-4iMxLRMCxGyk7lEvkkvrxw4aJeC93YIIrfbBlUJ062kilUUnAiMb81eEkVvCVoh3ON283ans7+OQkuy1uHW+Hw==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-ia32@0.17.19: resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} engines: {node: '>=12'} @@ -1552,6 +1642,15 @@ packages: dev: true optional: true + /@esbuild/linux-ia32@0.18.13: + resolution: {integrity: sha512-I3OKGbynl3AAIO6onXNrup/ttToE6Rv2XYfFgLK/wnr2J+1g+7k4asLrE+n7VMhaqX+BUnyWkCu27rl+62Adug==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-loong64@0.15.12: resolution: {integrity: sha512-tZEowDjvU7O7I04GYvWQOS4yyP9E/7YlsB0jjw1Ycukgr2ycEzKyIk5tms5WnLBymaewc6VmRKnn5IJWgK4eFw==} engines: {node: '>=12'} @@ -1570,6 +1669,15 @@ packages: dev: true optional: true + /@esbuild/linux-loong64@0.18.13: + resolution: {integrity: sha512-8pcKDApAsKc6WW51ZEVidSGwGbebYw2qKnO1VyD8xd6JN0RN6EUXfhXmDk9Vc4/U3Y4AoFTexQewQDJGsBXBpg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-mips64el@0.17.19: resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} engines: {node: '>=12'} @@ -1579,6 +1687,15 @@ packages: dev: true optional: true + /@esbuild/linux-mips64el@0.18.13: + resolution: {integrity: sha512-6GU+J1PLiVqWx8yoCK4Z0GnfKyCGIH5L2KQipxOtbNPBs+qNDcMJr9euxnyJ6FkRPyMwaSkjejzPSISD9hb+gg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-ppc64@0.17.19: resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} engines: {node: '>=12'} @@ -1588,6 +1705,15 @@ packages: dev: true optional: true + /@esbuild/linux-ppc64@0.18.13: + resolution: {integrity: sha512-pfn/OGZ8tyR8YCV7MlLl5hAit2cmS+j/ZZg9DdH0uxdCoJpV7+5DbuXrR+es4ayRVKIcfS9TTMCs60vqQDmh+w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-riscv64@0.17.19: resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} engines: {node: '>=12'} @@ -1597,6 +1723,15 @@ packages: dev: true optional: true + /@esbuild/linux-riscv64@0.18.13: + resolution: {integrity: sha512-aIbhU3LPg0lOSCfVeGHbmGYIqOtW6+yzO+Nfv57YblEK01oj0mFMtvDJlOaeAZ6z0FZ9D13oahi5aIl9JFphGg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-s390x@0.17.19: resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} engines: {node: '>=12'} @@ -1606,6 +1741,15 @@ packages: dev: true optional: true + /@esbuild/linux-s390x@0.18.13: + resolution: {integrity: sha512-Pct1QwF2sp+5LVi4Iu5Y+6JsGaV2Z2vm4O9Dd7XZ5tKYxEHjFtb140fiMcl5HM1iuv6xXO8O1Vrb1iJxHlv8UA==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-x64@0.17.19: resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} engines: {node: '>=12'} @@ -1615,6 +1759,15 @@ packages: dev: true optional: true + /@esbuild/linux-x64@0.18.13: + resolution: {integrity: sha512-zTrIP0KzYP7O0+3ZnmzvUKgGtUvf4+piY8PIO3V8/GfmVd3ZyHJGz7Ht0np3P1wz+I8qJ4rjwJKqqEAbIEPngA==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/netbsd-x64@0.17.19: resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} engines: {node: '>=12'} @@ -1624,6 +1777,15 @@ packages: dev: true optional: true + /@esbuild/netbsd-x64@0.18.13: + resolution: {integrity: sha512-I6zs10TZeaHDYoGxENuksxE1sxqZpCp+agYeW039yqFwh3MgVvdmXL5NMveImOC6AtpLvE4xG5ujVic4NWFIDQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + /@esbuild/openbsd-x64@0.17.19: resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} engines: {node: '>=12'} @@ -1633,6 +1795,15 @@ packages: dev: true optional: true + /@esbuild/openbsd-x64@0.18.13: + resolution: {integrity: sha512-W5C5nczhrt1y1xPG5bV+0M12p2vetOGlvs43LH8SopQ3z2AseIROu09VgRqydx5qFN7y9qCbpgHLx0kb0TcW7g==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + /@esbuild/sunos-x64@0.17.19: resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} engines: {node: '>=12'} @@ -1642,6 +1813,15 @@ packages: dev: true optional: true + /@esbuild/sunos-x64@0.18.13: + resolution: {integrity: sha512-X/xzuw4Hzpo/yq3YsfBbIsipNgmsm8mE/QeWbdGdTTeZ77fjxI2K0KP3AlhZ6gU3zKTw1bKoZTuKLnqcJ537qw==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + /@esbuild/win32-arm64@0.17.19: resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} engines: {node: '>=12'} @@ -1651,6 +1831,15 @@ packages: dev: true optional: true + /@esbuild/win32-arm64@0.18.13: + resolution: {integrity: sha512-4CGYdRQT/ILd+yLLE5i4VApMPfGE0RPc/wFQhlluDQCK09+b4JDbxzzjpgQqTPrdnP7r5KUtGVGZYclYiPuHrw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@esbuild/win32-ia32@0.17.19: resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} engines: {node: '>=12'} @@ -1660,6 +1849,15 @@ packages: dev: true optional: true + /@esbuild/win32-ia32@0.18.13: + resolution: {integrity: sha512-D+wKZaRhQI+MUGMH+DbEr4owC2D7XnF+uyGiZk38QbgzLcofFqIOwFs7ELmIeU45CQgfHNy9Q+LKW3cE8g37Kg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@esbuild/win32-x64@0.17.19: resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} engines: {node: '>=12'} @@ -1669,6 +1867,15 @@ packages: dev: true optional: true + /@esbuild/win32-x64@0.18.13: + resolution: {integrity: sha512-iVl6lehAfJS+VmpF3exKpNQ8b0eucf5VWfzR8S7xFve64NBNz2jPUgx1X93/kfnkfgP737O+i1k54SVQS7uVZA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@eslint-community/eslint-utils@4.4.0(eslint@8.27.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1803,6 +2010,48 @@ packages: slash: 3.0.0 dev: true + /@jest/core@29.5.0: + resolution: {integrity: sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/console': 29.5.0 + '@jest/reporters': 29.5.0 + '@jest/test-result': 29.5.0 + '@jest/transform': 29.5.0 + '@jest/types': 29.5.0 + '@types/node': 14.18.29 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.8.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.5.0 + jest-config: 29.5.0(@types/node@14.18.29) + jest-haste-map: 29.5.0 + jest-message-util: 29.5.0 + jest-regex-util: 29.4.3 + jest-resolve: 29.5.0 + jest-resolve-dependencies: 29.5.0 + jest-runner: 29.5.0 + jest-runtime: 29.5.0 + jest-snapshot: 29.5.0 + jest-util: 29.5.0 + jest-validate: 29.5.0 + jest-watcher: 29.5.0 + micromatch: 4.0.5 + pretty-format: 29.5.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - supports-color + - ts-node + dev: true + /@jest/core@29.5.0(ts-node@10.9.1): resolution: {integrity: sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2780,15 +3029,11 @@ packages: resolution: {integrity: sha512-sm+QncWaPmM73IPwFlmWSKPqjdTXZeFf/7aEmWh00z7yl2FjqophPt0dE1EHW9P1giMC5rMviv7OUbSDmWzXXA==} dev: true - /@tanstack/query-core@4.29.19: - resolution: {integrity: sha512-uPe1DukeIpIHpQi6UzIgBcXsjjsDaLnc7hF+zLBKnaUlh7jFE/A+P8t4cU4VzKPMFB/C970n/9SxtpO5hmIRgw==} - dev: true - /@tanstack/query-core@4.29.7: resolution: {integrity: sha512-GXG4b5hV2Loir+h2G+RXhJdoZhJLnrBWsuLB2r0qBRyhWuXq9w/dWxzvpP89H0UARlH6Mr9DiVj4SMtpkF/aUA==} dev: true - /@tanstack/react-query@4.28.0(react-dom@17.0.2)(react@17.0.2): + /@tanstack/react-query@4.28.0(react@18.2.0): resolution: {integrity: sha512-8cGBV5300RHlvYdS4ea+G1JcZIt5CIuprXYFnsWggkmGoC0b5JaqG0fIX3qwDL9PTNkKvG76NGThIWbpXivMrQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -2801,30 +3046,11 @@ packages: optional: true dependencies: '@tanstack/query-core': 4.27.0 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - use-sync-external-store: 1.2.0(react@17.0.2) - dev: true - - /@tanstack/react-query@4.29.19(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-XiTIOHHQ5Cw1WUlHaD4fmVUMhoWjuNJlAeJGq7eM4BraI5z7y8WkZO+NR8PSuRnQGblpuVdjClQbDFtwxTtTUw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-native: '*' - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true - dependencies: - '@tanstack/query-core': 4.29.19 react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) use-sync-external-store: 1.2.0(react@18.2.0) dev: true - /@tanstack/react-query@4.29.7(react-dom@17.0.2)(react@17.0.2): + /@tanstack/react-query@4.29.7(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-ijBWEzAIo09fB1yd22slRZzprrZ5zMdWYzBnCg5qiXuFbH78uGN1qtGz8+Ed4MuhaPaYSD+hykn+QEKtQviEtg==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -2837,9 +3063,9 @@ packages: optional: true dependencies: '@tanstack/query-core': 4.29.7 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - use-sync-external-store: 1.2.0(react@17.0.2) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + use-sync-external-store: 1.2.0(react@18.2.0) dev: true /@tanstack/svelte-query@4.29.7(svelte@3.59.2): @@ -2863,7 +3089,7 @@ packages: '@trpc/server': 10.32.0 dev: true - /@trpc/next@10.32.0(@tanstack/react-query@4.29.19)(@trpc/client@10.32.0)(@trpc/react-query@10.32.0)(@trpc/server@10.32.0)(next@13.4.7)(react-dom@18.2.0)(react@18.2.0): + /@trpc/next@10.32.0(@tanstack/react-query@4.29.7)(@trpc/client@10.32.0)(@trpc/react-query@10.32.0)(@trpc/server@10.32.0)(next@13.4.7)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-qJ3yvJDTVmiOOPW1x/FCpmOvtcMJXnP2t3EbcUFrqwgH+D1KdbU2Kk1iSa89Gwy/BBEmXUxaKkWoM3e4Id80gw==} peerDependencies: '@tanstack/react-query': ^4.18.0 @@ -2874,9 +3100,9 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' dependencies: - '@tanstack/react-query': 4.29.19(react-dom@18.2.0)(react@18.2.0) + '@tanstack/react-query': 4.29.7(react-dom@18.2.0)(react@18.2.0) '@trpc/client': 10.32.0(@trpc/server@10.32.0) - '@trpc/react-query': 10.32.0(@tanstack/react-query@4.29.19)(@trpc/client@10.32.0)(@trpc/server@10.32.0)(react-dom@18.2.0)(react@18.2.0) + '@trpc/react-query': 10.32.0(@tanstack/react-query@4.29.7)(@trpc/client@10.32.0)(@trpc/server@10.32.0)(react-dom@18.2.0)(react@18.2.0) '@trpc/server': 10.32.0 next: 13.4.7(@babel/core@7.22.5)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 @@ -2884,7 +3110,7 @@ packages: react-ssr-prepass: 1.5.0(react@18.2.0) dev: true - /@trpc/react-query@10.32.0(@tanstack/react-query@4.29.19)(@trpc/client@10.32.0)(@trpc/server@10.32.0)(react-dom@18.2.0)(react@18.2.0): + /@trpc/react-query@10.32.0(@tanstack/react-query@4.29.7)(@trpc/client@10.32.0)(@trpc/server@10.32.0)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-M4W1aGm3VAfQ9u4FJyK8M+pkcAV4lp32ZHxKnkRydxcsladhsqveZC9oqZqwklprEcYHtWU8v3R+jRGUjfCxpw==} peerDependencies: '@tanstack/react-query': ^4.18.0 @@ -2893,7 +3119,7 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' dependencies: - '@tanstack/react-query': 4.29.19(react-dom@18.2.0)(react@18.2.0) + '@tanstack/react-query': 4.29.7(react-dom@18.2.0)(react@18.2.0) '@trpc/client': 10.32.0(@trpc/server@10.32.0) '@trpc/server': 10.32.0 react: 18.2.0 @@ -3147,8 +3373,8 @@ packages: resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} dev: true - /@types/react@18.0.26: - resolution: {integrity: sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==} + /@types/react@18.2.0: + resolution: {integrity: sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA==} dependencies: '@types/prop-types': 15.7.5 '@types/scheduler': 0.16.3 @@ -3796,6 +4022,10 @@ packages: engines: {node: '>=12'} dev: true + /any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: true + /anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -4158,6 +4388,15 @@ packages: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + + /bundle-require@4.0.1(esbuild@0.18.13): + resolution: {integrity: sha512-9NQkRHlNdNpDBGmLpngF3EFDcwodhMUuLz9PaWYciVcQF9SE4LFjM2DB/xV1Li5JiuDMv7ZUWuC3rGbqR0MAXQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.17' + dependencies: + esbuild: 0.18.13 + load-tsconfig: 0.2.5 dev: true /busboy@1.6.0: @@ -4509,6 +4748,11 @@ packages: engines: {node: '>=14'} dev: true + /commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + dev: true + /commander@6.2.1: resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} engines: {node: '>= 6'} @@ -4764,7 +5008,6 @@ packages: /decimal.js@10.4.2: resolution: {integrity: sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA==} - dev: false /decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} @@ -5308,6 +5551,36 @@ packages: '@esbuild/win32-x64': 0.17.19 dev: true + /esbuild@0.18.13: + resolution: {integrity: sha512-vhg/WR/Oiu4oUIkVhmfcc23G6/zWuEQKFS+yiosSHe4aN6+DQRXIfeloYGibIfVhkr4wyfuVsGNLr+sQU1rWWw==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.18.13 + '@esbuild/android-arm64': 0.18.13 + '@esbuild/android-x64': 0.18.13 + '@esbuild/darwin-arm64': 0.18.13 + '@esbuild/darwin-x64': 0.18.13 + '@esbuild/freebsd-arm64': 0.18.13 + '@esbuild/freebsd-x64': 0.18.13 + '@esbuild/linux-arm': 0.18.13 + '@esbuild/linux-arm64': 0.18.13 + '@esbuild/linux-ia32': 0.18.13 + '@esbuild/linux-loong64': 0.18.13 + '@esbuild/linux-mips64el': 0.18.13 + '@esbuild/linux-ppc64': 0.18.13 + '@esbuild/linux-riscv64': 0.18.13 + '@esbuild/linux-s390x': 0.18.13 + '@esbuild/linux-x64': 0.18.13 + '@esbuild/netbsd-x64': 0.18.13 + '@esbuild/openbsd-x64': 0.18.13 + '@esbuild/sunos-x64': 0.18.13 + '@esbuild/win32-arm64': 0.18.13 + '@esbuild/win32-ia32': 0.18.13 + '@esbuild/win32-x64': 0.18.13 + dev: true + /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -6147,6 +6420,17 @@ packages: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} dev: true + /glob@7.1.6: + resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} dependencies: @@ -6742,14 +7026,14 @@ packages: node-notifier: optional: true dependencies: - '@jest/core': 29.5.0(ts-node@10.9.1) + '@jest/core': 29.5.0 '@jest/test-result': 29.5.0 '@jest/types': 29.5.0 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 import-local: 3.1.0 - jest-config: 29.5.0(@types/node@14.18.32)(ts-node@10.9.1) + jest-config: 29.5.0(@types/node@14.18.29) jest-util: 29.5.0 jest-validate: 29.5.0 prompts: 2.4.2 @@ -6816,6 +7100,45 @@ packages: - ts-node dev: true + /jest-config@29.5.0(@types/node@14.18.29): + resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + dependencies: + '@babel/core': 7.22.5 + '@jest/test-sequencer': 29.5.0 + '@jest/types': 29.5.0 + '@types/node': 14.18.29 + babel-jest: 29.5.0(@babel/core@7.22.5) + chalk: 4.1.2 + ci-info: 3.8.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.5.0 + jest-environment-node: 29.5.0 + jest-get-type: 29.4.3 + jest-regex-util: 29.4.3 + jest-resolve: 29.5.0 + jest-runner: 29.5.0 + jest-util: 29.5.0 + jest-validate: 29.5.0 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 29.5.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + /jest-config@29.5.0(@types/node@14.18.29)(ts-node@10.9.1): resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -7201,7 +7524,7 @@ packages: node-notifier: optional: true dependencies: - '@jest/core': 29.5.0(ts-node@10.9.1) + '@jest/core': 29.5.0 '@jest/types': 29.5.0 import-local: 3.1.0 jest-cli: 29.5.0 @@ -7251,6 +7574,11 @@ packages: - ts-node dev: true + /joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + dev: true + /js-sdsl@4.4.1: resolution: {integrity: sha512-6Gsx8R0RucyePbWqPssR8DyfuXmLBooYN5cZFZKjHGnQuaf7pEzhtpceagJxVu4LqhYY5EYA7nko3FmeHZ1KbA==} dev: true @@ -7406,6 +7734,11 @@ packages: set-cookie-parser: 2.6.0 dev: true + /lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + dev: true + /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -7422,6 +7755,11 @@ packages: array-back: 6.2.2 dev: true + /load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + /load-yaml-file@0.2.0: resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} engines: {node: '>=6'} @@ -7473,6 +7811,10 @@ packages: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true + /lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + dev: true + /lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} dev: true @@ -7753,6 +8095,14 @@ packages: engines: {node: '>=12.0.0'} dev: true + /mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + dev: true + /nanoid@3.3.6: resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -8338,6 +8688,22 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} + /postcss-load-config@4.0.1: + resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 2.1.0 + yaml: 2.2.1 + dev: true + /postcss@8.4.14: resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==} engines: {node: ^10 || ^12 || >=14} @@ -8581,17 +8947,6 @@ packages: minimist: 1.2.8 strip-json-comments: 2.0.1 - /react-dom@17.0.2(react@17.0.2): - resolution: {integrity: sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==} - peerDependencies: - react: 17.0.2 - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react: 17.0.2 - scheduler: 0.20.2 - dev: true - /react-dom@18.2.0(react@18.2.0): resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: @@ -8618,14 +8973,6 @@ packages: react: 18.2.0 dev: true - /react@17.0.2: - resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==} - engines: {node: '>=0.10.0'} - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - dev: true - /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -8934,13 +9281,6 @@ packages: resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} dev: true - /scheduler@0.20.2: - resolution: {integrity: sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==} - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - dev: true - /scheduler@0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} dependencies: @@ -9161,6 +9501,13 @@ packages: engines: {node: '>=0.10.0'} dev: true + /source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + dependencies: + whatwg-url: 7.1.0 + dev: true + /spawn-command@0.0.2-1: resolution: {integrity: sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==} dev: true @@ -9390,6 +9737,20 @@ packages: react: 18.2.0 dev: true + /sucrase@3.33.0: + resolution: {integrity: sha512-ARGC7vbufOHfpvyGcZZXFaXCMZ9A4fffOGC5ucOW7+WHDGlAe8LJdf3Jts1sWhDeiI1RSWrKy5Hodl+JWGdW2A==} + engines: {node: '>=8'} + hasBin: true + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + commander: 4.1.1 + glob: 7.1.6 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + dev: true + /superagent@8.0.9: resolution: {integrity: sha512-4C7Bh5pyHTvU33KpZgwrNKh/VQnvgtCSqPRfJAUdmrtSYePVzVg4E4OzsrbkhJj9O7SO6Bnv75K/F8XVZT8YHA==} engines: {node: '>=6.4.0 <13 || >=14'} @@ -9469,14 +9830,14 @@ packages: engines: {node: '>= 8'} dev: true - /swr@2.0.3(react@17.0.2): + /swr@2.0.3(react@18.2.0): resolution: {integrity: sha512-sGvQDok/AHEWTPfhUWXEHBVEXmgGnuahyhmRQbjl9XBYxT/MSlAzvXEKQpyM++bMPaI52vcWS2HiKNaW7+9OFw==} engines: {pnpm: '7'} peerDependencies: react: ^16.11.0 || ^17.0.0 || ^18.0.0 dependencies: - react: 17.0.2 - use-sync-external-store: 1.2.0(react@17.0.2) + react: 18.2.0 + use-sync-external-store: 1.2.0(react@18.2.0) dev: true /table-layout@1.0.2: @@ -9562,6 +9923,19 @@ packages: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true + /thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + dependencies: + thenify: 3.3.1 + dev: true + + /thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + dependencies: + any-promise: 1.3.0 + dev: true + /thread-stream@2.3.0: resolution: {integrity: sha512-kaDqm1DET9pp3NXwR8382WHbnpXnRkN9xGN9dQt3B2+dmXiW8X1SOwmFOxAErEQ47ObhZ96J6yhZNXuyCOL7KA==} dependencies: @@ -9638,6 +10012,12 @@ packages: /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + /tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + dependencies: + punycode: 2.3.0 + dev: true + /tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -9648,12 +10028,16 @@ packages: engines: {node: '>=8'} dev: true + /ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + dev: true + /ts-japi@1.8.0: resolution: {integrity: sha512-77cNpz1CMJsImcikE7gnihMKOME4c/prCaruBmr7yMRHGnmEt1llFyJbuokXCaEmm0hERPv2VlzTyZ+pb43leg==} engines: {node: '>=10'} dev: false - /ts-jest@29.0.1(@babel/core@7.22.5)(jest@29.5.0)(typescript@4.6.2): + /ts-jest@29.0.1(@babel/core@7.22.5)(esbuild@0.18.13)(jest@29.5.0)(typescript@4.6.2): resolution: {integrity: sha512-htQOHshgvhn93QLxrmxpiQPk69+M1g7govO1g6kf6GsjCv4uvRV0znVmDrrvjUrVCnTYeY4FBxTYYYD4airyJA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -9676,6 +10060,7 @@ packages: dependencies: '@babel/core': 7.22.5 bs-logger: 0.2.6 + esbuild: 0.18.13 fast-json-stable-stringify: 2.1.0 jest: 29.5.0(@types/node@14.18.29)(ts-node@10.9.1) jest-util: 29.5.0 @@ -9722,7 +10107,7 @@ packages: yargs-parser: 21.1.1 dev: true - /ts-jest@29.0.5(@babel/core@7.22.5)(jest@29.5.0)(typescript@4.9.4): + /ts-jest@29.0.5(@babel/core@7.22.5)(esbuild@0.18.13)(jest@29.5.0)(typescript@4.9.4): resolution: {integrity: sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -9745,6 +10130,7 @@ packages: dependencies: '@babel/core': 7.22.5 bs-logger: 0.2.6 + esbuild: 0.18.13 fast-json-stable-stringify: 2.1.0 jest: 29.5.0 jest-util: 29.5.0 @@ -9756,7 +10142,7 @@ packages: yargs-parser: 21.1.1 dev: true - /ts-jest@29.0.5(@babel/core@7.22.5)(jest@29.5.0)(typescript@4.9.5): + /ts-jest@29.0.5(@babel/core@7.22.5)(esbuild@0.18.13)(jest@29.5.0)(typescript@4.9.5): resolution: {integrity: sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -9779,6 +10165,7 @@ packages: dependencies: '@babel/core': 7.22.5 bs-logger: 0.2.6 + esbuild: 0.18.13 fast-json-stable-stringify: 2.1.0 jest: 29.5.0 jest-util: 29.5.0 @@ -9790,6 +10177,40 @@ packages: yargs-parser: 21.1.1 dev: true + /ts-jest@29.0.5(@babel/core@7.22.5)(jest@29.5.0)(typescript@4.9.4): + resolution: {integrity: sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + '@babel/core': 7.22.5 + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + jest: 29.5.0 + jest-util: 29.5.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.5.3 + typescript: 4.9.4 + yargs-parser: 21.1.1 + dev: true + /ts-morph@16.0.0: resolution: {integrity: sha512-jGNF0GVpFj0orFw55LTsQxVYEUOCWBAbR5Ls7fTYE5pQsbW18ssTb/6UXx/GYAEjS+DQTp8VoTw0vqYMiaaQuw==} dependencies: @@ -9887,6 +10308,41 @@ packages: /tslib@2.6.0: resolution: {integrity: sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==} + /tsup@7.1.0: + resolution: {integrity: sha512-mazl/GRAk70j8S43/AbSYXGgvRP54oQeX8Un4iZxzATHt0roW0t6HYDVZIXMw0ZQIpvr1nFMniIVnN5186lW7w==} + engines: {node: '>=16.14'} + hasBin: true + peerDependencies: + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.1.0' + peerDependenciesMeta: + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + dependencies: + bundle-require: 4.0.1(esbuild@0.18.13) + cac: 6.7.14 + chokidar: 3.5.3 + debug: 4.3.4 + esbuild: 0.18.13 + execa: 5.1.1 + globby: 11.1.0 + joycon: 3.1.1 + postcss-load-config: 4.0.1 + resolve-from: 5.0.0 + rollup: 3.25.3 + source-map: 0.8.0-beta.0 + sucrase: 3.33.0 + tree-kill: 1.2.2 + transitivePeerDependencies: + - supports-color + - ts-node + dev: true + /tsutils@3.21.0(typescript@4.6.2): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} @@ -10143,14 +10599,6 @@ packages: engines: {node: '>=0.12.0'} dev: false - /use-sync-external-store@1.2.0(react@17.0.2): - resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - react: 17.0.2 - dev: true - /use-sync-external-store@1.2.0(react@18.2.0): resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} peerDependencies: @@ -10394,6 +10842,10 @@ packages: /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + /webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + dev: true + /whatwg-fetch@3.6.2: resolution: {integrity: sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==} dev: true @@ -10404,6 +10856,14 @@ packages: tr46: 0.0.3 webidl-conversions: 3.0.1 + /whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + dev: true + /when@3.7.8: resolution: {integrity: sha512-5cZ7mecD3eYcMiCH4wtRPA5iFJZ50BJYDfckI5RRpQiktMiYTcn0ccLTZOvcbBume+1304fQztxeNzNS9Gvrnw==} dev: false @@ -10542,7 +11002,6 @@ packages: /yaml@2.2.1: resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==} engines: {node: '>= 14'} - dev: false /yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} diff --git a/tests/integration/test-run/package-lock.json b/tests/integration/test-run/package-lock.json index 2bdb83310..1cd36856e 100644 --- a/tests/integration/test-run/package-lock.json +++ b/tests/integration/test-run/package-lock.json @@ -132,6 +132,7 @@ "@paralleldrive/cuid2": "^2.2.0", "@types/bcryptjs": "^2.4.2", "bcryptjs": "^2.4.3", + "buffer": "^6.0.3", "change-case": "^4.1.2", "colors": "1.4.0", "decimal.js": "^10.4.2", @@ -370,6 +371,7 @@ "@types/node": "^14.18.29", "@types/pluralize": "^0.0.29", "bcryptjs": "^2.4.3", + "buffer": "^6.0.3", "change-case": "^4.1.2", "colors": "1.4.0", "copyfiles": "^2.4.1", diff --git a/tests/integration/tests/nextjs/test-project/package-lock.json b/tests/integration/tests/nextjs/test-project/package-lock.json index d7b1be834..ae60ac1b9 100644 --- a/tests/integration/tests/nextjs/test-project/package-lock.json +++ b/tests/integration/tests/nextjs/test-project/package-lock.json @@ -17,6 +17,7 @@ "next": "13.1.4", "react": "18.2.0", "react-dom": "18.2.0", + "superjson": "^1.12.4", "swr": "^2.2.0", "typescript": "4.9.4", "zod": "3.21.1" @@ -331,11 +332,36 @@ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/csstype": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" }, + "node_modules/is-what": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.15.tgz", + "integrity": "sha512-uKua1wfy3Yt+YqsD6mTUEa2zSi3G1oPlqTflgaPJ7z63vUGN5pxFpnQfeSLMFnJDEsdvOtkp1rUWkYjB4YfhgA==", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -520,6 +546,17 @@ } } }, + "node_modules/superjson": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-1.12.4.tgz", + "integrity": "sha512-vkpPQAxdCg9SLfPv5GPC5fnGrui/WryktoN9O5+Zif/14QIMjw+RITf/5LbBh+9QpBFb3KNvJth+puz2H8o6GQ==", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/swr": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.0.tgz", @@ -725,11 +762,24 @@ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" }, + "copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "requires": { + "is-what": "^4.1.8" + } + }, "csstype": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" }, + "is-what": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.15.tgz", + "integrity": "sha512-uKua1wfy3Yt+YqsD6mTUEa2zSi3G1oPlqTflgaPJ7z63vUGN5pxFpnQfeSLMFnJDEsdvOtkp1rUWkYjB4YfhgA==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -835,6 +885,14 @@ "client-only": "0.0.1" } }, + "superjson": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-1.12.4.tgz", + "integrity": "sha512-vkpPQAxdCg9SLfPv5GPC5fnGrui/WryktoN9O5+Zif/14QIMjw+RITf/5LbBh+9QpBFb3KNvJth+puz2H8o6GQ==", + "requires": { + "copy-anything": "^3.0.2" + } + }, "swr": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.0.tgz", diff --git a/tests/integration/tests/nextjs/test-project/package.json b/tests/integration/tests/nextjs/test-project/package.json index e9fc87b33..a412f8674 100644 --- a/tests/integration/tests/nextjs/test-project/package.json +++ b/tests/integration/tests/nextjs/test-project/package.json @@ -18,6 +18,7 @@ "next": "13.1.4", "react": "18.2.0", "react-dom": "18.2.0", + "superjson": "^1.12.4", "swr": "^2.2.0", "typescript": "4.9.4", "zod": "3.21.1" diff --git a/tests/integration/utils/jest-ext.ts b/tests/integration/utils/jest-ext.ts index 9d49d82ca..649ba104c 100644 --- a/tests/integration/utils/jest-ext.ts +++ b/tests/integration/utils/jest-ext.ts @@ -1,5 +1,5 @@ import { format } from 'util'; -import { isPrismaClientKnownRequestError } from '@zenstackhq/runtime/error'; +import { isPrismaClientKnownRequestError } from '@zenstackhq/runtime'; export const toBeRejectedByPolicy = async function (received: Promise, expectedMessages?: string[]) { if (!(received instanceof Promise)) {