twenty-client-sdk 0.8.0-canary.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,584 @@
1
+ import { mkdir as f, readdir as q, rm as l, rename as A, cp as R, appendFile as m, copyFile as c, writeFile as I } from "node:fs/promises";
2
+ import { join as r } from "node:path";
3
+ import { generate as y } from "@genql/cli";
4
+ import { build as u } from "esbuild";
5
+ const _ = "TWENTY_API_URL", P = "// __STRIPPED_DURING_INJECTION_START__", k = "// __STRIPPED_DURING_INJECTION_END__", d = "// __UPLOAD_FILE_START__", p = "// __UPLOAD_FILE_END__", o = (n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), T = (n, t) => {
6
+ let e = n;
7
+ return e = e.replace(
8
+ new RegExp(
9
+ `${o(P)}[\\s\\S]*?${o(k)}\\n?`
10
+ ),
11
+ ""
12
+ ), e = e.replace("'__TWENTY_DEFAULT_URL__'", t.defaultUrl), e = e.replace(/TwentyGeneratedClient/g, t.apiClientName), t.includeUploadFile ? (e = e.replace(
13
+ new RegExp(`\\s*${o(d)}\\n`),
14
+ `
15
+ `
16
+ ), e = e.replace(
17
+ new RegExp(`\\s*${o(p)}\\n`),
18
+ `
19
+ `
20
+ )) : e = e.replace(
21
+ new RegExp(
22
+ `\\s*${o(d)}[\\s\\S]*?${o(p)}\\n?`
23
+ ),
24
+ `
25
+ `
26
+ ), `
27
+ // ${t.apiClientName} (auto-injected by twenty-client-sdk)
28
+ ${e}`;
29
+ }, E = (n) => f(n, { recursive: !0 }), g = async (n) => {
30
+ let t;
31
+ try {
32
+ t = await q(n);
33
+ } catch (e) {
34
+ if (e instanceof Error && "code" in e && e.code === "ENOENT") {
35
+ await f(n, { recursive: !0 });
36
+ return;
37
+ }
38
+ throw e;
39
+ }
40
+ await Promise.all(
41
+ t.map(
42
+ (e) => l(r(n, e), { recursive: !0, force: !0 })
43
+ )
44
+ );
45
+ }, C = async (n, t) => {
46
+ try {
47
+ await A(n, t);
48
+ } catch (e) {
49
+ if (e instanceof Error && "code" in e && e.code === "EXDEV")
50
+ await R(n, t, { recursive: !0 }), await l(n, { recursive: !0, force: !0 });
51
+ else
52
+ throw e;
53
+ }
54
+ }, h = (n) => l(n, { recursive: !0, force: !0 }), w = `// Ambient type stubs for the genql-generated code this template gets
55
+ // injected into. They enable full typecheck/lint on this file.
56
+ // __STRIPPED_DURING_INJECTION_START__
57
+ type QueryGenqlSelection = Record<string, unknown>;
58
+ type MutationGenqlSelection = Record<string, unknown>;
59
+ type GraphqlOperation = Record<string, unknown>;
60
+
61
+ type ClientOptions = {
62
+ url?: string;
63
+ headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
64
+ fetcher?: (
65
+ operation: GraphqlOperation | GraphqlOperation[],
66
+ ) => Promise<unknown>;
67
+ fetch?: typeof globalThis.fetch;
68
+ batch?: unknown;
69
+ };
70
+
71
+ type Client = {
72
+ query: (
73
+ request: QueryGenqlSelection & { __name?: string },
74
+ ) => Promise<unknown>;
75
+ mutation: (
76
+ request: MutationGenqlSelection & { __name?: string },
77
+ ) => Promise<unknown>;
78
+ };
79
+
80
+ declare function createClient(options: ClientOptions): Client;
81
+
82
+ declare class GenqlError extends Error {
83
+ constructor(errors: unknown, data: unknown);
84
+ }
85
+ // __STRIPPED_DURING_INJECTION_END__
86
+
87
+ const APP_ACCESS_TOKEN_ENV_KEY = 'TWENTY_APP_ACCESS_TOKEN';
88
+ const API_KEY_ENV_KEY = 'TWENTY_API_KEY';
89
+
90
+ type TwentyGeneratedClientOptions = ClientOptions;
91
+
92
+ type ProcessEnvironment = Record<string, string | undefined>;
93
+
94
+ type GraphqlErrorPayloadEntry = {
95
+ message?: string;
96
+ extensions?: { code?: string };
97
+ };
98
+
99
+ type GraphqlResponsePayload = {
100
+ data?: Record<string, unknown>;
101
+ errors?: GraphqlErrorPayloadEntry[];
102
+ };
103
+
104
+ type GraphqlResponse = {
105
+ status: number;
106
+ statusText: string;
107
+ payload: GraphqlResponsePayload | null;
108
+ rawBody: string;
109
+ };
110
+
111
+ const getProcessEnvironment = (): ProcessEnvironment => {
112
+ const processObject = (
113
+ globalThis as { process?: { env?: ProcessEnvironment } }
114
+ ).process;
115
+
116
+ return processObject?.env ?? {};
117
+ };
118
+
119
+ const getTokenFromAuthorizationHeader = (
120
+ authorizationHeader: string | undefined,
121
+ ): string | null => {
122
+ if (typeof authorizationHeader !== 'string') {
123
+ return null;
124
+ }
125
+
126
+ const trimmedAuthorizationHeader = authorizationHeader.trim();
127
+
128
+ if (trimmedAuthorizationHeader.length === 0) {
129
+ return null;
130
+ }
131
+
132
+ if (trimmedAuthorizationHeader === 'Bearer') {
133
+ return null;
134
+ }
135
+
136
+ if (trimmedAuthorizationHeader.startsWith('Bearer ')) {
137
+ return trimmedAuthorizationHeader.slice('Bearer '.length).trim();
138
+ }
139
+
140
+ return trimmedAuthorizationHeader;
141
+ };
142
+
143
+ const getTokenFromHeaders = (
144
+ headers: HeadersInit | undefined,
145
+ ): string | null => {
146
+ if (!headers) {
147
+ return null;
148
+ }
149
+
150
+ if (headers instanceof Headers) {
151
+ return getTokenFromAuthorizationHeader(
152
+ headers.get('Authorization') ?? undefined,
153
+ );
154
+ }
155
+
156
+ if (Array.isArray(headers)) {
157
+ const matchedAuthorizationHeader = headers.find(
158
+ ([headerName]) => headerName.toLowerCase() === 'authorization',
159
+ );
160
+
161
+ return getTokenFromAuthorizationHeader(matchedAuthorizationHeader?.[1]);
162
+ }
163
+
164
+ const headersRecord = headers as Record<string, string | undefined>;
165
+
166
+ return getTokenFromAuthorizationHeader(
167
+ headersRecord.Authorization ?? headersRecord.authorization,
168
+ );
169
+ };
170
+
171
+ const hasAuthenticationErrorInGraphqlPayload = (
172
+ payload: GraphqlResponsePayload | null,
173
+ ): boolean => {
174
+ if (!payload?.errors) {
175
+ return false;
176
+ }
177
+
178
+ return payload.errors.some((graphqlError) => {
179
+ return (
180
+ graphqlError.extensions?.code === 'UNAUTHENTICATED' ||
181
+ graphqlError.message?.toLowerCase() === 'unauthorized'
182
+ );
183
+ });
184
+ };
185
+
186
+ const defaultOptions: TwentyGeneratedClientOptions = {
187
+ url: '__TWENTY_DEFAULT_URL__',
188
+ headers: {
189
+ 'Content-Type': 'application/json',
190
+ },
191
+ };
192
+
193
+ export class TwentyGeneratedClient {
194
+ private client: Client;
195
+ private url: string;
196
+ private requestOptions: RequestInit;
197
+ private headers: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
198
+ private fetchImplementation: typeof globalThis.fetch | null;
199
+ private authorizationToken: string | null;
200
+ private refreshAccessTokenPromise: Promise<string | null> | null = null;
201
+
202
+ constructor(options?: TwentyGeneratedClientOptions) {
203
+ const merged: TwentyGeneratedClientOptions = {
204
+ ...defaultOptions,
205
+ ...options,
206
+ };
207
+
208
+ const {
209
+ url,
210
+ headers,
211
+ fetch: customFetchImplementation,
212
+ fetcher: _fetcher,
213
+ batch: _batch,
214
+ ...requestOptions
215
+ } = merged;
216
+
217
+ this.url = url ?? '';
218
+ this.requestOptions = requestOptions;
219
+ this.headers = headers ?? {};
220
+ this.fetchImplementation =
221
+ customFetchImplementation ?? globalThis.fetch ?? null;
222
+
223
+ const processEnvironment = getProcessEnvironment();
224
+ const tokenFromHeaders = getTokenFromHeaders(
225
+ typeof headers === 'function' ? undefined : headers,
226
+ );
227
+
228
+ // Priority: explicit header > app access token > api key (legacy).
229
+ this.authorizationToken =
230
+ tokenFromHeaders ??
231
+ processEnvironment[APP_ACCESS_TOKEN_ENV_KEY] ??
232
+ processEnvironment[API_KEY_ENV_KEY] ??
233
+ null;
234
+
235
+ this.client = createClient({
236
+ ...merged,
237
+ headers: undefined,
238
+ fetcher: async (operation) =>
239
+ this.executeGraphqlRequestWithOptionalRefresh({
240
+ operation,
241
+ }),
242
+ });
243
+ }
244
+
245
+ query<R extends QueryGenqlSelection>(request: R & { __name?: string }) {
246
+ return this.client.query(request);
247
+ }
248
+
249
+ mutation<R extends MutationGenqlSelection>(request: R & { __name?: string }) {
250
+ return this.client.mutation(request);
251
+ }
252
+
253
+ // __UPLOAD_FILE_START__
254
+ async uploadFile(
255
+ fileBuffer: Buffer,
256
+ filename: string,
257
+ contentType: string = 'application/octet-stream',
258
+ fieldMetadataUniversalIdentifier: string,
259
+ ): Promise<{
260
+ id: string;
261
+ path: string;
262
+ size: number;
263
+ createdAt: string;
264
+ url: string;
265
+ }> {
266
+ const form = new FormData();
267
+
268
+ form.append(
269
+ 'operations',
270
+ JSON.stringify({
271
+ query: \`mutation UploadFilesFieldFileByUniversalIdentifier($file: Upload!, $fieldMetadataUniversalIdentifier: String!) {
272
+ uploadFilesFieldFileByUniversalIdentifier(file: $file, fieldMetadataUniversalIdentifier: $fieldMetadataUniversalIdentifier) { id path size createdAt url }
273
+ }\`,
274
+ variables: {
275
+ file: null,
276
+ fieldMetadataUniversalIdentifier,
277
+ },
278
+ }),
279
+ );
280
+ form.append('map', JSON.stringify({ '0': ['variables.file'] }));
281
+ form.append(
282
+ '0',
283
+ new Blob([fileBuffer as BlobPart], { type: contentType }),
284
+ filename,
285
+ );
286
+
287
+ const result = await this.executeGraphqlRequestWithOptionalRefresh({
288
+ operation: form,
289
+ headers: {},
290
+ requestInit: {
291
+ method: 'POST',
292
+ },
293
+ });
294
+
295
+ if (result.errors) {
296
+ throw new GenqlError(result.errors, result.data);
297
+ }
298
+
299
+ const data = result.data as Record<string, unknown>;
300
+
301
+ return data.uploadFilesFieldFileByUniversalIdentifier as {
302
+ id: string;
303
+ path: string;
304
+ size: number;
305
+ createdAt: string;
306
+ url: string;
307
+ };
308
+ }
309
+ // __UPLOAD_FILE_END__
310
+
311
+ private async executeGraphqlRequestWithOptionalRefresh({
312
+ operation,
313
+ headers,
314
+ requestInit,
315
+ }: {
316
+ operation: GraphqlOperation | GraphqlOperation[] | FormData;
317
+ headers?: HeadersInit;
318
+ requestInit?: RequestInit;
319
+ }) {
320
+ const firstResponse = await this.executeGraphqlRequest({
321
+ operation,
322
+ headers,
323
+ requestInit,
324
+ token: this.authorizationToken,
325
+ });
326
+
327
+ if (this.shouldRefreshToken(firstResponse)) {
328
+ const refreshedAccessToken = await this.requestRefreshedAccessToken();
329
+
330
+ if (refreshedAccessToken) {
331
+ const retryResponse = await this.executeGraphqlRequest({
332
+ operation,
333
+ headers,
334
+ requestInit,
335
+ token: refreshedAccessToken,
336
+ });
337
+
338
+ return this.assertResponseIsSuccessful(retryResponse);
339
+ }
340
+ }
341
+
342
+ return this.assertResponseIsSuccessful(firstResponse);
343
+ }
344
+
345
+ private async executeGraphqlRequest({
346
+ operation,
347
+ headers,
348
+ requestInit,
349
+ token,
350
+ }: {
351
+ operation: GraphqlOperation | GraphqlOperation[] | FormData;
352
+ headers?: HeadersInit;
353
+ requestInit?: RequestInit;
354
+ token: string | null;
355
+ }): Promise<GraphqlResponse> {
356
+ if (!this.fetchImplementation) {
357
+ throw new Error(
358
+ 'Global \`fetch\` function is not available, ' +
359
+ 'pass a fetch implementation to the Twenty client',
360
+ );
361
+ }
362
+
363
+ const resolvedHeaders = await this.resolveHeaders();
364
+ const requestHeaders = new Headers(resolvedHeaders);
365
+
366
+ if (headers) {
367
+ new Headers(headers).forEach((value, key) =>
368
+ requestHeaders.set(key, value),
369
+ );
370
+ }
371
+
372
+ if (operation instanceof FormData) {
373
+ requestHeaders.delete('Content-Type');
374
+ } else {
375
+ requestHeaders.set('Content-Type', 'application/json');
376
+ }
377
+
378
+ if (token) {
379
+ requestHeaders.set('Authorization', \`Bearer \${token}\`);
380
+ } else {
381
+ requestHeaders.delete('Authorization');
382
+ }
383
+
384
+ const response = await this.fetchImplementation.call(globalThis, this.url, {
385
+ ...this.requestOptions,
386
+ ...requestInit,
387
+ method: requestInit?.method ?? 'POST',
388
+ headers: requestHeaders,
389
+ body:
390
+ operation instanceof FormData ? operation : JSON.stringify(operation),
391
+ });
392
+
393
+ const rawBody = await response.text();
394
+ let payload: GraphqlResponsePayload | null = null;
395
+
396
+ if (rawBody.trim().length > 0) {
397
+ try {
398
+ payload = JSON.parse(rawBody) as GraphqlResponsePayload;
399
+ } catch {
400
+ payload = null;
401
+ }
402
+ }
403
+
404
+ return {
405
+ status: response.status,
406
+ statusText: response.statusText,
407
+ payload,
408
+ rawBody,
409
+ };
410
+ }
411
+
412
+ private async resolveHeaders(): Promise<HeadersInit> {
413
+ if (typeof this.headers === 'function') {
414
+ return (await this.headers()) ?? {};
415
+ }
416
+
417
+ return this.headers ?? {};
418
+ }
419
+
420
+ private shouldRefreshToken(response: GraphqlResponse): boolean {
421
+ if (response.status === 401) {
422
+ return true;
423
+ }
424
+
425
+ return hasAuthenticationErrorInGraphqlPayload(response.payload);
426
+ }
427
+
428
+ private assertResponseIsSuccessful(response: GraphqlResponse) {
429
+ if (response.status < 200 || response.status >= 300) {
430
+ throw new Error(\`\${response.statusText}: \${response.rawBody}\`);
431
+ }
432
+
433
+ if (response.payload === null) {
434
+ throw new Error('Invalid JSON response');
435
+ }
436
+
437
+ return response.payload;
438
+ }
439
+
440
+ private async requestRefreshedAccessToken(): Promise<string | null> {
441
+ const refreshAccessTokenFunction = (
442
+ globalThis as {
443
+ frontComponentHostCommunicationApi?: {
444
+ requestAccessTokenRefresh?: () => Promise<string>;
445
+ };
446
+ }
447
+ ).frontComponentHostCommunicationApi?.requestAccessTokenRefresh;
448
+
449
+ if (typeof refreshAccessTokenFunction !== 'function') {
450
+ return null;
451
+ }
452
+
453
+ if (!this.refreshAccessTokenPromise) {
454
+ this.refreshAccessTokenPromise = refreshAccessTokenFunction()
455
+ .then((refreshedAccessToken) => {
456
+ if (
457
+ typeof refreshedAccessToken !== 'string' ||
458
+ refreshedAccessToken.length === 0
459
+ ) {
460
+ return null;
461
+ }
462
+
463
+ this.setAuthorizationToken(refreshedAccessToken);
464
+
465
+ return refreshedAccessToken;
466
+ })
467
+ .catch((refreshError: unknown) => {
468
+ console.error('Twenty client: token refresh failed', refreshError);
469
+
470
+ return null;
471
+ })
472
+ .finally(() => {
473
+ this.refreshAccessTokenPromise = null;
474
+ });
475
+ }
476
+
477
+ return this.refreshAccessTokenPromise;
478
+ }
479
+
480
+ private setAuthorizationToken(token: string) {
481
+ this.authorizationToken = token;
482
+
483
+ const processEnvironment = getProcessEnvironment();
484
+
485
+ processEnvironment[APP_ACCESS_TOKEN_ENV_KEY] = token;
486
+ }
487
+ }
488
+ `, O = {
489
+ DateTime: "string",
490
+ JSON: "Record<string, unknown>",
491
+ UUID: "string"
492
+ }, v = "core/generated", N = async ({
493
+ schema: n,
494
+ outputPath: t,
495
+ clientWrapperTemplateSource: e
496
+ }) => {
497
+ const a = e ?? w, s = `${t}.tmp`;
498
+ await E(s), await g(s);
499
+ try {
500
+ await y({
501
+ schema: n,
502
+ output: s,
503
+ scalarTypes: O
504
+ });
505
+ const i = T(a, {
506
+ apiClientName: "CoreApiClient",
507
+ defaultUrl: `\`\${process.env.${_}}/graphql\``,
508
+ includeUploadFile: !0
509
+ });
510
+ await m(r(s, "index.ts"), i), await h(t), await C(s, t), await S(t);
511
+ } catch (i) {
512
+ throw await h(s), i;
513
+ }
514
+ }, x = async ({
515
+ packageRoot: n,
516
+ schema: t
517
+ }) => {
518
+ const e = r(n, "dist", v);
519
+ await N({ schema: t, outputPath: e }), await c(
520
+ r(e, "index.mjs"),
521
+ r(n, "dist", "core.mjs")
522
+ ), await c(
523
+ r(e, "index.cjs"),
524
+ r(n, "dist", "core.cjs")
525
+ );
526
+ }, S = async (n) => {
527
+ const t = r(n, "index.ts"), e = r(n, "index.mjs");
528
+ await u({
529
+ entryPoints: [t],
530
+ outfile: e,
531
+ bundle: !0,
532
+ format: "esm",
533
+ platform: "node",
534
+ target: "node18",
535
+ sourcemap: !1,
536
+ minify: !1
537
+ }), await u({
538
+ entryPoints: [t],
539
+ outfile: r(n, "index.cjs"),
540
+ bundle: !0,
541
+ format: "cjs",
542
+ platform: "node",
543
+ target: "node18",
544
+ sourcemap: !1,
545
+ minify: !1
546
+ }), await I(
547
+ r(n, "package.json"),
548
+ JSON.stringify(
549
+ { type: "module", main: "index.mjs", module: "index.mjs" },
550
+ null,
551
+ 2
552
+ )
553
+ );
554
+ }, G = {
555
+ DateTime: "string",
556
+ JSON: "Record<string, unknown>",
557
+ UUID: "string"
558
+ }, b = async ({
559
+ schema: n,
560
+ outputPath: t,
561
+ clientWrapperTemplateSource: e
562
+ }) => {
563
+ const a = e ?? w;
564
+ await E(t), await g(t), await y({
565
+ schema: n,
566
+ output: t,
567
+ scalarTypes: {
568
+ ...G,
569
+ Upload: "File"
570
+ }
571
+ });
572
+ const s = T(a, {
573
+ apiClientName: "MetadataApiClient",
574
+ defaultUrl: `\`\${process.env.${_}}/metadata\``,
575
+ includeUploadFile: !0
576
+ });
577
+ await m(r(t, "index.ts"), s);
578
+ };
579
+ export {
580
+ v as GENERATED_CORE_DIR,
581
+ N as generateCoreClientFromSchema,
582
+ b as generateMetadataClient,
583
+ x as replaceCoreClient
584
+ };
@@ -0,0 +1,60 @@
1
+ import type { QueryGenqlSelection, Query, MutationGenqlSelection, Mutation, SubscriptionGenqlSelection, Subscription } from './schema';
2
+ import { type FieldsSelection, type GraphqlOperation, type ClientOptions, GenqlError } from './runtime';
3
+ export type { FieldsSelection } from './runtime';
4
+ export { GenqlError };
5
+ export * from './schema';
6
+ export interface Client {
7
+ query<R extends QueryGenqlSelection>(request: R & {
8
+ __name?: string;
9
+ }): Promise<FieldsSelection<Query, R>>;
10
+ mutation<R extends MutationGenqlSelection>(request: R & {
11
+ __name?: string;
12
+ }): Promise<FieldsSelection<Mutation, R>>;
13
+ }
14
+ export declare const createClient: (options?: ClientOptions | undefined) => Client;
15
+ export declare const everything: {
16
+ __scalar: boolean;
17
+ };
18
+ export type QueryResult<fields extends QueryGenqlSelection> = FieldsSelection<Query, fields>;
19
+ export declare const generateQueryOp: (fields: QueryGenqlSelection & {
20
+ __name?: string;
21
+ }) => GraphqlOperation;
22
+ export type MutationResult<fields extends MutationGenqlSelection> = FieldsSelection<Mutation, fields>;
23
+ export declare const generateMutationOp: (fields: MutationGenqlSelection & {
24
+ __name?: string;
25
+ }) => GraphqlOperation;
26
+ export type SubscriptionResult<fields extends SubscriptionGenqlSelection> = FieldsSelection<Subscription, fields>;
27
+ export declare const generateSubscriptionOp: (fields: SubscriptionGenqlSelection & {
28
+ __name?: string;
29
+ }) => GraphqlOperation;
30
+ type MetadataApiClientOptions = ClientOptions;
31
+ export declare class MetadataApiClient {
32
+ private client;
33
+ private url;
34
+ private requestOptions;
35
+ private headers;
36
+ private fetchImplementation;
37
+ private authorizationToken;
38
+ private refreshAccessTokenPromise;
39
+ constructor(options?: MetadataApiClientOptions);
40
+ query<R extends QueryGenqlSelection>(request: R & {
41
+ __name?: string;
42
+ }): Promise<FieldsSelection<Query, R>>;
43
+ mutation<R extends MutationGenqlSelection>(request: R & {
44
+ __name?: string;
45
+ }): Promise<FieldsSelection<Mutation, R>>;
46
+ uploadFile(fileBuffer: Buffer, filename: string, contentType: string | undefined, fieldMetadataUniversalIdentifier: string): Promise<{
47
+ id: string;
48
+ path: string;
49
+ size: number;
50
+ createdAt: string;
51
+ url: string;
52
+ }>;
53
+ private executeGraphqlRequestWithOptionalRefresh;
54
+ private executeGraphqlRequest;
55
+ private resolveHeaders;
56
+ private shouldRefreshToken;
57
+ private assertResponseIsSuccessful;
58
+ private requestRefreshedAccessToken;
59
+ private setAuthorizationToken;
60
+ }