diff --git a/common/api-review/data-connect.api.md b/common/api-review/data-connect.api.md index 1a698c229b4..1b93be0e9c3 100644 --- a/common/api-review/data-connect.api.md +++ b/common/api-review/data-connect.api.md @@ -52,6 +52,36 @@ export class DataConnect { setInitialized(): void; } +// @public +export class DataConnectError extends FirebaseError { + readonly code: DataConnectErrorCode; +} + +// @public (undocumented) +export type DataConnectErrorCode = 'other' | 'already-initialized' | 'not-initialized' | 'not-supported' | 'invalid-argument' | 'partial-error' | 'unauthorized'; + +// @public +export class DataConnectOperationError extends DataConnectError { + /* Excluded from this release type: name */ + readonly response: DataConnectOperationResponse; +} + +// @public (undocumented) +export interface DataConnectOperationErrorInfo { + // (undocumented) + readonly message: string; + // (undocumented) + readonly path: Array; +} + +// @public (undocumented) +export interface DataConnectOperationResponse { + // (undocumented) + readonly data?: Record | null; + // (undocumented) + readonly errors: DataConnectOperationErrorInfo[]; +} + // @public export interface DataConnectOptions extends ConnectorConfig { // (undocumented) @@ -67,7 +97,7 @@ export interface DataConnectResult extends OpResult { // @public export interface DataConnectSubscription { // (undocumented) - errCallback?: (e?: FirebaseError) => void; + errCallback?: (e?: DataConnectError) => void; // (undocumented) unsubscribe: () => void; // (undocumented) @@ -118,7 +148,7 @@ export interface MutationResult extends DataConnectResult void; // @public -export type OnErrorSubscription = (err?: FirebaseError) => void; +export type OnErrorSubscription = (err?: DataConnectError) => void; // @public export type OnResultSubscription = (res: QueryResult) => void; diff --git a/packages/data-connect/src/api/index.ts b/packages/data-connect/src/api/index.ts index 885dac5a923..71bd95557dc 100644 --- a/packages/data-connect/src/api/index.ts +++ b/packages/data-connect/src/api/index.ts @@ -22,3 +22,11 @@ export * from './Mutation'; export * from './query'; export { setLogLevel } from '../logger'; export { validateArgs } from '../util/validateArgs'; + +export { DataConnectError, DataConnectOperationError } from '../core/error'; + +export type { + DataConnectErrorCode, + DataConnectOperationResponse, + DataConnectOperationErrorInfo +} from '../core/error'; diff --git a/packages/data-connect/src/core/error.ts b/packages/data-connect/src/core/error.ts index f0beb128afa..3c817f616c3 100644 --- a/packages/data-connect/src/core/error.ts +++ b/packages/data-connect/src/core/error.ts @@ -40,8 +40,8 @@ export const Code = { /** An error returned by a DataConnect operation. */ export class DataConnectError extends FirebaseError { - /** The stack of the error. */ - readonly stack?: string; + /** @internal */ + readonly name: string = 'DataConnectError'; /** @hideconstructor */ constructor( @@ -52,13 +52,64 @@ export class DataConnectError extends FirebaseError { /** * A custom error description. */ - readonly message: string + message: string ) { super(code, message); - // HACK: We write a toString property directly because Error is not a real - // class and so inheritance does not work correctly. We could alternatively - // do the same "back-door inheritance" trick that FirebaseError does. - this.toString = () => `${this.name}: [code=${this.code}]: ${this.message}`; + // Ensure the instanceof operator works as expected on subclasses of Error. + // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#custom_error_types + // and https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget + Object.setPrototypeOf(this, new.target.prototype); } + + /** @internal */ + toString(): string { + return `${this.name}[code=${this.code}]: ${this.message}`; + } +} + +/** An error returned by a DataConnect operation. */ +export class DataConnectOperationError extends DataConnectError { + /** @internal */ + readonly name: string = 'DataConnectOperationError'; + + /** The response received from the backend. */ + readonly response: DataConnectOperationResponse; + + /** @hideconstructor */ + constructor(message: string, response: DataConnectOperationResponse) { + super('partial-error', message); + this.response = response; + + // Ensure the instanceof operator works as expected on subclasses of Error. + // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#custom_error_types + // and https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget + Object.setPrototypeOf(this, new.target.prototype); + } +} + +export interface DataConnectOperationResponse { + // The "data" provided by the backend in the response message. + // + // Will be `undefined` if no "data" was provided in the response message. + // Otherwise, will be `null` if `null` was explicitly specified as the "data" + // in the response message. Otherwise, will be the value of the "data" + // specified as the "data" in the response message + readonly data?: Record | null; + + // The list of errors provided by the backend in the response message. + readonly errors: DataConnectOperationErrorInfo[]; +} + +// Information about the error, as provided in the response from the backend. +// See https://spec.graphql.org/draft/#sec-Errors +export interface DataConnectOperationErrorInfo { + // The error message. + readonly message: string; + + // The path of the field in the response data to which this error relates. + // String values in this array refer to field names. Numeric values in this + // array always satisfy `Number.isInteger()` and refer to the index in an + // array. + readonly path: Array; }