Files
AFFiNE-Mirror/packages/frontend/workspace-impl/src/cloud/blob.ts
T
liuyi 26db1d436d refactor(server): server errors (#5741)
standardize the error raising in both GraphQL Resolvers and Controllers.

Now, All user aware errors should be throwed with `HttpException`'s variants, for example `NotFoundException`.

> Directly throwing `GraphQLError` are forbidden.
The GraphQL errorFormatter will handle it automatically and set `code`, `status` in error extensions.

At the same time, the frontend `GraphQLError` should be imported from `@affine/graphql`, which introduce a better error extensions type.

----
controller example:
```js
@Get('/docs/${id}')
doc() {
  // ...
  // imported from '@nestjs/common'
  throw new NotFoundException('Doc is not found.');
  // ...
}
```
the above will response as:
```
status: 404 Not Found
{
  "message": "Doc is not found.",
  "statusCode": 404,
  "error": "Not Found"
}
```

resolver example:
```js
@Mutation()
invite() {
  // ...
  throw new PayloadTooLargeException('Workspace seats is full.')
  // ...
}
```

the above will response as:
```
status: 200 Ok
{
  "data": null,
  "errors": [
    {
      "message": "Workspace seats is full.",
      "extensions": {
        "code": 404,
        "status": "Not Found"
      }
    }
  ]
}
```

for frontend GraphQLError user-friend, a helper function introduced:

```js
import { findGraphQLError } from '@affine/graphql'

fetch(query)
  .catch(errOrArr => {
    const e = findGraphQLError(errOrArr, e => e.extensions.code === 404)
    if (e) {
      // handle
    }
})
```
2024-01-31 08:43:03 +00:00

78 lines
1.7 KiB
TypeScript

import {
deleteBlobMutation,
fetchWithTraceReport,
findGraphQLError,
getBaseUrl,
listBlobsQuery,
setBlobMutation,
} from '@affine/graphql';
import { fetcher } from '@affine/graphql';
import { type BlobStorage, BlobStorageOverCapacity } from '@toeverything/infra';
import { bufferToBlob } from '../utils/buffer-to-blob';
export class AffineCloudBlobStorage implements BlobStorage {
constructor(private readonly workspaceId: string) {}
name = 'affine-cloud';
readonly = false;
async get(key: string) {
const suffix = key.startsWith('/')
? key
: `/api/workspaces/${this.workspaceId}/blobs/${key}`;
return fetchWithTraceReport(getBaseUrl() + suffix).then(async res => {
if (!res.ok) {
// status not in the range 200-299
return null;
}
return bufferToBlob(await res.arrayBuffer());
});
}
async set(key: string, value: Blob) {
// set blob will check blob size & quota
return await fetcher({
query: setBlobMutation,
variables: {
workspaceId: this.workspaceId,
blob: new File([value], key),
},
})
.then(res => res.setBlob)
.catch(err => {
const uploadError = findGraphQLError(
err,
e => e.extensions.code === 413
);
if (uploadError) {
throw new BlobStorageOverCapacity(uploadError);
}
throw err;
});
}
async delete(key: string) {
await fetcher({
query: deleteBlobMutation,
variables: {
workspaceId: key,
hash: key,
},
});
}
async list() {
const result = await fetcher({
query: listBlobsQuery,
variables: {
workspaceId: this.workspaceId,
},
});
return result.listBlobs;
}
}