feat!: affine cloud support (#3813)

Co-authored-by: Hongtao Lye <codert.sn@gmail.com>
Co-authored-by: liuyi <forehalo@gmail.com>
Co-authored-by: LongYinan <lynweklm@gmail.com>
Co-authored-by: X1a0t <405028157@qq.com>
Co-authored-by: JimmFly <yangjinfei001@gmail.com>
Co-authored-by: Peng Xiao <pengxiao@outlook.com>
Co-authored-by: xiaodong zuo <53252747+zuoxiaodong0815@users.noreply.github.com>
Co-authored-by: DarkSky <25152247+darkskygit@users.noreply.github.com>
Co-authored-by: Qi <474021214@qq.com>
Co-authored-by: danielchim <kahungchim@gmail.com>
This commit is contained in:
Alex Yang
2023-08-29 05:07:05 -05:00
committed by GitHub
parent d0145c6f38
commit 2f6c4e3696
414 changed files with 19469 additions and 7591 deletions

View File

@@ -22,6 +22,8 @@
"postinstall": "gql-gen"
},
"dependencies": {
"graphql": "^16.8.0"
"@affine/env": "workspace:*",
"graphql": "^16.8.0",
"nanoid": "^4.0.2"
}
}

View File

@@ -1,8 +1,15 @@
import { nanoid } from 'nanoid';
import type { Mock } from 'vitest';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { gqlFetcherFactory } from '../fetcher';
import type { GraphQLQuery } from '../graphql';
import {
generateRandUTF16Chars,
SPAN_ID_BYTES,
TRACE_ID_BYTES,
TraceReporter,
} from '../utils';
const query: GraphQLQuery = {
id: 'query',
@@ -51,16 +58,18 @@ describe('GraphQL fetcher', () => {
variables: { a: 1, b: '2', c: { d: false } },
});
expect(fetch.mock.lastCall[1]).toMatchInlineSnapshot(`
{
"body": "{\\"query\\":\\"query { field }\\",\\"variables\\":{\\"a\\":1,\\"b\\":\\"2\\",\\"c\\":{\\"d\\":false}},\\"operationName\\":\\"query\\"}",
"headers": {
"x-definition-name": "query",
"x-operation-name": "query",
},
"method": "POST",
}
`);
expect(fetch.mock.lastCall[1]).toEqual(
expect.objectContaining({
body: '{"query":"query { field }","variables":{"a":1,"b":"2","c":{"d":false}},"operationName":"query"}',
headers: expect.objectContaining({
'content-type': 'application/json',
'x-definition-name': 'query',
'x-operation-name': 'query',
'x-request-id': expect.any(String),
}),
method: 'POST',
})
);
});
it('should correctly ignore nil variables', async () => {
@@ -110,3 +119,41 @@ describe('GraphQL fetcher', () => {
`);
});
});
describe('Trace Reporter', () => {
const startTime = new Date().toISOString();
const traceId = generateRandUTF16Chars(TRACE_ID_BYTES);
const spanId = generateRandUTF16Chars(SPAN_ID_BYTES);
const requestId = nanoid();
it('spanId, traceId should be right format', () => {
expect(
new RegExp(`^[0-9a-f]{${SPAN_ID_BYTES * 2}}$`).test(
generateRandUTF16Chars(SPAN_ID_BYTES)
)
).toBe(true);
expect(
new RegExp(`^[0-9a-f]{${TRACE_ID_BYTES * 2}}$`).test(
generateRandUTF16Chars(TRACE_ID_BYTES)
)
).toBe(true);
});
it('test createTraceSpan', () => {
const traceSpan = TraceReporter.createTraceSpan(
traceId,
spanId,
requestId,
startTime
);
expect(traceSpan.startTime).toBe(startTime);
expect(
traceSpan.name ===
`projects/{GCP_PROJECT_ID}/traces/${traceId}/spans/${spanId}`
).toBe(true);
expect(traceSpan.spanId).toBe(spanId);
expect(traceSpan.attributes.attributeMap.requestId.stringValue.value).toBe(
requestId
);
});
});

View File

@@ -1,9 +1,18 @@
import type { ExecutionResult } from 'graphql';
import { GraphQLError } from 'graphql';
import { isNil, isObject, merge } from 'lodash-es';
import { nanoid } from 'nanoid';
import type { GraphQLQuery } from './graphql';
import type { Mutations, Queries } from './schema';
import {
generateRandUTF16Chars,
SPAN_ID_BYTES,
TRACE_FLAG,
TRACE_ID_BYTES,
TRACE_VERSION,
traceReporter,
} from './utils';
export type NotArray<T> = T extends Array<unknown> ? never : T;
@@ -116,19 +125,24 @@ export function transformToForm(body: RequestBody) {
if (body.operationName) {
gqlBody.name = body.operationName;
}
const map: Record<string, [string]> = {};
const files: File[] = [];
if (body.variables) {
let i = 0;
Object.entries(body.variables).forEach(([key, value]) => {
if (value instanceof File) {
gqlBody.map['0'] = [`variables.${key}`];
form.append(`${i}`, value);
map['0'] = [`variables.${key}`];
files[i] = value;
i++;
}
});
}
form.append('operations', JSON.stringify(gqlBody));
form.set('operations', JSON.stringify(gqlBody));
form.set('map', JSON.stringify(map));
for (const [i, file] of files.entries()) {
form.set(`${i}`, file);
}
return form;
}
@@ -159,20 +173,25 @@ export const gqlFetcherFactory = (endpoint: string) => {
): Promise<QueryResponse<Query>> => {
const body = formatRequestBody(options);
const ret = fetch(
const isFormData = body instanceof FormData;
const headers: Record<string, string> = {
'x-operation-name': options.query.operationName,
'x-definition-name': options.query.definitionName,
};
if (!isFormData) {
headers['content-type'] = 'application/json';
}
const ret = fetchWithReport(
endpoint,
merge(options.context, {
method: 'POST',
headers: {
'x-operation-name': options.query.operationName,
'x-definition-name': options.query.definitionName,
},
body: body instanceof FormData ? body : JSON.stringify(body),
headers,
body: isFormData ? body : JSON.stringify(body),
})
).then(async res => {
if (res.headers.get('content-type') === 'application/json') {
if (res.headers.get('content-type')?.startsWith('application/json')) {
const result = (await res.json()) as ExecutionResult;
if (res.status >= 400) {
if (res.status >= 400 || result.errors) {
if (result.errors && result.errors.length > 0) {
throw result.errors.map(
error => new GraphQLError(error.message, error)
@@ -194,3 +213,40 @@ export const gqlFetcherFactory = (endpoint: string) => {
return gqlFetch;
};
export const fetchWithReport = (
input: RequestInfo | URL,
init?: RequestInit
): Promise<Response> => {
const startTime = new Date().toISOString();
const spanId = generateRandUTF16Chars(SPAN_ID_BYTES);
const traceId = generateRandUTF16Chars(TRACE_ID_BYTES);
const traceparent = `${TRACE_VERSION}-${traceId}-${spanId}-${TRACE_FLAG}`;
init = init || {};
init.headers = init.headers || new Headers();
const requestId = nanoid();
if (init.headers instanceof Headers) {
init.headers.append('x-request-id', requestId);
init.headers.append('traceparent', traceparent);
} else {
const headers = init.headers as Record<string, string>;
headers['x-request-id'] = requestId;
headers['traceparent'] = traceparent;
}
if (!traceReporter) {
return fetch(input, init);
}
return fetch(input, init)
.then(response => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
traceReporter!.cacheTrace(traceId, spanId, requestId, startTime);
return response;
})
.catch(err => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
traceReporter!.uploadTrace(traceId, spanId, requestId, startTime);
return Promise.reject(err);
});
};

View File

@@ -0,0 +1,3 @@
mutation deleteBlob($workspaceId: String!, $hash: String!) {
deleteBlob(workspaceId: $workspaceId, hash: $hash)
}

View File

@@ -0,0 +1,3 @@
query listBlobs($workspaceId: String!) {
listBlobs(workspaceId: $workspaceId)
}

View File

@@ -0,0 +1,3 @@
mutation setBlob($workspaceId: String!, $blob: Upload!) {
setBlob(workspaceId: $workspaceId, blob: $blob)
}

View File

@@ -0,0 +1,8 @@
mutation changeEmail($id: String!, $newEmail: String!) {
changeEmail(id: $id, email: $newEmail) {
id
name
avatarUrl
email
}
}

View File

@@ -0,0 +1,8 @@
mutation changePassword($id: String!, $newPassword: String!) {
changePassword(id: $id, newPassword: $newPassword) {
id
name
avatarUrl
email
}
}

View File

@@ -0,0 +1,5 @@
mutation deleteAccount {
deleteAccount {
success
}
}

View File

@@ -0,0 +1,3 @@
mutation deleteWorkspace($id: String!) {
deleteWorkspace(id: $id)
}

View File

@@ -0,0 +1,10 @@
query getCurrentUser {
currentUser {
id
name
email
emailVerified
avatarUrl
createdAt
}
}

View File

@@ -0,0 +1,14 @@
query getInviteInfo($inviteId: String!) {
getInviteInfo(inviteId: $inviteId) {
workspace {
id
name
avatar
}
user {
id
name
avatarUrl
}
}
}

View File

@@ -0,0 +1,3 @@
query getIsOwner($workspaceId: String!) {
isOwner(workspaceId: $workspaceId)
}

View File

@@ -0,0 +1,14 @@
query getMembersByWorkspaceId($workspaceId: String!) {
workspace(id: $workspaceId) {
members {
id
name
email
avatarUrl
permission
inviteId
accepted
emailVerified
}
}
}

View File

@@ -0,0 +1,5 @@
query getPublicWorkspace($id: String!) {
publicWorkspace(id: $id) {
id
}
}

View File

@@ -0,0 +1,9 @@
query getUser($email: String!) {
user(email: $email) {
id
name
avatarUrl
email
hasPassword
}
}

View File

@@ -0,0 +1,5 @@
query getWorkspacePublicById($id: String!) {
workspace(id: $id) {
public
}
}

View File

@@ -0,0 +1,5 @@
query getWorkspaceSharedPages($workspaceId: String!) {
workspace(id: $workspaceId) {
sharedPages
}
}

View File

@@ -0,0 +1,5 @@
query getWorkspace($id: String!) {
workspace(id: $id) {
id
}
}

View File

@@ -0,0 +1,5 @@
query getWorkspaces {
workspaces {
id
}
}

View File

@@ -7,6 +7,71 @@ export interface GraphQLQuery {
containsFile?: boolean;
}
export const deleteBlobMutation = {
id: 'deleteBlobMutation' as const,
operationName: 'deleteBlob',
definitionName: 'deleteBlob',
containsFile: false,
query: `
mutation deleteBlob($workspaceId: String!, $hash: String!) {
deleteBlob(workspaceId: $workspaceId, hash: $hash)
}`,
};
export const listBlobsQuery = {
id: 'listBlobsQuery' as const,
operationName: 'listBlobs',
definitionName: 'listBlobs',
containsFile: false,
query: `
query listBlobs($workspaceId: String!) {
listBlobs(workspaceId: $workspaceId)
}`,
};
export const setBlobMutation = {
id: 'setBlobMutation' as const,
operationName: 'setBlob',
definitionName: 'setBlob',
containsFile: true,
query: `
mutation setBlob($workspaceId: String!, $blob: Upload!) {
setBlob(workspaceId: $workspaceId, blob: $blob)
}`,
};
export const changeEmailMutation = {
id: 'changeEmailMutation' as const,
operationName: 'changeEmail',
definitionName: 'changeEmail',
containsFile: false,
query: `
mutation changeEmail($id: String!, $newEmail: String!) {
changeEmail(id: $id, email: $newEmail) {
id
name
avatarUrl
email
}
}`,
};
export const changePasswordMutation = {
id: 'changePasswordMutation' as const,
operationName: 'changePassword',
definitionName: 'changePassword',
containsFile: false,
query: `
mutation changePassword($id: String!, $newPassword: String!) {
changePassword(id: $id, newPassword: $newPassword) {
id
name
avatarUrl
email
}
}`,
};
export const createWorkspaceMutation = {
id: 'createWorkspaceMutation' as const,
operationName: 'createWorkspace',
@@ -22,6 +87,327 @@ mutation createWorkspace($init: Upload!) {
}`,
};
export const deleteAccountMutation = {
id: 'deleteAccountMutation' as const,
operationName: 'deleteAccount',
definitionName: 'deleteAccount',
containsFile: false,
query: `
mutation deleteAccount {
deleteAccount {
success
}
}`,
};
export const deleteWorkspaceMutation = {
id: 'deleteWorkspaceMutation' as const,
operationName: 'deleteWorkspace',
definitionName: 'deleteWorkspace',
containsFile: false,
query: `
mutation deleteWorkspace($id: String!) {
deleteWorkspace(id: $id)
}`,
};
export const getCurrentUserQuery = {
id: 'getCurrentUserQuery' as const,
operationName: 'getCurrentUser',
definitionName: 'currentUser',
containsFile: false,
query: `
query getCurrentUser {
currentUser {
id
name
email
emailVerified
avatarUrl
createdAt
}
}`,
};
export const getInviteInfoQuery = {
id: 'getInviteInfoQuery' as const,
operationName: 'getInviteInfo',
definitionName: 'getInviteInfo',
containsFile: false,
query: `
query getInviteInfo($inviteId: String!) {
getInviteInfo(inviteId: $inviteId) {
workspace {
id
name
avatar
}
user {
id
name
avatarUrl
}
}
}`,
};
export const getIsOwnerQuery = {
id: 'getIsOwnerQuery' as const,
operationName: 'getIsOwner',
definitionName: 'isOwner',
containsFile: false,
query: `
query getIsOwner($workspaceId: String!) {
isOwner(workspaceId: $workspaceId)
}`,
};
export const getMembersByWorkspaceIdQuery = {
id: 'getMembersByWorkspaceIdQuery' as const,
operationName: 'getMembersByWorkspaceId',
definitionName: 'workspace',
containsFile: false,
query: `
query getMembersByWorkspaceId($workspaceId: String!) {
workspace(id: $workspaceId) {
members {
id
name
email
avatarUrl
permission
inviteId
accepted
emailVerified
}
}
}`,
};
export const getPublicWorkspaceQuery = {
id: 'getPublicWorkspaceQuery' as const,
operationName: 'getPublicWorkspace',
definitionName: 'publicWorkspace',
containsFile: false,
query: `
query getPublicWorkspace($id: String!) {
publicWorkspace(id: $id) {
id
}
}`,
};
export const getUserQuery = {
id: 'getUserQuery' as const,
operationName: 'getUser',
definitionName: 'user',
containsFile: false,
query: `
query getUser($email: String!) {
user(email: $email) {
id
name
avatarUrl
email
hasPassword
}
}`,
};
export const getWorkspacePublicByIdQuery = {
id: 'getWorkspacePublicByIdQuery' as const,
operationName: 'getWorkspacePublicById',
definitionName: 'workspace',
containsFile: false,
query: `
query getWorkspacePublicById($id: String!) {
workspace(id: $id) {
public
}
}`,
};
export const getWorkspaceSharedPagesQuery = {
id: 'getWorkspaceSharedPagesQuery' as const,
operationName: 'getWorkspaceSharedPages',
definitionName: 'workspace',
containsFile: false,
query: `
query getWorkspaceSharedPages($workspaceId: String!) {
workspace(id: $workspaceId) {
sharedPages
}
}`,
};
export const getWorkspaceQuery = {
id: 'getWorkspaceQuery' as const,
operationName: 'getWorkspace',
definitionName: 'workspace',
containsFile: false,
query: `
query getWorkspace($id: String!) {
workspace(id: $id) {
id
}
}`,
};
export const getWorkspacesQuery = {
id: 'getWorkspacesQuery' as const,
operationName: 'getWorkspaces',
definitionName: 'workspaces',
containsFile: false,
query: `
query getWorkspaces {
workspaces {
id
}
}`,
};
export const leaveWorkspaceMutation = {
id: 'leaveWorkspaceMutation' as const,
operationName: 'leaveWorkspace',
definitionName: 'leaveWorkspace',
containsFile: false,
query: `
mutation leaveWorkspace($workspaceId: String!) {
leaveWorkspace(workspaceId: $workspaceId)
}`,
};
export const revokeMemberPermissionMutation = {
id: 'revokeMemberPermissionMutation' as const,
operationName: 'revokeMemberPermission',
definitionName: 'revoke',
containsFile: false,
query: `
mutation revokeMemberPermission($workspaceId: String!, $userId: String!) {
revoke(workspaceId: $workspaceId, userId: $userId)
}`,
};
export const revokePageMutation = {
id: 'revokePageMutation' as const,
operationName: 'revokePage',
definitionName: 'revokePage',
containsFile: false,
query: `
mutation revokePage($workspaceId: String!, $pageId: String!) {
revokePage(workspaceId: $workspaceId, pageId: $pageId)
}`,
};
export const sendChangeEmailMutation = {
id: 'sendChangeEmailMutation' as const,
operationName: 'sendChangeEmail',
definitionName: 'sendChangeEmail',
containsFile: false,
query: `
mutation sendChangeEmail($email: String!, $callbackUrl: String!) {
sendChangeEmail(email: $email, callbackUrl: $callbackUrl)
}`,
};
export const sendChangePasswordEmailMutation = {
id: 'sendChangePasswordEmailMutation' as const,
operationName: 'sendChangePasswordEmail',
definitionName: 'sendChangePasswordEmail',
containsFile: false,
query: `
mutation sendChangePasswordEmail($email: String!, $callbackUrl: String!) {
sendChangePasswordEmail(email: $email, callbackUrl: $callbackUrl)
}`,
};
export const sendSetPasswordEmailMutation = {
id: 'sendSetPasswordEmailMutation' as const,
operationName: 'sendSetPasswordEmail',
definitionName: 'sendSetPasswordEmail',
containsFile: false,
query: `
mutation sendSetPasswordEmail($email: String!, $callbackUrl: String!) {
sendSetPasswordEmail(email: $email, callbackUrl: $callbackUrl)
}`,
};
export const setRevokePageMutation = {
id: 'setRevokePageMutation' as const,
operationName: 'setRevokePage',
definitionName: 'revokePage',
containsFile: false,
query: `
mutation setRevokePage($workspaceId: String!, $pageId: String!) {
revokePage(workspaceId: $workspaceId, pageId: $pageId)
}`,
};
export const setSharePageMutation = {
id: 'setSharePageMutation' as const,
operationName: 'setSharePage',
definitionName: 'sharePage',
containsFile: false,
query: `
mutation setSharePage($workspaceId: String!, $pageId: String!) {
sharePage(workspaceId: $workspaceId, pageId: $pageId)
}`,
};
export const setWorkspacePublicByIdMutation = {
id: 'setWorkspacePublicByIdMutation' as const,
operationName: 'setWorkspacePublicById',
definitionName: 'updateWorkspace',
containsFile: false,
query: `
mutation setWorkspacePublicById($id: ID!, $public: Boolean!) {
updateWorkspace(input: {id: $id, public: $public}) {
id
}
}`,
};
export const sharePageMutation = {
id: 'sharePageMutation' as const,
operationName: 'sharePage',
definitionName: 'sharePage',
containsFile: false,
query: `
mutation sharePage($workspaceId: String!, $pageId: String!) {
sharePage(workspaceId: $workspaceId, pageId: $pageId)
}`,
};
export const signInMutation = {
id: 'signInMutation' as const,
operationName: 'signIn',
definitionName: 'signIn',
containsFile: false,
query: `
mutation signIn($email: String!, $password: String!) {
signIn(email: $email, password: $password) {
token {
token
}
}
}`,
};
export const signUpMutation = {
id: 'signUpMutation' as const,
operationName: 'signUp',
definitionName: 'signUp',
containsFile: false,
query: `
mutation signUp($name: String!, $email: String!, $password: String!) {
signUp(name: $name, email: $email, password: $password) {
token {
token
}
}
}`,
};
export const uploadAvatarMutation = {
id: 'uploadAvatarMutation' as const,
operationName: 'uploadAvatar',
@@ -38,17 +424,40 @@ mutation uploadAvatar($id: String!, $avatar: Upload!) {
}`,
};
export const workspaceByIdQuery = {
id: 'workspaceByIdQuery' as const,
operationName: 'workspaceById',
definitionName: 'workspace',
export const inviteByEmailMutation = {
id: 'inviteByEmailMutation' as const,
operationName: 'inviteByEmail',
definitionName: 'invite',
containsFile: false,
query: `
query workspaceById($id: String!) {
workspace(id: $id) {
id
public
createdAt
}
mutation inviteByEmail($workspaceId: String!, $email: String!, $permission: Permission!, $sendInviteMail: Boolean) {
invite(
workspaceId: $workspaceId
email: $email
permission: $permission
sendInviteMail: $sendInviteMail
)
}`,
};
export const acceptInviteByInviteIdMutation = {
id: 'acceptInviteByInviteIdMutation' as const,
operationName: 'acceptInviteByInviteId',
definitionName: 'acceptInviteById',
containsFile: false,
query: `
mutation acceptInviteByInviteId($workspaceId: String!, $inviteId: String!) {
acceptInviteById(workspaceId: $workspaceId, inviteId: $inviteId)
}`,
};
export const acceptInviteByWorkspaceIdMutation = {
id: 'acceptInviteByWorkspaceIdMutation' as const,
operationName: 'acceptInviteByWorkspaceId',
definitionName: 'acceptInvite',
containsFile: false,
query: `
mutation acceptInviteByWorkspaceId($workspaceId: String!) {
acceptInvite(workspaceId: $workspaceId)
}`,
};

View File

@@ -0,0 +1,3 @@
mutation leaveWorkspace($workspaceId: String!) {
leaveWorkspace(workspaceId: $workspaceId)
}

View File

@@ -0,0 +1,3 @@
mutation revokeMemberPermission($workspaceId: String!, $userId: String!) {
revoke(workspaceId: $workspaceId, userId: $userId)
}

View File

@@ -0,0 +1,3 @@
mutation revokePage($workspaceId: String!, $pageId: String!) {
revokePage(workspaceId: $workspaceId, pageId: $pageId)
}

View File

@@ -0,0 +1,3 @@
mutation sendChangeEmail($email: String!, $callbackUrl: String!) {
sendChangeEmail(email: $email, callbackUrl: $callbackUrl)
}

View File

@@ -0,0 +1,3 @@
mutation sendChangePasswordEmail($email: String!, $callbackUrl: String!) {
sendChangePasswordEmail(email: $email, callbackUrl: $callbackUrl)
}

View File

@@ -0,0 +1,3 @@
mutation sendSetPasswordEmail($email: String!, $callbackUrl: String!) {
sendSetPasswordEmail(email: $email, callbackUrl: $callbackUrl)
}

View File

@@ -0,0 +1,3 @@
mutation setRevokePage($workspaceId: String!, $pageId: String!) {
revokePage(workspaceId: $workspaceId, pageId: $pageId)
}

View File

@@ -0,0 +1,3 @@
mutation setSharePage($workspaceId: String!, $pageId: String!) {
sharePage(workspaceId: $workspaceId, pageId: $pageId)
}

View File

@@ -0,0 +1,5 @@
mutation setWorkspacePublicById($id: ID!, $public: Boolean!) {
updateWorkspace(input: { id: $id, public: $public }) {
id
}
}

View File

@@ -0,0 +1,3 @@
mutation sharePage($workspaceId: String!, $pageId: String!) {
sharePage(workspaceId: $workspaceId, pageId: $pageId)
}

View File

@@ -0,0 +1,7 @@
mutation signIn($email: String!, $password: String!) {
signIn(email: $email, password: $password) {
token {
token
}
}
}

View File

@@ -0,0 +1,7 @@
mutation signUp($name: String!, $email: String!, $password: String!) {
signUp(name: $name, email: $email, password: $password) {
token {
token
}
}
}

View File

@@ -0,0 +1,13 @@
mutation inviteByEmail(
$workspaceId: String!
$email: String!
$permission: Permission!
$sendInviteMail: Boolean
) {
invite(
workspaceId: $workspaceId
email: $email
permission: $permission
sendInviteMail: $sendInviteMail
)
}

View File

@@ -0,0 +1,3 @@
mutation acceptInviteByInviteId($workspaceId: String!, $inviteId: String!) {
acceptInviteById(workspaceId: $workspaceId, inviteId: $inviteId)
}

View File

@@ -0,0 +1,3 @@
mutation acceptInviteByWorkspaceId($workspaceId: String!) {
acceptInvite(workspaceId: $workspaceId)
}

View File

@@ -1,7 +0,0 @@
query workspaceById($id: String!) {
workspace(id: $id) {
id
public
createdAt
}
}

View File

@@ -1,3 +1,4 @@
export * from './fetcher';
export * from './graphql';
export * from './schema';
import '@affine/env/global';

View File

@@ -32,6 +32,10 @@ export interface Scalars {
Upload: { input: File; output: File };
}
export enum NewFeaturesKind {
EarlyAccess = 'EarlyAccess',
}
/** User permission in workspace */
export enum Permission {
Admin = 'Admin',
@@ -46,6 +50,61 @@ export interface UpdateWorkspaceInput {
public: InputMaybe<Scalars['Boolean']['input']>;
}
export type DeleteBlobMutationVariables = Exact<{
workspaceId: Scalars['String']['input'];
hash: Scalars['String']['input'];
}>;
export type DeleteBlobMutation = {
__typename?: 'Mutation';
deleteBlob: boolean;
};
export type ListBlobsQueryVariables = Exact<{
workspaceId: Scalars['String']['input'];
}>;
export type ListBlobsQuery = { __typename?: 'Query'; listBlobs: Array<string> };
export type SetBlobMutationVariables = Exact<{
workspaceId: Scalars['String']['input'];
blob: Scalars['Upload']['input'];
}>;
export type SetBlobMutation = { __typename?: 'Mutation'; setBlob: string };
export type ChangeEmailMutationVariables = Exact<{
id: Scalars['String']['input'];
newEmail: Scalars['String']['input'];
}>;
export type ChangeEmailMutation = {
__typename?: 'Mutation';
changeEmail: {
__typename?: 'UserType';
id: string;
name: string;
avatarUrl: string | null;
email: string;
};
};
export type ChangePasswordMutationVariables = Exact<{
id: Scalars['String']['input'];
newPassword: Scalars['String']['input'];
}>;
export type ChangePasswordMutation = {
__typename?: 'Mutation';
changePassword: {
__typename?: 'UserType';
id: string;
name: string;
avatarUrl: string | null;
email: string;
};
};
export type CreateWorkspaceMutationVariables = Exact<{
init: Scalars['Upload']['input'];
}>;
@@ -60,6 +119,270 @@ export type CreateWorkspaceMutation = {
};
};
export type DeleteAccountMutationVariables = Exact<{ [key: string]: never }>;
export type DeleteAccountMutation = {
__typename?: 'Mutation';
deleteAccount: { __typename?: 'DeleteAccount'; success: boolean };
};
export type DeleteWorkspaceMutationVariables = Exact<{
id: Scalars['String']['input'];
}>;
export type DeleteWorkspaceMutation = {
__typename?: 'Mutation';
deleteWorkspace: boolean;
};
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never }>;
export type GetCurrentUserQuery = {
__typename?: 'Query';
currentUser: {
__typename?: 'UserType';
id: string;
name: string;
email: string;
emailVerified: string | null;
avatarUrl: string | null;
createdAt: string | null;
};
};
export type GetInviteInfoQueryVariables = Exact<{
inviteId: Scalars['String']['input'];
}>;
export type GetInviteInfoQuery = {
__typename?: 'Query';
getInviteInfo: {
__typename?: 'InvitationType';
workspace: {
__typename?: 'InvitationWorkspaceType';
id: string;
name: string;
avatar: string;
};
user: {
__typename?: 'UserType';
id: string;
name: string;
avatarUrl: string | null;
};
};
};
export type GetIsOwnerQueryVariables = Exact<{
workspaceId: Scalars['String']['input'];
}>;
export type GetIsOwnerQuery = { __typename?: 'Query'; isOwner: boolean };
export type GetMembersByWorkspaceIdQueryVariables = Exact<{
workspaceId: Scalars['String']['input'];
}>;
export type GetMembersByWorkspaceIdQuery = {
__typename?: 'Query';
workspace: {
__typename?: 'WorkspaceType';
members: Array<{
__typename?: 'InviteUserType';
id: string;
name: string | null;
email: string | null;
avatarUrl: string | null;
permission: Permission;
inviteId: string;
accepted: boolean;
emailVerified: string | null;
}>;
};
};
export type GetPublicWorkspaceQueryVariables = Exact<{
id: Scalars['String']['input'];
}>;
export type GetPublicWorkspaceQuery = {
__typename?: 'Query';
publicWorkspace: { __typename?: 'WorkspaceType'; id: string };
};
export type GetUserQueryVariables = Exact<{
email: Scalars['String']['input'];
}>;
export type GetUserQuery = {
__typename?: 'Query';
user: {
__typename?: 'UserType';
id: string;
name: string;
avatarUrl: string | null;
email: string;
hasPassword: boolean | null;
} | null;
};
export type GetWorkspacePublicByIdQueryVariables = Exact<{
id: Scalars['String']['input'];
}>;
export type GetWorkspacePublicByIdQuery = {
__typename?: 'Query';
workspace: { __typename?: 'WorkspaceType'; public: boolean };
};
export type GetWorkspaceSharedPagesQueryVariables = Exact<{
workspaceId: Scalars['String']['input'];
}>;
export type GetWorkspaceSharedPagesQuery = {
__typename?: 'Query';
workspace: { __typename?: 'WorkspaceType'; sharedPages: Array<string> };
};
export type GetWorkspaceQueryVariables = Exact<{
id: Scalars['String']['input'];
}>;
export type GetWorkspaceQuery = {
__typename?: 'Query';
workspace: { __typename?: 'WorkspaceType'; id: string };
};
export type GetWorkspacesQueryVariables = Exact<{ [key: string]: never }>;
export type GetWorkspacesQuery = {
__typename?: 'Query';
workspaces: Array<{ __typename?: 'WorkspaceType'; id: string }>;
};
export type LeaveWorkspaceMutationVariables = Exact<{
workspaceId: Scalars['String']['input'];
}>;
export type LeaveWorkspaceMutation = {
__typename?: 'Mutation';
leaveWorkspace: boolean;
};
export type RevokeMemberPermissionMutationVariables = Exact<{
workspaceId: Scalars['String']['input'];
userId: Scalars['String']['input'];
}>;
export type RevokeMemberPermissionMutation = {
__typename?: 'Mutation';
revoke: boolean;
};
export type RevokePageMutationVariables = Exact<{
workspaceId: Scalars['String']['input'];
pageId: Scalars['String']['input'];
}>;
export type RevokePageMutation = {
__typename?: 'Mutation';
revokePage: boolean;
};
export type SendChangeEmailMutationVariables = Exact<{
email: Scalars['String']['input'];
callbackUrl: Scalars['String']['input'];
}>;
export type SendChangeEmailMutation = {
__typename?: 'Mutation';
sendChangeEmail: boolean;
};
export type SendChangePasswordEmailMutationVariables = Exact<{
email: Scalars['String']['input'];
callbackUrl: Scalars['String']['input'];
}>;
export type SendChangePasswordEmailMutation = {
__typename?: 'Mutation';
sendChangePasswordEmail: boolean;
};
export type SendSetPasswordEmailMutationVariables = Exact<{
email: Scalars['String']['input'];
callbackUrl: Scalars['String']['input'];
}>;
export type SendSetPasswordEmailMutation = {
__typename?: 'Mutation';
sendSetPasswordEmail: boolean;
};
export type SetRevokePageMutationVariables = Exact<{
workspaceId: Scalars['String']['input'];
pageId: Scalars['String']['input'];
}>;
export type SetRevokePageMutation = {
__typename?: 'Mutation';
revokePage: boolean;
};
export type SetSharePageMutationVariables = Exact<{
workspaceId: Scalars['String']['input'];
pageId: Scalars['String']['input'];
}>;
export type SetSharePageMutation = {
__typename?: 'Mutation';
sharePage: boolean;
};
export type SetWorkspacePublicByIdMutationVariables = Exact<{
id: Scalars['ID']['input'];
public: Scalars['Boolean']['input'];
}>;
export type SetWorkspacePublicByIdMutation = {
__typename?: 'Mutation';
updateWorkspace: { __typename?: 'WorkspaceType'; id: string };
};
export type SharePageMutationVariables = Exact<{
workspaceId: Scalars['String']['input'];
pageId: Scalars['String']['input'];
}>;
export type SharePageMutation = { __typename?: 'Mutation'; sharePage: boolean };
export type SignInMutationVariables = Exact<{
email: Scalars['String']['input'];
password: Scalars['String']['input'];
}>;
export type SignInMutation = {
__typename?: 'Mutation';
signIn: {
__typename?: 'UserType';
token: { __typename?: 'TokenType'; token: string };
};
};
export type SignUpMutationVariables = Exact<{
name: Scalars['String']['input'];
email: Scalars['String']['input'];
password: Scalars['String']['input'];
}>;
export type SignUpMutation = {
__typename?: 'Mutation';
signUp: {
__typename?: 'UserType';
token: { __typename?: 'TokenType'; token: string };
};
};
export type UploadAvatarMutationVariables = Exact<{
id: Scalars['String']['input'];
avatar: Scalars['Upload']['input'];
@@ -76,34 +399,204 @@ export type UploadAvatarMutation = {
};
};
export type WorkspaceByIdQueryVariables = Exact<{
id: Scalars['String']['input'];
export type InviteByEmailMutationVariables = Exact<{
workspaceId: Scalars['String']['input'];
email: Scalars['String']['input'];
permission: Permission;
sendInviteMail: InputMaybe<Scalars['Boolean']['input']>;
}>;
export type WorkspaceByIdQuery = {
__typename?: 'Query';
workspace: {
__typename?: 'WorkspaceType';
id: string;
public: boolean;
createdAt: string;
};
export type InviteByEmailMutation = { __typename?: 'Mutation'; invite: string };
export type AcceptInviteByInviteIdMutationVariables = Exact<{
workspaceId: Scalars['String']['input'];
inviteId: Scalars['String']['input'];
}>;
export type AcceptInviteByInviteIdMutation = {
__typename?: 'Mutation';
acceptInviteById: boolean;
};
export type Queries = {
name: 'workspaceByIdQuery';
variables: WorkspaceByIdQueryVariables;
response: WorkspaceByIdQuery;
export type AcceptInviteByWorkspaceIdMutationVariables = Exact<{
workspaceId: Scalars['String']['input'];
}>;
export type AcceptInviteByWorkspaceIdMutation = {
__typename?: 'Mutation';
acceptInvite: boolean;
};
export type Queries =
| {
name: 'listBlobsQuery';
variables: ListBlobsQueryVariables;
response: ListBlobsQuery;
}
| {
name: 'getCurrentUserQuery';
variables: GetCurrentUserQueryVariables;
response: GetCurrentUserQuery;
}
| {
name: 'getInviteInfoQuery';
variables: GetInviteInfoQueryVariables;
response: GetInviteInfoQuery;
}
| {
name: 'getIsOwnerQuery';
variables: GetIsOwnerQueryVariables;
response: GetIsOwnerQuery;
}
| {
name: 'getMembersByWorkspaceIdQuery';
variables: GetMembersByWorkspaceIdQueryVariables;
response: GetMembersByWorkspaceIdQuery;
}
| {
name: 'getPublicWorkspaceQuery';
variables: GetPublicWorkspaceQueryVariables;
response: GetPublicWorkspaceQuery;
}
| {
name: 'getUserQuery';
variables: GetUserQueryVariables;
response: GetUserQuery;
}
| {
name: 'getWorkspacePublicByIdQuery';
variables: GetWorkspacePublicByIdQueryVariables;
response: GetWorkspacePublicByIdQuery;
}
| {
name: 'getWorkspaceSharedPagesQuery';
variables: GetWorkspaceSharedPagesQueryVariables;
response: GetWorkspaceSharedPagesQuery;
}
| {
name: 'getWorkspaceQuery';
variables: GetWorkspaceQueryVariables;
response: GetWorkspaceQuery;
}
| {
name: 'getWorkspacesQuery';
variables: GetWorkspacesQueryVariables;
response: GetWorkspacesQuery;
};
export type Mutations =
| {
name: 'deleteBlobMutation';
variables: DeleteBlobMutationVariables;
response: DeleteBlobMutation;
}
| {
name: 'setBlobMutation';
variables: SetBlobMutationVariables;
response: SetBlobMutation;
}
| {
name: 'changeEmailMutation';
variables: ChangeEmailMutationVariables;
response: ChangeEmailMutation;
}
| {
name: 'changePasswordMutation';
variables: ChangePasswordMutationVariables;
response: ChangePasswordMutation;
}
| {
name: 'createWorkspaceMutation';
variables: CreateWorkspaceMutationVariables;
response: CreateWorkspaceMutation;
}
| {
name: 'deleteAccountMutation';
variables: DeleteAccountMutationVariables;
response: DeleteAccountMutation;
}
| {
name: 'deleteWorkspaceMutation';
variables: DeleteWorkspaceMutationVariables;
response: DeleteWorkspaceMutation;
}
| {
name: 'leaveWorkspaceMutation';
variables: LeaveWorkspaceMutationVariables;
response: LeaveWorkspaceMutation;
}
| {
name: 'revokeMemberPermissionMutation';
variables: RevokeMemberPermissionMutationVariables;
response: RevokeMemberPermissionMutation;
}
| {
name: 'revokePageMutation';
variables: RevokePageMutationVariables;
response: RevokePageMutation;
}
| {
name: 'sendChangeEmailMutation';
variables: SendChangeEmailMutationVariables;
response: SendChangeEmailMutation;
}
| {
name: 'sendChangePasswordEmailMutation';
variables: SendChangePasswordEmailMutationVariables;
response: SendChangePasswordEmailMutation;
}
| {
name: 'sendSetPasswordEmailMutation';
variables: SendSetPasswordEmailMutationVariables;
response: SendSetPasswordEmailMutation;
}
| {
name: 'setRevokePageMutation';
variables: SetRevokePageMutationVariables;
response: SetRevokePageMutation;
}
| {
name: 'setSharePageMutation';
variables: SetSharePageMutationVariables;
response: SetSharePageMutation;
}
| {
name: 'setWorkspacePublicByIdMutation';
variables: SetWorkspacePublicByIdMutationVariables;
response: SetWorkspacePublicByIdMutation;
}
| {
name: 'sharePageMutation';
variables: SharePageMutationVariables;
response: SharePageMutation;
}
| {
name: 'signInMutation';
variables: SignInMutationVariables;
response: SignInMutation;
}
| {
name: 'signUpMutation';
variables: SignUpMutationVariables;
response: SignUpMutation;
}
| {
name: 'uploadAvatarMutation';
variables: UploadAvatarMutationVariables;
response: UploadAvatarMutation;
}
| {
name: 'inviteByEmailMutation';
variables: InviteByEmailMutationVariables;
response: InviteByEmailMutation;
}
| {
name: 'acceptInviteByInviteIdMutation';
variables: AcceptInviteByInviteIdMutationVariables;
response: AcceptInviteByInviteIdMutation;
}
| {
name: 'acceptInviteByWorkspaceIdMutation';
variables: AcceptInviteByWorkspaceIdMutationVariables;
response: AcceptInviteByWorkspaceIdMutation;
};

View File

@@ -0,0 +1,174 @@
export const SPAN_ID_BYTES = 8;
export const TRACE_ID_BYTES = 16;
export const TRACE_VERSION = '00';
export const TRACE_FLAG = '01';
const BytesBuffer = Array(32);
type TraceSpan = {
name: string;
spanId: string;
displayName: {
value: string;
truncatedByteCount: number;
};
startTime: string;
endTime: string;
attributes: {
attributeMap: {
requestId: {
stringValue: {
value: string;
truncatedByteCount: number;
};
};
};
droppedAttributesCount: number;
};
};
/**
* inspired by open-telemetry/opentelemetry-js
*/
export function generateRandUTF16Chars(bytes: number) {
for (let i = 0; i < bytes * 2; i++) {
BytesBuffer[i] = Math.floor(Math.random() * 16) + 48;
// valid hex characters in the range 48-57 and 97-102
if (BytesBuffer[i] >= 58) {
BytesBuffer[i] += 39;
}
}
return String.fromCharCode(...BytesBuffer.slice(0, bytes * 2));
}
export class TraceReporter {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
static traceReportEndpoint = process.env.TRACE_REPORT_ENDPOINT!;
static shouldReportTrace = process.env.SHOULD_REPORT_TRACE;
private spansCache = new Array<TraceSpan>();
private reportIntervalId: number | undefined | NodeJS.Timeout;
private reportInterval = 60_000;
private static instance: TraceReporter;
public static getInstance(): TraceReporter {
if (!TraceReporter.instance) {
const instance = (TraceReporter.instance = new TraceReporter());
instance.initTraceReport();
}
return TraceReporter.instance;
}
public cacheTrace(
traceId: string,
spanId: string,
requestId: string,
startTime: string
) {
const span = TraceReporter.createTraceSpan(
traceId,
spanId,
requestId,
startTime
);
this.spansCache.push(span);
if (this.spansCache.length <= 1) {
this.initTraceReport();
}
}
public uploadTrace(
traceId: string,
spanId: string,
requestId: string,
startTime: string
) {
const span = TraceReporter.createTraceSpan(
traceId,
spanId,
requestId,
startTime
);
TraceReporter.reportToTraceEndpoint(JSON.stringify({ spans: [span] }));
}
public static reportToTraceEndpoint(payload: string): void {
if (typeof navigator !== 'undefined') {
navigator.sendBeacon(TraceReporter.traceReportEndpoint, payload);
} else {
fetch(TraceReporter.traceReportEndpoint, {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json',
},
body: payload,
}).catch(console.warn);
}
}
public static createTraceSpan(
traceId: string,
spanId: string,
requestId: string,
startTime: string
): TraceSpan {
return {
name: `projects/{GCP_PROJECT_ID}/traces/${traceId}/spans/${spanId}`,
spanId,
displayName: {
value: 'fetch',
truncatedByteCount: 0,
},
startTime,
endTime: new Date().toISOString(),
attributes: {
attributeMap: {
requestId: {
stringValue: {
value: requestId,
truncatedByteCount: 0,
},
},
},
droppedAttributesCount: 0,
},
};
}
private initTraceReport = () => {
if (!this.reportIntervalId && TraceReporter.shouldReportTrace) {
if (typeof window !== 'undefined') {
this.reportIntervalId = window.setInterval(
this.reportHandler,
this.reportInterval
);
} else {
this.reportIntervalId = setInterval(
this.reportHandler,
this.reportInterval
);
}
}
};
private reportHandler = () => {
if (this.spansCache.length <= 0) {
clearInterval(this.reportIntervalId);
this.reportIntervalId = undefined;
return;
}
TraceReporter.reportToTraceEndpoint(
JSON.stringify({ spans: [...this.spansCache] })
);
this.spansCache = [];
};
}
export const traceReporter = !process.env.SHOULD_REPORT_TRACE
? null
: TraceReporter.getInstance();

View File

@@ -5,5 +5,10 @@
"composite": true,
"noEmit": false,
"outDir": "lib"
}
},
"references": [
{
"path": "../env"
}
]
}