security: vendor @genql/cli codegen to drop undici/native-fetch (#21339)
## What Vendors a narrowed copy of [`@genql/cli@3.0.5`](https://github.com/remorses/genql) (MIT) into `packages/twenty-client-sdk/src/generate/genql/` and repoints the two client generators at it, then removes `@genql/cli` from `twenty-client-sdk`, `twenty-sdk` and `create-twenty-app`. ## Why `@genql/cli` was used **only** to generate the typed GraphQL client from an SDL string. It is unmaintained and pulls in vulnerable/abandoned transitives — `undici@5` (**30 Dependabot alerts**), `native-fetch`, `listr`, `yargs`, etc. None of these were ever executed by Twenty: the sole consumer of `undici`/`native-fetch` is `@genql/cli`'s live-endpoint schema-introspection path, and Twenty always passes a schema string, never an endpoint. Removing the package eliminates the dependency at the source — for Twenty and for scaffolded end-user apps. ## What changed vs upstream The vendored copy (`genql/README.md` + `genql/LICENSE`) keeps the `render/` and `runtime/` trees verbatim and narrows the orchestration: - **Dropped the endpoint/introspection path** (`schema/fetchSchema.ts`) — the only `undici`/`native-fetch`/`qs` consumer. - **Dropped `listr`** — generation tasks run as plain sequential `async` functions (file contents unchanged). - **Replaced `fs-extra`/`mkdirp`/`rimraf`** with `node:fs`. - **Runtime templates are imported as `?raw`** and bundled, instead of read from `node_modules` at generation time. - **Kept `prettier@^2.8` and `@graphql-tools/*`** so the generated output is byte-for-byte identical. ## Verification - **Byte-identical output**: regenerating the metadata client from its committed schema produces a recursive-diff-clean result vs the previous `@genql/cli` output (including the copied `runtime/` folder). The core client generates and esbuild-bundles cleanly. - The public `twenty-client-sdk/generate` barrel API is unchanged (twenty-server / twenty-sdk consumers unaffected). - `undici@^5`, `native-fetch`, `@genql/cli`, `listr`, `yargs@^15` and `subscriptions-transport-ws@0.9` are gone from `yarn.lock` (net −364 lines). - `twenty-client-sdk` and `twenty-sdk` typecheck, lint and build; `twenty-client-sdk` tests pass (9/9). ## Notes - The vendored folder is excluded from `oxlint`/`oxfmt` (it is third-party code, with `@ts-nocheck` on the verbatim renderers, mirroring the generated output). - Stacks conceptually on #21334 (drops `@genql/runtime`); the two are independent and only overlap trivially in `yarn.lock`. `@genql/runtime` is intentionally left for that PR.
This commit is contained in:
@@ -28,7 +28,6 @@
|
||||
},
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@genql/cli": "^3.0.3",
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^12.0.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"categories": {
|
||||
"correctness": "off"
|
||||
},
|
||||
"ignorePatterns": ["node_modules", "dist", "generated"],
|
||||
"ignorePatterns": ["node_modules", "dist", "generated", "src/generate/genql"],
|
||||
"rules": {
|
||||
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
|
||||
"no-console": "off",
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
dist
|
||||
generated
|
||||
src/generate/genql
|
||||
|
||||
@@ -48,11 +48,14 @@
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@genql/cli": "^3.0.3",
|
||||
"@genql/runtime": "^2.10.0",
|
||||
"esbuild": "^0.28.0",
|
||||
"graphql": "^16.8.1"
|
||||
"graphql": "^16.8.1",
|
||||
"lodash": "^4.17.21",
|
||||
"prettier": "^2.8.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.17.15",
|
||||
"@typescript/native-preview": "^7.0.0-dev.20260116.1",
|
||||
"tsc-alias": "^1.8.16",
|
||||
"twenty-shared": "workspace:*",
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
import { mkdtemp, rm } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { generateCoreClientFromSchema } from '../generate-core-client';
|
||||
|
||||
// Exercises a client produced by the vendored genql codegen end to end: the
|
||||
// real runtime builds the GraphQL operation from a selection, posts it through
|
||||
// an injected fetch, and parses the response. This complements
|
||||
// client-wrapper-auth.test.ts, which stubs createClient and so never runs the
|
||||
// vendored runtime's query-building path.
|
||||
const SCHEMA = `
|
||||
type Query {
|
||||
hello: String
|
||||
person(id: ID!): Person
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
createPerson(name: String!): Person
|
||||
}
|
||||
|
||||
type Person {
|
||||
id: ID!
|
||||
name: String
|
||||
}
|
||||
|
||||
schema {
|
||||
query: Query
|
||||
mutation: Mutation
|
||||
}
|
||||
`;
|
||||
|
||||
type GeneratedClient = {
|
||||
query: (request: Record<string, unknown>) => Promise<any>;
|
||||
mutation: (request: Record<string, unknown>) => Promise<any>;
|
||||
};
|
||||
|
||||
type CreateClient = (options: {
|
||||
url?: string;
|
||||
fetch?: typeof globalThis.fetch;
|
||||
batch?: boolean;
|
||||
}) => GeneratedClient;
|
||||
|
||||
const jsonResponse = (body: unknown, status = 200) =>
|
||||
new Response(JSON.stringify(body), {
|
||||
status,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
|
||||
describe('Generated client queries the API (vendored genql runtime)', () => {
|
||||
let temporaryDir: string;
|
||||
let createClient: CreateClient;
|
||||
|
||||
beforeAll(async () => {
|
||||
temporaryDir = await mkdtemp(join(tmpdir(), 'twenty-genql-query-'));
|
||||
const outputPath = join(temporaryDir, 'client');
|
||||
|
||||
await generateCoreClientFromSchema({ schema: SCHEMA, outputPath });
|
||||
|
||||
const generatedModule = await import(
|
||||
`${pathToFileURL(join(outputPath, 'index.mjs')).href}?t=${Date.now()}`
|
||||
);
|
||||
createClient = generatedModule.createClient as CreateClient;
|
||||
}, 60000);
|
||||
|
||||
afterAll(async () => {
|
||||
if (temporaryDir) {
|
||||
await rm(temporaryDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('builds a GraphQL query from a selection and returns parsed data', async () => {
|
||||
const fetchMock = vi.fn(async (_url: string, _init?: RequestInit) =>
|
||||
jsonResponse({ data: { person: { id: '1', name: 'Ada' } } }),
|
||||
);
|
||||
|
||||
const client = createClient({
|
||||
url: 'https://example.test/graphql',
|
||||
fetch: fetchMock as unknown as typeof globalThis.fetch,
|
||||
});
|
||||
|
||||
const result = await client.query({
|
||||
person: { __args: { id: '1' }, id: true, name: true },
|
||||
});
|
||||
|
||||
expect(result).toEqual({ person: { id: '1', name: 'Ada' } });
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
const [calledUrl, requestInit] = fetchMock.mock.calls[0];
|
||||
expect(calledUrl).toBe('https://example.test/graphql');
|
||||
expect(requestInit?.method).toBe('POST');
|
||||
|
||||
const body = JSON.parse(String(requestInit?.body));
|
||||
expect(body.query).toMatch(/person/);
|
||||
expect(body.query).toMatch(/\bid\b/);
|
||||
expect(body.query).toMatch(/\bname\b/);
|
||||
// the id argument made it into the operation (inline or as a variable)
|
||||
expect(JSON.stringify(body)).toContain('1');
|
||||
});
|
||||
|
||||
it('builds a mutation operation', async () => {
|
||||
const fetchMock = vi.fn(async (_url: string, _init?: RequestInit) =>
|
||||
jsonResponse({ data: { createPerson: { id: '2', name: 'Lin' } } }),
|
||||
);
|
||||
|
||||
const client = createClient({
|
||||
url: 'https://example.test/graphql',
|
||||
fetch: fetchMock as unknown as typeof globalThis.fetch,
|
||||
});
|
||||
|
||||
const result = await client.mutation({
|
||||
createPerson: { __args: { name: 'Lin' }, id: true, name: true },
|
||||
});
|
||||
|
||||
expect(result).toEqual({ createPerson: { id: '2', name: 'Lin' } });
|
||||
const body = JSON.parse(String(fetchMock.mock.calls[0][1]?.body));
|
||||
expect(body.query).toMatch(/mutation/);
|
||||
expect(body.query).toMatch(/createPerson/);
|
||||
});
|
||||
|
||||
it('throws when the API responds with GraphQL errors', async () => {
|
||||
const fetchMock = vi.fn(async () =>
|
||||
jsonResponse({ errors: [{ message: 'Not authorized' }], data: null }),
|
||||
);
|
||||
|
||||
const client = createClient({
|
||||
url: 'https://example.test/graphql',
|
||||
fetch: fetchMock as unknown as typeof globalThis.fetch,
|
||||
});
|
||||
|
||||
await expect(
|
||||
client.query({ person: { __args: { id: '1' }, id: true } }),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('rejects (does not hang) when a batched request fails', async () => {
|
||||
const fetchMock = vi.fn(async () => {
|
||||
throw new Error('network down');
|
||||
});
|
||||
|
||||
const client = createClient({
|
||||
url: 'https://example.test/graphql',
|
||||
fetch: fetchMock as unknown as typeof globalThis.fetch,
|
||||
// batch is the path where the runtime previously swallowed fetch errors,
|
||||
// leaving the caller's promise unsettled.
|
||||
batch: true,
|
||||
});
|
||||
|
||||
await expect(
|
||||
client.query({ person: { __args: { id: '1' }, id: true } }),
|
||||
).rejects.toThrow(/network down/);
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,12 @@
|
||||
import { appendFile, copyFile, writeFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { generate } from '@genql/cli';
|
||||
import { build } from 'esbuild';
|
||||
import { DEFAULT_API_URL_NAME } from 'twenty-shared/application';
|
||||
|
||||
import { buildClientWrapperSource } from './client-wrapper';
|
||||
import { emptyDir, ensureDir, move, remove } from './fs-utils';
|
||||
import { generate } from './genql';
|
||||
import twentyClientTemplateSource from './twenty-client-template.ts?raw';
|
||||
|
||||
const COMMON_SCALAR_TYPES = {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { appendFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { generate } from '@genql/cli';
|
||||
import { DEFAULT_API_URL_NAME } from 'twenty-shared/application';
|
||||
|
||||
import { buildClientWrapperSource } from './client-wrapper';
|
||||
import { emptyDir, ensureDir } from './fs-utils';
|
||||
import { generate } from './genql';
|
||||
import twentyClientTemplateSource from './twenty-client-template.ts?raw';
|
||||
|
||||
const COMMON_SCALAR_TYPES = {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Tommaso De Rossi, morse <beats.by.morse@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,45 @@
|
||||
# Vendored genql codegen
|
||||
|
||||
This folder is a narrowed, vendored copy of [`@genql/cli`](https://github.com/remorses/genql)
|
||||
`3.0.5` (MIT, © Tommaso De Rossi "morse"), used to generate the typed GraphQL
|
||||
client from an SDL string.
|
||||
|
||||
It was vendored to remove `@genql/cli` from the dependency graph, which pulled
|
||||
in abandoned and vulnerable transitive packages (`undici`, `native-fetch`,
|
||||
`listr`, etc.) that Twenty never executed.
|
||||
|
||||
## What was kept
|
||||
|
||||
- `render/` — the schema → TypeScript client renderers (copied verbatim).
|
||||
- `runtime/` — the genql client runtime, copied verbatim into every generated
|
||||
client's `runtime/` folder (see `runtime-templates.ts`).
|
||||
- `tasks/`, `helpers/`, `main.ts` — narrowed orchestration.
|
||||
|
||||
## What was changed vs upstream
|
||||
|
||||
- **Dropped the live-endpoint introspection path** (`schema/fetchSchema.ts`),
|
||||
which was the only consumer of `undici` / `native-fetch` / `qs`. Twenty always
|
||||
passes a schema string, never an endpoint.
|
||||
- **Dropped `listr`** — the generation tasks now run as plain sequential
|
||||
`async` functions. File contents are unchanged.
|
||||
- **Replaced `fs-extra` / `mkdirp` / `rimraf`** with `node:fs`.
|
||||
- **Replaced `@graphql-tools/load`** with graphql's own `buildSchema` — Twenty
|
||||
passes an SDL string, so the extra loader (and its dependency) is unnecessary.
|
||||
Verified to produce byte-identical output.
|
||||
- **Runtime files are imported as `?raw` text** (`runtime-templates.ts`) instead
|
||||
of read from `node_modules` at generation time, so they ship with this bundle.
|
||||
- **`Config` was narrowed** to the schema-string inputs Twenty actually passes
|
||||
(`schema`, `output`, `scalarTypes`, `sortProperties`). The introspection
|
||||
(`endpoint`/`useGet`/`headers`), custom-`fetch` (`fetchImport`) and listr
|
||||
(`verbose`) options were removed. The renderers are otherwise verbatim, so the
|
||||
generated client still defaults its url/fetch to `undefined` (Twenty's wrapper
|
||||
supplies them) and the output is unchanged.
|
||||
|
||||
The generated output is byte-for-byte identical to what `@genql/cli@3.0.5`
|
||||
produced; `prettier@^2.8` is retained for that reason. The runtime query path is
|
||||
covered by `__tests__/generated-client-query.test.ts`, which drives a real
|
||||
generated client against a mock transport.
|
||||
|
||||
## License
|
||||
|
||||
MIT — see `LICENSE`.
|
||||
@@ -0,0 +1,19 @@
|
||||
// Path the generated client imports its runtime from.
|
||||
export const RUNTIME_LIB_NAME = './runtime';
|
||||
|
||||
// Configuration for the narrowed codegen. Twenty only ever generates from a
|
||||
// schema string. Upstream genql additionally supported live-endpoint
|
||||
// introspection (`endpoint`/`useGet`/`headers`), a custom `fetch` import
|
||||
// (`fetchImport`) and listr `verbose` output — all of which were dropped when
|
||||
// this codegen was vendored, so they are omitted here. The generated client's
|
||||
// connection (url/fetch) is provided by the wrapper in twenty-client-template.ts.
|
||||
export interface Config {
|
||||
// the schema string (SDL)
|
||||
schema?: string;
|
||||
// the output dir
|
||||
output?: string;
|
||||
// maps GraphQL scalars to TypeScript types
|
||||
scalarTypes?: { [k: string]: string };
|
||||
// sort the schema lexicographically before rendering
|
||||
sortProperties?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { promises as fs } from 'node:fs';
|
||||
import { homedir } from 'node:os';
|
||||
import { parse, resolve } from 'node:path';
|
||||
|
||||
// Guard against `clear` ever wiping something significant if a caller passes a
|
||||
// misconfigured output path: refuse the filesystem root, shallow top-level
|
||||
// directories, and the user's home directory or any of its ancestors.
|
||||
const assertSafeToClear = (target: string) => {
|
||||
const { root } = parse(target);
|
||||
const depthBelowRoot = target
|
||||
.slice(root.length)
|
||||
.split(/[\\/]/)
|
||||
.filter(Boolean).length;
|
||||
|
||||
const home = resolve(homedir());
|
||||
const isHomeOrAncestor = target === home || home.startsWith(`${target}/`);
|
||||
|
||||
if (target === root || depthBelowRoot < 2 || isHomeOrAncestor) {
|
||||
throw new Error(`Refusing to recursively clear unsafe path: ${target}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const ensurePath = async (path: string[], clear: boolean = false) => {
|
||||
const target = resolve(...path);
|
||||
if (clear) {
|
||||
assertSafeToClear(target);
|
||||
await fs.rm(target, { recursive: true, force: true });
|
||||
}
|
||||
await fs.mkdir(target, { recursive: true });
|
||||
};
|
||||
|
||||
export const writeFileToPath = async (path: string[], content: string) => {
|
||||
const folder = resolve(...path, '..');
|
||||
await fs.mkdir(folder, { recursive: true });
|
||||
await fs.writeFile(resolve(...path), content);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
// @ts-nocheck
|
||||
import prettier from 'prettier/standalone'
|
||||
import { BuiltInParserName } from 'prettier'
|
||||
import parserGraphql from 'prettier/parser-graphql'
|
||||
import parserTS from 'prettier/parser-typescript'
|
||||
|
||||
export const prettify = (code: string, parser?: BuiltInParserName): string => {
|
||||
// return code
|
||||
return prettier.format(code, {
|
||||
parser,
|
||||
plugins: [parserGraphql, parserTS],
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
printWidth: 80,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { generate } from './main';
|
||||
export { type Config } from './config';
|
||||
@@ -0,0 +1,14 @@
|
||||
import { type Config } from './config';
|
||||
import { writeClientFiles } from './tasks/clientTasks';
|
||||
import { loadConfiguredSchema } from './tasks/schemaTask';
|
||||
|
||||
// Drop-in replacement for `@genql/cli`'s `generate`, narrowed to the
|
||||
// schema-string code path Twenty uses. See ./README.md for details.
|
||||
export const generate = async (config: Config): Promise<void> => {
|
||||
if (!config.output) {
|
||||
throw new Error('`output` must be defined in the config');
|
||||
}
|
||||
|
||||
const schema = await loadConfiguredSchema(config);
|
||||
await writeClientFiles(config, schema);
|
||||
};
|
||||
@@ -0,0 +1,158 @@
|
||||
// @ts-nocheck
|
||||
import { GraphQLSchema } from 'graphql'
|
||||
import { RenderContext } from '../common/RenderContext'
|
||||
import { RUNTIME_LIB_NAME } from '../../config'
|
||||
import { requestTypeName } from '../requestTypes/requestTypeName'
|
||||
|
||||
const renderClientCode = (ctx: RenderContext) => {
|
||||
const url = ctx.config?.endpoint ? `"${ctx.config.endpoint}"` : 'undefined'
|
||||
const fetchImport = ctx.config?.fetchImport
|
||||
return `
|
||||
function(options${url ? '?' : ''}: ClientOptions): Client {
|
||||
return createClientOriginal({
|
||||
url: ${url},
|
||||
${fetchImport ? `fetch,` : ''}
|
||||
...options,
|
||||
queryRoot: typeMap.Query!,
|
||||
mutationRoot: typeMap.Mutation!,
|
||||
subscriptionRoot: typeMap.Subscription!,
|
||||
}) as any
|
||||
}`
|
||||
}
|
||||
|
||||
export const renderClientEsm = (schema: GraphQLSchema, ctx: RenderContext) => {
|
||||
const queryType = schema.getQueryType()
|
||||
const mutationType = schema.getMutationType()
|
||||
const subscriptionType = schema.getSubscriptionType()
|
||||
const fetchImport = ctx.config?.fetchImport || ''
|
||||
ctx.addCodeBlock(`
|
||||
${fetchImport}
|
||||
${renderClientTypesImports({ mutationType, queryType, subscriptionType })}
|
||||
import {
|
||||
linkTypeMap,
|
||||
createClient as createClientOriginal,
|
||||
generateGraphqlOperation,
|
||||
type FieldsSelection, type GraphqlOperation, type ClientOptions, GenqlError
|
||||
} from '${RUNTIME_LIB_NAME}'
|
||||
export type { FieldsSelection } from '${RUNTIME_LIB_NAME}'
|
||||
export { GenqlError }
|
||||
|
||||
import types from './types'
|
||||
export * from './schema'
|
||||
const typeMap = linkTypeMap(types as any)
|
||||
|
||||
${renderClientType({ mutationType, queryType, subscriptionType })}
|
||||
|
||||
export const createClient = ${renderClientCode(ctx)}
|
||||
|
||||
export const everything = {
|
||||
__scalar: true
|
||||
}
|
||||
`)
|
||||
|
||||
if (queryType) {
|
||||
ctx.addCodeBlock(`
|
||||
export type QueryResult<fields extends ${requestTypeName(
|
||||
queryType,
|
||||
)}> = FieldsSelection<${queryType.name}, fields>
|
||||
export const generateQueryOp: (fields: ${requestTypeName(
|
||||
queryType,
|
||||
)} & { __name?: string }) => GraphqlOperation = function(fields) {
|
||||
return generateGraphqlOperation('query', typeMap.Query!, fields as any)
|
||||
}
|
||||
`)
|
||||
}
|
||||
if (mutationType) {
|
||||
ctx.addCodeBlock(`
|
||||
export type MutationResult<fields extends ${requestTypeName(
|
||||
mutationType,
|
||||
)}> = FieldsSelection<${mutationType.name}, fields>
|
||||
export const generateMutationOp: (fields: ${requestTypeName(
|
||||
mutationType,
|
||||
)} & { __name?: string }) => GraphqlOperation = function(fields) {
|
||||
return generateGraphqlOperation('mutation', typeMap.Mutation!, fields as any)
|
||||
}
|
||||
`)
|
||||
}
|
||||
if (subscriptionType) {
|
||||
ctx.addCodeBlock(`
|
||||
export type SubscriptionResult<fields extends ${requestTypeName(
|
||||
subscriptionType,
|
||||
)}> = FieldsSelection<${subscriptionType.name}, fields>
|
||||
export const generateSubscriptionOp: (fields: ${requestTypeName(
|
||||
subscriptionType,
|
||||
)} & { __name?: string }) => GraphqlOperation = function(fields) {
|
||||
return generateGraphqlOperation('subscription', typeMap.Subscription!, fields as any)
|
||||
}
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
function renderClientTypesImports({
|
||||
queryType,
|
||||
mutationType,
|
||||
subscriptionType,
|
||||
}) {
|
||||
const imports: string[] = []
|
||||
if (queryType) {
|
||||
imports.push(
|
||||
requestTypeName(queryType),
|
||||
|
||||
queryType.name,
|
||||
)
|
||||
}
|
||||
|
||||
if (mutationType) {
|
||||
imports.push(
|
||||
requestTypeName(mutationType),
|
||||
|
||||
mutationType.name,
|
||||
)
|
||||
}
|
||||
if (subscriptionType) {
|
||||
imports.push(
|
||||
requestTypeName(subscriptionType),
|
||||
|
||||
subscriptionType.name,
|
||||
)
|
||||
}
|
||||
if (imports.length > 0) {
|
||||
return `import type {${imports.join(',')}} from './schema'`
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function renderClientType({ queryType, mutationType, subscriptionType }) {
|
||||
let interfaceContent = ''
|
||||
|
||||
if (queryType) {
|
||||
interfaceContent += `
|
||||
query<R extends ${requestTypeName(queryType)}>(
|
||||
request: R & { __name?: string },
|
||||
): Promise<FieldsSelection<${queryType.name}, R>>
|
||||
`
|
||||
}
|
||||
|
||||
if (mutationType) {
|
||||
interfaceContent += `
|
||||
mutation<R extends ${requestTypeName(mutationType)}>(
|
||||
request: R & { __name?: string },
|
||||
): Promise<FieldsSelection<${mutationType.name}, R>>
|
||||
`
|
||||
}
|
||||
|
||||
// TODO add subscription client again
|
||||
// if (subscriptionType) {
|
||||
// interfaceContent += `
|
||||
// subscription<R extends ${requestTypeName(subscriptionType)}>(
|
||||
// request: R & { __name?: string },
|
||||
// ): Observable<FieldsSelection<${subscriptionType.name}, R>>
|
||||
// `
|
||||
// }
|
||||
|
||||
return `
|
||||
export interface Client {
|
||||
${interfaceContent}
|
||||
}
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// @ts-nocheck
|
||||
import { GraphQLSchema } from 'graphql'
|
||||
import { BuiltInParserName } from 'prettier'
|
||||
import { Config } from '../../config'
|
||||
import { prettify } from '../../helpers/prettify'
|
||||
import { relativeImportPath } from './relativeImportPath'
|
||||
|
||||
interface Import {
|
||||
isDefault: boolean
|
||||
module?: string
|
||||
alias?: string
|
||||
}
|
||||
|
||||
interface ImportMap {
|
||||
[from: string]: Import[]
|
||||
}
|
||||
|
||||
export class RenderContext {
|
||||
protected codeBlocks: string[] = []
|
||||
protected imports: ImportMap = {}
|
||||
protected importAliasCounter = 0
|
||||
|
||||
constructor(public schema?: GraphQLSchema, public config?: Config) {}
|
||||
|
||||
addCodeBlock(block: string) {
|
||||
if (block) {
|
||||
this.codeBlocks.push(block)
|
||||
}
|
||||
}
|
||||
|
||||
addImport(
|
||||
from: string,
|
||||
isDefault: boolean,
|
||||
module?: string,
|
||||
fromAbsolute?: boolean,
|
||||
noAlias?: boolean,
|
||||
) {
|
||||
if (this.config && this.config.output) {
|
||||
from = fromAbsolute
|
||||
? from
|
||||
: relativeImportPath(this.config.output, from)
|
||||
}
|
||||
|
||||
if (!this.imports[from]) this.imports[from] = []
|
||||
|
||||
const imports = this.imports[from]
|
||||
|
||||
const existing = imports.find(
|
||||
(i) =>
|
||||
(isDefault && i.isDefault) ||
|
||||
(!isDefault && i.module === module),
|
||||
)
|
||||
|
||||
if (existing) return existing.alias
|
||||
|
||||
this.importAliasCounter++
|
||||
const alias = noAlias ? undefined : `a${this.importAliasCounter}`
|
||||
imports.push({ isDefault, module, alias })
|
||||
|
||||
return alias
|
||||
}
|
||||
|
||||
protected getImportBlock() {
|
||||
const imports: string[] = []
|
||||
|
||||
Object.keys(this.imports).forEach((from) => {
|
||||
let defaultImport = this.imports[from].find((i) => i.isDefault)
|
||||
const namedImports = this.imports[from].filter((i) => !i.isDefault)
|
||||
const statements: string[] = []
|
||||
|
||||
if (defaultImport) {
|
||||
statements.push(defaultImport.alias || '')
|
||||
}
|
||||
|
||||
if (namedImports.length > 0) {
|
||||
statements.push(
|
||||
`{${namedImports
|
||||
.map((i) =>
|
||||
i.alias ? `${i.module} as ${i.alias}` : i.module,
|
||||
)
|
||||
.join(',')}}`,
|
||||
)
|
||||
}
|
||||
|
||||
imports.push(`import ${statements.join(',')} from '${from}'`)
|
||||
})
|
||||
|
||||
if (imports.length > 0) return imports.join('\n')
|
||||
else return
|
||||
}
|
||||
|
||||
toCode(parser?: BuiltInParserName, pretty = false) {
|
||||
const blocks = [...this.codeBlocks]
|
||||
|
||||
if (parser && (parser === 'typescript' || parser === 'babel')) {
|
||||
const importBlock = this.getImportBlock()
|
||||
if (importBlock) blocks.unshift(importBlock)
|
||||
}
|
||||
if (parser && pretty) {
|
||||
return prettify(blocks.join('\n\n'), parser)
|
||||
}
|
||||
if (parser) {
|
||||
return blocks.join('\n\n')
|
||||
}
|
||||
return blocks.join('')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// @ts-nocheck
|
||||
import { GraphQLArgument, GraphQLEnumValue, GraphQLField, GraphQLInputField, GraphQLNamedType } from 'graphql'
|
||||
|
||||
|
||||
export const comment = (comment: { text?: string | null; deprecated?: string | null }) => {
|
||||
const lines: string[] = []
|
||||
|
||||
if (comment.deprecated) {
|
||||
lines.push(`@deprecated ${comment.deprecated.replace(/\s/g, ' ')}`)
|
||||
}
|
||||
|
||||
if (comment.text) {
|
||||
lines.push(...comment.text.split('\n'))
|
||||
}
|
||||
|
||||
return lines.length > 0
|
||||
? lines.length === 1
|
||||
? `\n/** ${lines[0]} */\n`
|
||||
: `\n/**\n${lines.map(l => ` * ${l}`).join('\n')}\n */\n`
|
||||
: ''
|
||||
}
|
||||
|
||||
export const typeComment = (type: GraphQLNamedType) =>
|
||||
comment({
|
||||
text: type.description,
|
||||
})
|
||||
|
||||
export const fieldComment = (field: GraphQLEnumValue | GraphQLField<any, any, any>) =>
|
||||
comment({
|
||||
deprecated: field.deprecationReason,
|
||||
text: field.description,
|
||||
})
|
||||
|
||||
export const argumentComment = (arg: GraphQLArgument | GraphQLInputField) =>
|
||||
comment({
|
||||
text: arg.description,
|
||||
})
|
||||
@@ -0,0 +1,11 @@
|
||||
// @ts-nocheck
|
||||
export const excludedTypes = [
|
||||
'__Schema',
|
||||
'__Type',
|
||||
'__TypeKind',
|
||||
'__Field',
|
||||
'__InputValue',
|
||||
'__EnumValue',
|
||||
'__Directive',
|
||||
'__DirectiveLocation',
|
||||
]
|
||||
@@ -0,0 +1,7 @@
|
||||
// @ts-nocheck
|
||||
import path from 'path'
|
||||
|
||||
export const relativeImportPath = (from: string, to: string) => {
|
||||
const fromResolved = path.relative(from, to)
|
||||
return fromResolved[0] === '.' ? fromResolved : `./${fromResolved}`
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// @ts-nocheck
|
||||
import { GraphQLInputType, GraphQLNonNull, GraphQLOutputType, isListType, isNamedType, isNonNullType, isScalarType } from 'graphql'
|
||||
|
||||
|
||||
const render = (
|
||||
type: GraphQLOutputType | GraphQLInputType,
|
||||
nonNull: boolean,
|
||||
root: boolean,
|
||||
undefinableValues: boolean,
|
||||
undefinableFields: boolean,
|
||||
wrap: (x: string) => string = x => x
|
||||
): string => {
|
||||
|
||||
if (root) {
|
||||
if (undefinableFields) {
|
||||
if (isNonNullType(type)) {
|
||||
return `: ${render(type.ofType, true, false, undefinableValues, undefinableFields, wrap)}`
|
||||
} else {
|
||||
const rendered = render(type, true, false, undefinableValues, undefinableFields, wrap)
|
||||
return undefinableValues ? `?: ${rendered}` : `?: (${rendered} | null)`
|
||||
}
|
||||
} else {
|
||||
return `: ${render(type, false, false, undefinableValues, undefinableFields, wrap)}`
|
||||
}
|
||||
}
|
||||
|
||||
if (isNamedType(type)) {
|
||||
let typeName = type.name
|
||||
|
||||
// if is a scalar use the scalar interface to not expose reserved words
|
||||
if (isScalarType(type)) {
|
||||
typeName = `Scalars['${typeName}']`
|
||||
}
|
||||
|
||||
const typing = wrap(typeName)
|
||||
|
||||
if (undefinableValues) {
|
||||
return nonNull ? typing : `(${typing} | undefined)`
|
||||
} else {
|
||||
return nonNull ? typing : `(${typing} | null)`
|
||||
}
|
||||
}
|
||||
|
||||
if (isListType(type)) {
|
||||
const typing = `${render(type.ofType, false, false, undefinableValues, undefinableFields, wrap)}[]`
|
||||
|
||||
if (undefinableValues) {
|
||||
return nonNull ? typing : `(${typing} | undefined)`
|
||||
} else {
|
||||
return nonNull ? typing : `(${typing} | null)`
|
||||
}
|
||||
}
|
||||
|
||||
return render((type as GraphQLNonNull<any>).ofType, true, false, undefinableValues, undefinableFields, wrap)
|
||||
}
|
||||
|
||||
export const renderTyping = (
|
||||
type: GraphQLOutputType | GraphQLInputType,
|
||||
undefinableValues: boolean,
|
||||
undefinableFields: boolean,
|
||||
root = true,
|
||||
wrap: any = undefined
|
||||
) => render(type, false, root, undefinableValues, undefinableFields, wrap)
|
||||
@@ -0,0 +1,16 @@
|
||||
// @ts-nocheck
|
||||
export function sortKeys(obj: Record<any, any>): Record<any, any> {
|
||||
obj = obj || {}
|
||||
const ordered = {}
|
||||
Object.keys(obj)
|
||||
.sort()
|
||||
// .reverse()
|
||||
.forEach(function(key) {
|
||||
ordered[key] = obj[key]
|
||||
})
|
||||
return ordered
|
||||
}
|
||||
|
||||
export function intersection<T>(a: T[][]): T[] {
|
||||
return a.reduce((p, c) => p.filter((e) => c.includes(e)))
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// @ts-nocheck
|
||||
import { GraphQLInputObjectType } from 'graphql'
|
||||
import { argumentComment, typeComment } from '../common/comment'
|
||||
import { RenderContext } from '../common/RenderContext'
|
||||
import { renderTyping } from '../common/renderTyping'
|
||||
import { sortKeys } from '../common/support'
|
||||
|
||||
export const inputObjectType = (type: GraphQLInputObjectType, ctx: RenderContext) => {
|
||||
let fields = type.getFields()
|
||||
|
||||
if (ctx.config?.sortProperties) {
|
||||
fields = sortKeys(fields)
|
||||
}
|
||||
|
||||
|
||||
const fieldStrings = Object.keys(fields).map(fieldName => {
|
||||
const field = fields[fieldName]
|
||||
return `${argumentComment(field)}${field.name}${renderTyping(field.type, false, true)}`
|
||||
})
|
||||
|
||||
ctx.addCodeBlock(`${typeComment(type)}export interface ${type.name} {${fieldStrings.join(',')}}`)
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// @ts-nocheck
|
||||
import {
|
||||
getNamedType,
|
||||
GraphQLField,
|
||||
GraphQLInterfaceType,
|
||||
GraphQLObjectType,
|
||||
isEnumType,
|
||||
isInterfaceType,
|
||||
isScalarType,
|
||||
} from 'graphql'
|
||||
import { argumentComment, fieldComment, typeComment } from '../common/comment'
|
||||
import { RenderContext } from '../common/RenderContext'
|
||||
|
||||
import { requestTypeName } from './requestTypeName'
|
||||
import { sortKeys } from '../common/support'
|
||||
import { renderTyping } from '../common/renderTyping'
|
||||
|
||||
const INDENTATION = ' '
|
||||
|
||||
export const objectType = (
|
||||
type: GraphQLObjectType | GraphQLInterfaceType,
|
||||
ctx: RenderContext,
|
||||
) => {
|
||||
let fields = type.getFields()
|
||||
|
||||
if (ctx.config?.sortProperties) {
|
||||
fields = sortKeys(fields)
|
||||
}
|
||||
|
||||
let fieldStrings = Object.keys(fields).map((fieldName) => {
|
||||
const field = fields[fieldName]
|
||||
|
||||
const types: string[] = []
|
||||
const resolvedType = getNamedType(field.type)
|
||||
const resolvable = !(
|
||||
isEnumType(resolvedType) || isScalarType(resolvedType)
|
||||
)
|
||||
const argsPresent = field.args.length > 0
|
||||
const argsString = toArgsString(field)
|
||||
const argsOptional = !argsString.match(/[^?]:/)
|
||||
|
||||
if (argsPresent) {
|
||||
if (resolvable) {
|
||||
types.push(
|
||||
`(${requestTypeName(resolvedType)} & { __args${
|
||||
argsOptional ? '?' : ''
|
||||
}: ${argsString} })`,
|
||||
)
|
||||
} else {
|
||||
// TODO if i want to add __directive support, i need to make this __args optional
|
||||
types.push(`{ __args: ${argsString} }`)
|
||||
}
|
||||
// if (resolvable) {
|
||||
// types.push(`[${argsString},${requestTypeName(resolvedType)}]`)
|
||||
// } else {
|
||||
// types.push(`[${argsString}]`)
|
||||
// }
|
||||
}
|
||||
|
||||
if (argsOptional && !resolvable) {
|
||||
types.push('boolean | number')
|
||||
}
|
||||
if (!argsPresent && resolvable) {
|
||||
types.push(requestTypeName(resolvedType))
|
||||
}
|
||||
|
||||
return `${fieldComment(field)}${field.name}?: ${types.join(' | ')}`
|
||||
})
|
||||
|
||||
if (isInterfaceType(type) && ctx.schema) {
|
||||
let interfaceProperties = ctx.schema
|
||||
.getPossibleTypes(type)
|
||||
.map((t) => `on_${t.name}?: ${requestTypeName(t)}`)
|
||||
if (ctx.config?.sortProperties) {
|
||||
interfaceProperties = interfaceProperties.sort()
|
||||
}
|
||||
fieldStrings = fieldStrings.concat(interfaceProperties)
|
||||
}
|
||||
|
||||
fieldStrings.push('__typename?: boolean | number')
|
||||
fieldStrings.push('__scalar?: boolean | number')
|
||||
|
||||
// add indentation
|
||||
fieldStrings = fieldStrings.map((x) =>
|
||||
x
|
||||
.split('\n')
|
||||
.filter(Boolean)
|
||||
.map((l) => INDENTATION + l)
|
||||
.join('\n'),
|
||||
)
|
||||
|
||||
ctx.addCodeBlock(
|
||||
`${typeComment(type)}export interface ${requestTypeName(
|
||||
type,
|
||||
)}{\n${fieldStrings.join('\n')}\n}`,
|
||||
)
|
||||
}
|
||||
|
||||
export const toArgsString = (field: GraphQLField<any, any, any>) => {
|
||||
let fields = field.args
|
||||
.map(
|
||||
(a) =>
|
||||
`${argumentComment(a)}${a.name}${renderTyping(
|
||||
a.type,
|
||||
false,
|
||||
true,
|
||||
)}`,
|
||||
)
|
||||
.join(', ')
|
||||
return `{${fields}}`
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
// @ts-nocheck
|
||||
import {
|
||||
GraphQLSchema,
|
||||
isInputObjectType,
|
||||
isInterfaceType,
|
||||
isObjectType,
|
||||
isUnionType,
|
||||
GraphQLObjectType,
|
||||
} from 'graphql'
|
||||
import { excludedTypes } from '../common/excludedTypes'
|
||||
import { RenderContext } from '../common/RenderContext'
|
||||
import { inputObjectType } from './inputObjectType'
|
||||
import { objectType } from './objectType'
|
||||
import { unionType } from './unionType'
|
||||
import { sortKeys } from '../common/support'
|
||||
import { requestTypeName } from './requestTypeName'
|
||||
|
||||
export const renderRequestTypes = (
|
||||
schema: GraphQLSchema,
|
||||
ctx: RenderContext,
|
||||
) => {
|
||||
let typeMap = schema.getTypeMap()
|
||||
|
||||
if (ctx.config?.sortProperties) {
|
||||
typeMap = sortKeys(typeMap)
|
||||
}
|
||||
|
||||
for (const name in typeMap) {
|
||||
if (excludedTypes.includes(name)) continue
|
||||
|
||||
const type = typeMap[name]
|
||||
|
||||
if (isObjectType(type) || isInterfaceType(type)) objectType(type, ctx)
|
||||
if (isInputObjectType(type)) inputObjectType(type, ctx)
|
||||
if (isUnionType(type)) unionType(type, ctx)
|
||||
}
|
||||
|
||||
const aliases = [
|
||||
{ type: schema.getQueryType(), name: 'QueryGenqlSelection' },
|
||||
{ type: schema.getMutationType(), name: 'MutationGenqlSelection' },
|
||||
{
|
||||
type: schema.getSubscriptionType(),
|
||||
name: 'SubscriptionGenqlSelection',
|
||||
},
|
||||
]
|
||||
.map(renderAlias)
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
|
||||
ctx.addCodeBlock(aliases)
|
||||
}
|
||||
|
||||
function renderAlias({
|
||||
type,
|
||||
name,
|
||||
}: {
|
||||
type?: GraphQLObjectType | null
|
||||
name: string
|
||||
}) {
|
||||
if (type && requestTypeName(type) !== name) {
|
||||
// TODO make the camel case or kebab case an option
|
||||
return `export type ${name} = ${requestTypeName(type)}`
|
||||
}
|
||||
return ''
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// @ts-nocheck
|
||||
import { GraphQLNamedType } from 'graphql'
|
||||
|
||||
export const requestTypeName = (type?: GraphQLNamedType) => {
|
||||
if (!type) return ''
|
||||
return `${type.name}GenqlSelection`
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// @ts-nocheck
|
||||
import { GraphQLUnionType } from 'graphql'
|
||||
import uniq from 'lodash/uniq'
|
||||
import { typeComment } from '../common/comment'
|
||||
import { RenderContext } from '../common/RenderContext'
|
||||
import { requestTypeName } from './requestTypeName'
|
||||
|
||||
export const unionType = (type: GraphQLUnionType, ctx: RenderContext) => {
|
||||
let types = [...type.getTypes()]
|
||||
if (ctx.config?.sortProperties) {
|
||||
types = types.sort()
|
||||
}
|
||||
const fieldStrings = types.map((t) => `on_${t.name}?:${requestTypeName(t)}`)
|
||||
|
||||
const commonInterfaces = uniq(types.map((x) => x.getInterfaces()).flat())
|
||||
fieldStrings.push(
|
||||
...commonInterfaces.map((type) => {
|
||||
return `on_${type.name}?: ${requestTypeName(type)}`
|
||||
}),
|
||||
)
|
||||
|
||||
fieldStrings.push('__typename?: boolean | number')
|
||||
|
||||
ctx.addCodeBlock(
|
||||
`${typeComment(type)}export interface ${requestTypeName(
|
||||
type,
|
||||
)}{\n${fieldStrings.map((x) => ' ' + x).join(',\n')}\n}`,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// @ts-nocheck
|
||||
import { GraphQLEnumType } from 'graphql'
|
||||
import { typeComment } from '../common/comment'
|
||||
import { RenderContext } from '../common/RenderContext'
|
||||
|
||||
export const enumType = (type: GraphQLEnumType, ctx: RenderContext) => {
|
||||
const values = type.getValues().map(v => `'${v.name}'`)
|
||||
ctx.addCodeBlock(`${typeComment(type)}export type ${type.name} = ${values.join(' | ')}`)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// @ts-nocheck
|
||||
import { GraphQLInterfaceType } from 'graphql'
|
||||
import { RenderContext } from '../common/RenderContext'
|
||||
import { typeComment } from '../common/comment'
|
||||
import { objectType } from './objectType'
|
||||
|
||||
export const interfaceType = (
|
||||
type: GraphQLInterfaceType,
|
||||
ctx: RenderContext,
|
||||
) => {
|
||||
if (!ctx.schema) {
|
||||
throw new Error('schema is required to render unionType')
|
||||
}
|
||||
const typeNames = ctx.schema.getPossibleTypes(type).map((t) => t.name)
|
||||
if (!typeNames.length) {
|
||||
objectType(type, ctx)
|
||||
} else {
|
||||
ctx.addCodeBlock(
|
||||
`${typeComment(type)}export type ${type.name} = (${typeNames.join(
|
||||
' | ',
|
||||
)}) & { __isUnion?: true }`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// interface should produce an object like
|
||||
// export type Nameable = {
|
||||
// __interface:{
|
||||
// name:string
|
||||
// };
|
||||
// __resolve:{
|
||||
// ['on_Card']: Card;
|
||||
// ['on_CardStack']: CardStack;
|
||||
// }
|
||||
// }
|
||||
|
||||
// export const interfaceType = (type: GraphQLInterfaceType, ctx: RenderContext) => {
|
||||
// if (!ctx.schema) {
|
||||
// throw new Error('schema is req required to render unionType ')
|
||||
// }
|
||||
// const typeNames = ctx.schema.getPossibleTypes(type).map((t) => t.name)
|
||||
// let resolveContent = typeNames
|
||||
// .map((name) => `on_${name}?: ${name}`)
|
||||
// .join('\n ')
|
||||
|
||||
// ctx.addCodeBlock(
|
||||
// `${typeComment(type)}export type ${type.name}={
|
||||
// __interface:
|
||||
// ${typeNames.join('|')}
|
||||
// __resolve: {
|
||||
// ${resolveContent}
|
||||
// }
|
||||
// __typename?: string
|
||||
// }`,
|
||||
// )
|
||||
// }
|
||||
@@ -0,0 +1,69 @@
|
||||
// @ts-nocheck
|
||||
import {
|
||||
GraphQLInterfaceType,
|
||||
GraphQLObjectType,
|
||||
isObjectType,
|
||||
} from 'graphql'
|
||||
import { fieldComment, typeComment } from '../common/comment'
|
||||
import { RenderContext } from '../common/RenderContext'
|
||||
import { renderTyping } from '../common/renderTyping'
|
||||
import { sortKeys } from '../common/support'
|
||||
|
||||
const INDENTATION = ' '
|
||||
|
||||
export const objectType = (
|
||||
type: GraphQLObjectType | GraphQLInterfaceType,
|
||||
ctx: RenderContext,
|
||||
) => {
|
||||
let fieldsMap = type.getFields()
|
||||
|
||||
if (ctx.config?.sortProperties) {
|
||||
fieldsMap = sortKeys(fieldsMap)
|
||||
}
|
||||
|
||||
const fields = Object.keys(fieldsMap).map(
|
||||
(fieldName) => fieldsMap[fieldName],
|
||||
)
|
||||
|
||||
if (!ctx.schema) throw new Error('no schema provided')
|
||||
|
||||
const typeNames = isObjectType(type)
|
||||
? [type.name]
|
||||
: ctx.schema.getPossibleTypes(type).map((t) => t.name)
|
||||
|
||||
let fieldStrings = fields
|
||||
.map((f) => {
|
||||
return `${fieldComment(f)}${f.name}${renderTyping(
|
||||
f.type,
|
||||
true,
|
||||
true,
|
||||
)}`
|
||||
})
|
||||
.concat([
|
||||
`__typename: ${
|
||||
typeNames.length > 0
|
||||
? typeNames.map((t) => `'${t}'`).join('|')
|
||||
: 'string'
|
||||
}`,
|
||||
])
|
||||
// add indentation
|
||||
fieldStrings = fieldStrings.map((x) =>
|
||||
x
|
||||
.split('\n')
|
||||
.filter(Boolean)
|
||||
.map((l) => INDENTATION + l)
|
||||
.join('\n'),
|
||||
)
|
||||
|
||||
// there is no need to add extensions as in graphql the implemented type must explicitly add the fields
|
||||
// const interfaceNames = isObjectType(type)
|
||||
// ? type.getInterfaces().map((i) => i.name)
|
||||
// : []
|
||||
// let extensions =
|
||||
// interfaceNames.length > 0 ? ` extends ${interfaceNames.join(',')}` : ''
|
||||
ctx.addCodeBlock(
|
||||
`${typeComment(type)}export interface ${
|
||||
type.name
|
||||
} {\n${fieldStrings.join('\n')}\n}`,
|
||||
)
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
// @ts-nocheck
|
||||
import {
|
||||
GraphQLSchema,
|
||||
isEnumType,
|
||||
isInterfaceType,
|
||||
isObjectType,
|
||||
isScalarType,
|
||||
isUnionType,
|
||||
GraphQLScalarType,
|
||||
GraphQLType,
|
||||
GraphQLObjectType,
|
||||
} from 'graphql'
|
||||
import { excludedTypes } from '../common/excludedTypes'
|
||||
import { RenderContext } from '../common/RenderContext'
|
||||
import { enumType } from './enumType'
|
||||
import { objectType } from './objectType'
|
||||
import { renderScalarTypes } from './scalarType'
|
||||
import { unionType } from './unionType'
|
||||
import { interfaceType } from './interfaceType'
|
||||
import { sortKeys } from '../common/support'
|
||||
|
||||
export const renderResponseTypes = (
|
||||
schema: GraphQLSchema,
|
||||
ctx: RenderContext,
|
||||
) => {
|
||||
let typeMap = schema.getTypeMap()
|
||||
if (ctx.config?.sortProperties) {
|
||||
typeMap = sortKeys(typeMap)
|
||||
}
|
||||
ctx.addCodeBlock(
|
||||
renderScalarTypes(
|
||||
ctx,
|
||||
Object.values(typeMap).filter((type): type is GraphQLScalarType =>
|
||||
isScalarType(type),
|
||||
),
|
||||
),
|
||||
)
|
||||
for (const name in typeMap) {
|
||||
if (excludedTypes.includes(name)) continue
|
||||
|
||||
const type = typeMap[name]
|
||||
|
||||
if (isEnumType(type)) enumType(type, ctx)
|
||||
if (isUnionType(type)) unionType(type, ctx)
|
||||
if (isObjectType(type)) objectType(type, ctx)
|
||||
if (isInterfaceType(type)) interfaceType(type, ctx)
|
||||
}
|
||||
|
||||
const aliases = [
|
||||
{ type: schema.getQueryType(), name: 'Query' },
|
||||
{ type: schema.getMutationType(), name: 'Mutation' },
|
||||
{ type: schema.getSubscriptionType(), name: 'Subscription' },
|
||||
]
|
||||
.map(renderAlias)
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
ctx.addCodeBlock(aliases)
|
||||
}
|
||||
|
||||
function renderAlias({
|
||||
type,
|
||||
name,
|
||||
}: {
|
||||
type?: GraphQLObjectType | null
|
||||
name: string
|
||||
}) {
|
||||
if (type && type.name !== name) {
|
||||
return `export type ${name} = ${type.name}`
|
||||
}
|
||||
return ''
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// @ts-nocheck
|
||||
import { GraphQLScalarType } from 'graphql'
|
||||
import { RenderContext } from '../common/RenderContext'
|
||||
import { getTypeMappedAlias } from './typeMappedAlias'
|
||||
|
||||
export function renderScalarTypes(
|
||||
ctx: RenderContext,
|
||||
types: GraphQLScalarType[],
|
||||
) {
|
||||
let content = ''
|
||||
types.forEach((type) => {
|
||||
content += ` ${type.name}: ${getTypeMappedAlias(type, ctx)},\n`
|
||||
})
|
||||
return `export type Scalars = {\n${content}}`
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// @ts-nocheck
|
||||
import { GraphQLNamedType } from 'graphql'
|
||||
import { RenderContext } from '../common/RenderContext'
|
||||
|
||||
const knownTypes: {
|
||||
[name: string]: string
|
||||
} = {
|
||||
Int: 'number',
|
||||
Float: 'number',
|
||||
String: 'string',
|
||||
Boolean: 'boolean',
|
||||
ID: 'string',
|
||||
}
|
||||
|
||||
export const getTypeMappedAlias = (
|
||||
type: GraphQLNamedType,
|
||||
ctx: RenderContext,
|
||||
) => {
|
||||
const map = { ...knownTypes, ...(ctx?.config?.scalarTypes || {}) }
|
||||
return map?.[type.name] || 'any'
|
||||
}
|
||||
|
||||
// export const renderTypeMappedAlias = (
|
||||
// type: GraphQLNamedType,
|
||||
// ctx: RenderContext,
|
||||
// ) => {
|
||||
// const mappedType = getTypeMappedAlias(type, ctx)
|
||||
// if (mappedType) {
|
||||
// ctx.addCodeBlock(
|
||||
// `${typeComment(type)}export type ${type.name} = ${mappedType}`,
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,43 @@
|
||||
// @ts-nocheck
|
||||
import { GraphQLUnionType } from 'graphql'
|
||||
import { RenderContext } from '../common/RenderContext'
|
||||
import { typeComment } from '../common/comment'
|
||||
|
||||
// union should produce an object like
|
||||
// export type ChangeCard = {
|
||||
// __union:SpecialCard | EffectCard;
|
||||
// __resolve:{
|
||||
// ['...on SpecialCard']: SpecialCard;
|
||||
// ['...on EffectCard']: EffectCard;
|
||||
// }
|
||||
// }
|
||||
|
||||
export const unionType = (type: GraphQLUnionType, ctx: RenderContext) => {
|
||||
let typeNames = type.getTypes().map((t) => t.name)
|
||||
if (ctx.config?.sortProperties) {
|
||||
typeNames = typeNames.sort()
|
||||
}
|
||||
ctx.addCodeBlock(
|
||||
`${typeComment(type)}export type ${type.name} = (${typeNames.join(
|
||||
' | ',
|
||||
)}) & { __isUnion?: true }`,
|
||||
)
|
||||
}
|
||||
|
||||
// export const unionType = (type: GraphQLUnionType, ctx: RenderContext) => {
|
||||
// const typeNames = type.getTypes().map((t) => t.name)
|
||||
// let resolveContent = typeNames
|
||||
// .map((name) => `on_${name}?: ${name}`)
|
||||
// .join('\n ')
|
||||
|
||||
// ctx.addCodeBlock(
|
||||
// `${typeComment(type)}export type ${type.name}={
|
||||
// __union:
|
||||
// ${typeNames.join('|')}
|
||||
// __resolve: {
|
||||
// ${resolveContent}
|
||||
// }
|
||||
// __typename?: string
|
||||
// }`,
|
||||
// )
|
||||
// }
|
||||
@@ -0,0 +1,7 @@
|
||||
// @ts-nocheck
|
||||
import { GraphQLSchema, printSchema } from 'graphql'
|
||||
import { RenderContext } from '../common/RenderContext'
|
||||
|
||||
export const renderSchema = (schema: GraphQLSchema, ctx: RenderContext) => {
|
||||
ctx.addCodeBlock(printSchema(schema))
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// @ts-nocheck
|
||||
import {
|
||||
GraphQLSchema,
|
||||
isInterfaceType,
|
||||
isObjectType,
|
||||
isUnionType,
|
||||
} from 'graphql'
|
||||
import { excludedTypes } from '../common/excludedTypes'
|
||||
import { RenderContext } from '../common/RenderContext'
|
||||
|
||||
const renderTypeGuard = (target: string, possible: string[]) => {
|
||||
return `
|
||||
const ${target}_possibleTypes: string[] = [${possible
|
||||
.map((t) => `'${t}'`)
|
||||
.join(',')}]
|
||||
export const is${target} = (obj?: { __typename?: any } | null): obj is ${target} => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "is${target}"')
|
||||
return ${target}_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
export const renderTypeGuards = (schema: GraphQLSchema, ctx: RenderContext) => {
|
||||
const typeMap = schema.getTypeMap()
|
||||
for (const name in typeMap) {
|
||||
if (excludedTypes.includes(name)) continue
|
||||
|
||||
const type = typeMap[name]
|
||||
|
||||
if (isUnionType(type)) {
|
||||
const types = type.getTypes().map((t) => t.name)
|
||||
ctx.addCodeBlock(renderTypeGuard(type.name, types))
|
||||
} else if (isInterfaceType(type)) {
|
||||
const types = schema.getPossibleTypes(type).map((t) => t.name)
|
||||
ctx.addCodeBlock(renderTypeGuard(type.name, types))
|
||||
} else if (isObjectType(type)) {
|
||||
ctx.addCodeBlock(renderTypeGuard(type.name, [type.name]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// @ts-nocheck
|
||||
import {
|
||||
getNamedType,
|
||||
GraphQLInterfaceType,
|
||||
GraphQLObjectType,
|
||||
isEnumType,
|
||||
isInterfaceType,
|
||||
isScalarType,
|
||||
GraphQLInputObjectType,
|
||||
GraphQLArgument,
|
||||
GraphQLField,
|
||||
} from 'graphql'
|
||||
import { RenderContext } from '../common/RenderContext'
|
||||
import { ArgMap, Field, FieldMap } from '../../runtime/types'
|
||||
import { isEmpty } from './support'
|
||||
|
||||
export const objectType = (
|
||||
type: GraphQLObjectType | GraphQLInterfaceType | GraphQLInputObjectType,
|
||||
ctx: RenderContext,
|
||||
) => {
|
||||
const typeObj: FieldMap<string> = Object.keys(type.getFields()).reduce<
|
||||
FieldMap<string>
|
||||
>((r, f) => {
|
||||
const field = type.getFields()[f]
|
||||
const namedType = getNamedType(field.type)
|
||||
const fieldObj: Field<string> = { type: namedType.name }
|
||||
r[f] = fieldObj
|
||||
|
||||
const args: readonly GraphQLArgument[] =
|
||||
(field as GraphQLField<any, any>).args || []
|
||||
|
||||
if (args.length > 0) {
|
||||
fieldObj.args = args.reduce<ArgMap<string>>((r, a) => {
|
||||
const concreteType = a.type.toString()
|
||||
const typename = getNamedType(a.type).name
|
||||
r[a.name] = [typename]
|
||||
if (typename !== concreteType) {
|
||||
r[a.name]?.push(concreteType)
|
||||
}
|
||||
return r
|
||||
}, {})
|
||||
}
|
||||
|
||||
return r
|
||||
}, {})
|
||||
|
||||
if (isInterfaceType(type) && ctx.schema) {
|
||||
ctx.schema.getPossibleTypes(type).map((t) => {
|
||||
if (!isEmpty(typeObj)) {
|
||||
typeObj[`on_${t.name}`] = { type: t.name }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (!isEmpty(typeObj)) {
|
||||
typeObj.__typename = { type: 'String' }
|
||||
}
|
||||
|
||||
// const scalar = Object.keys(type.getFields())
|
||||
// .map(f => type.getFields()[f])
|
||||
// .filter(f => isScalarType(getNamedType(f.type)) || isEnumType(getNamedType(f.type)))
|
||||
// .map(f => f.name)
|
||||
|
||||
// if (scalar.length > 0) typeObj.scalar = scalar
|
||||
|
||||
return typeObj
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// @ts-nocheck
|
||||
import {
|
||||
ArgMap,
|
||||
CompressedField,
|
||||
CompressedFieldMap,
|
||||
CompressedTypeMap,
|
||||
TypeMap,
|
||||
} from '../../runtime/types'
|
||||
import {
|
||||
GraphQLSchema,
|
||||
isEnumType,
|
||||
isInputObjectType,
|
||||
isInterfaceType,
|
||||
isObjectType,
|
||||
isScalarType,
|
||||
isUnionType,
|
||||
} from 'graphql'
|
||||
import { excludedTypes } from '../common/excludedTypes'
|
||||
import { RenderContext } from '../common/RenderContext'
|
||||
import { objectType } from './objectType'
|
||||
import { unionType } from './unionType'
|
||||
|
||||
export const renderTypeMap = (schema: GraphQLSchema, ctx: RenderContext) => {
|
||||
// remove fields key,
|
||||
// remove the Type.type and Type.args, replace with [type, args]
|
||||
// reverse args.{name}
|
||||
// Args type is deduced and added only when the concrete type is different from type name, remove the scalar field and replace with a top level scalars array field.
|
||||
const result: TypeMap<string> = {
|
||||
scalars: [],
|
||||
types: {},
|
||||
}
|
||||
|
||||
Object.keys(schema.getTypeMap())
|
||||
.filter((t) => !excludedTypes.includes(t))
|
||||
.map((t) => schema.getTypeMap()[t])
|
||||
.map((t) => {
|
||||
if (isObjectType(t) || isInterfaceType(t) || isInputObjectType(t))
|
||||
result.types[t.name] = objectType(t, ctx)
|
||||
else if (isUnionType(t)) result.types[t.name] = unionType(t, ctx)
|
||||
else if (isScalarType(t) || isEnumType(t)) {
|
||||
result.scalars.push(t.name)
|
||||
result.types[t.name] = {}
|
||||
}
|
||||
})
|
||||
|
||||
// change names of query, mutation on schemas that chose different names (hasura)
|
||||
const q = schema.getQueryType()
|
||||
if (q?.name && q?.name !== 'Query') {
|
||||
delete result.types[q.name]
|
||||
result.types.Query = objectType(q, ctx)
|
||||
// result.Query.name = 'Query'
|
||||
}
|
||||
|
||||
const m = schema.getMutationType()
|
||||
if (m?.name && m.name !== 'Mutation') {
|
||||
delete result.types[m.name]
|
||||
result.types.Mutation = objectType(m, ctx)
|
||||
// result.Mutation.name = 'Mutation'
|
||||
}
|
||||
|
||||
const s = schema.getSubscriptionType()
|
||||
if (s?.name && s.name !== 'Subscription') {
|
||||
delete result.types[s.name]
|
||||
result.types.Subscription = objectType(s, ctx)
|
||||
// result.Subscription.name = 'Subscription'
|
||||
}
|
||||
|
||||
ctx.addCodeBlock(
|
||||
JSON.stringify(replaceTypeNamesWithIndexes(result), null, 4),
|
||||
)
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
export function replaceTypeNamesWithIndexes(
|
||||
typeMap: TypeMap<string>,
|
||||
): CompressedTypeMap<number> {
|
||||
const nameToIndex: Record<string, number> = Object.assign(
|
||||
{},
|
||||
...Object.keys(typeMap.types).map((k, i) => ({ [k]: i })),
|
||||
)
|
||||
const scalars = typeMap.scalars.map((x) => nameToIndex[x])
|
||||
const types = Object.assign(
|
||||
{},
|
||||
...Object.keys(typeMap.types || {}).map((k) => {
|
||||
const type = typeMap.types[k]
|
||||
const fieldsMap = type || {}
|
||||
// processFields(fields, indexToName)
|
||||
const fields = Object.assign(
|
||||
{},
|
||||
...Object.keys(fieldsMap).map(
|
||||
(f): CompressedFieldMap<number> => {
|
||||
const content = fieldsMap[f] as any
|
||||
if (!content) {
|
||||
throw new Error('no content in field ' + f)
|
||||
}
|
||||
const [typeName, args] = [content.type, content.args]
|
||||
const res: CompressedField<number> = [
|
||||
typeName ? nameToIndex[typeName] : -1,
|
||||
]
|
||||
if (args) {
|
||||
res[1] = Object.assign(
|
||||
{},
|
||||
...Object.keys(args || {}).map((k) => {
|
||||
const arg = args?.[k]
|
||||
if (!arg) {
|
||||
throw new Error(
|
||||
'replaceTypeNamesWithIndexes: no arg for ' +
|
||||
k,
|
||||
)
|
||||
}
|
||||
return {
|
||||
[k]: [
|
||||
nameToIndex[arg[0]],
|
||||
...arg.slice(1),
|
||||
],
|
||||
} as ArgMap<number>
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
[f]: res,
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
return {
|
||||
[k]: {
|
||||
...fields,
|
||||
},
|
||||
}
|
||||
}),
|
||||
)
|
||||
return {
|
||||
scalars,
|
||||
types,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// @ts-nocheck
|
||||
import { GraphQLEnumType, GraphQLScalarType } from 'graphql'
|
||||
import { RenderContext } from '../common/RenderContext'
|
||||
import { Type } from '../../runtime/types'
|
||||
|
||||
export const scalarType = (
|
||||
type: GraphQLScalarType | GraphQLEnumType,
|
||||
_: RenderContext,
|
||||
): Type<string> => ({})
|
||||
@@ -0,0 +1,7 @@
|
||||
// @ts-nocheck
|
||||
export function isEmpty(x) {
|
||||
if (!x) {
|
||||
return true
|
||||
}
|
||||
return Object.keys(x).length === 0
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// @ts-nocheck
|
||||
import { GraphQLUnionType } from 'graphql'
|
||||
import { RenderContext } from '../common/RenderContext'
|
||||
import { FieldMap } from '../../runtime/types'
|
||||
import uniq from 'lodash/uniq'
|
||||
|
||||
export const unionType = (type: GraphQLUnionType, _: RenderContext) => {
|
||||
const types = type.getTypes()
|
||||
const typeObj: FieldMap<string> = types.reduce<FieldMap<string>>((r, t) => {
|
||||
r[`on_${t.name}`] = { type: t.name }
|
||||
return r
|
||||
}, {})
|
||||
|
||||
const commonInterfaces = uniq(types.map((x) => x.getInterfaces()).flat())
|
||||
commonInterfaces.forEach((t) => {
|
||||
typeObj[`on_${t.name}`] = { type: t.name }
|
||||
})
|
||||
|
||||
typeObj.__typename = { type: 'String' }
|
||||
|
||||
return typeObj
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// The generated client ships its own copy of the genql runtime. Upstream genql
|
||||
// reads these files from disk at generation time; we import them as raw text so
|
||||
// they are bundled into this package and copied verbatim into each generated
|
||||
// client's `runtime/` folder (with a `// @ts-nocheck` header, as genql does).
|
||||
import batcher from './runtime/batcher.ts?raw';
|
||||
import createClient from './runtime/createClient.ts?raw';
|
||||
import error from './runtime/error.ts?raw';
|
||||
import fetcher from './runtime/fetcher.ts?raw';
|
||||
import generateGraphqlOperation from './runtime/generateGraphqlOperation.ts?raw';
|
||||
import index from './runtime/index.ts?raw';
|
||||
import linkTypeMap from './runtime/linkTypeMap.ts?raw';
|
||||
import typeSelection from './runtime/typeSelection.ts?raw';
|
||||
import types from './runtime/types.ts?raw';
|
||||
|
||||
export const RUNTIME_TEMPLATE_FILES: { name: string; content: string }[] = [
|
||||
{ name: 'batcher.ts', content: batcher },
|
||||
{ name: 'createClient.ts', content: createClient },
|
||||
{ name: 'error.ts', content: error },
|
||||
{ name: 'fetcher.ts', content: fetcher },
|
||||
{ name: 'generateGraphqlOperation.ts', content: generateGraphqlOperation },
|
||||
{ name: 'index.ts', content: index },
|
||||
{ name: 'linkTypeMap.ts', content: linkTypeMap },
|
||||
{ name: 'typeSelection.ts', content: typeSelection },
|
||||
{ name: 'types.ts', content: types },
|
||||
];
|
||||
@@ -0,0 +1,270 @@
|
||||
import type { GraphqlOperation } from './generateGraphqlOperation'
|
||||
import { GenqlError } from './error'
|
||||
|
||||
type Variables = Record<string, any>
|
||||
|
||||
type QueryError = Error & {
|
||||
message: string
|
||||
|
||||
locations?: Array<{
|
||||
line: number
|
||||
column: number
|
||||
}>
|
||||
path?: any
|
||||
rid: string
|
||||
details?: Record<string, any>
|
||||
}
|
||||
type Result = {
|
||||
data: Record<string, any>
|
||||
errors: Array<QueryError>
|
||||
}
|
||||
type Fetcher = (
|
||||
batchedQuery: GraphqlOperation | Array<GraphqlOperation>,
|
||||
) => Promise<Array<Result>>
|
||||
type Options = {
|
||||
batchInterval?: number
|
||||
shouldBatch?: boolean
|
||||
maxBatchSize?: number
|
||||
}
|
||||
type Queue = Array<{
|
||||
request: GraphqlOperation
|
||||
resolve: (...args: Array<any>) => any
|
||||
reject: (...args: Array<any>) => any
|
||||
}>
|
||||
|
||||
/**
|
||||
* takes a list of requests (queue) and batches them into a single server request.
|
||||
* It will then resolve each individual requests promise with the appropriate data.
|
||||
* @private
|
||||
* @param {QueryBatcher} client - the client to use
|
||||
* @param {Queue} queue - the list of requests to batch
|
||||
*/
|
||||
function dispatchQueueBatch(client: QueryBatcher, queue: Queue): void {
|
||||
let batchedQuery: any = queue.map((item) => item.request)
|
||||
|
||||
if (batchedQuery.length === 1) {
|
||||
batchedQuery = batchedQuery[0]
|
||||
}
|
||||
|
||||
client.fetcher(batchedQuery).then((responses: any) => {
|
||||
if (queue.length === 1 && !Array.isArray(responses)) {
|
||||
if (responses.errors && responses.errors.length) {
|
||||
queue[0].reject(
|
||||
new GenqlError(responses.errors, responses.data),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
queue[0].resolve(responses)
|
||||
return
|
||||
} else if (responses.length !== queue.length) {
|
||||
throw new Error('response length did not match query length')
|
||||
}
|
||||
|
||||
for (let i = 0; i < queue.length; i++) {
|
||||
if (responses[i].errors && responses[i].errors.length) {
|
||||
queue[i].reject(
|
||||
new GenqlError(responses[i].errors, responses[i].data),
|
||||
)
|
||||
} else {
|
||||
queue[i].resolve(responses[i])
|
||||
}
|
||||
}
|
||||
}).catch((error: any) => {
|
||||
// Reject every queued request if the batched fetch fails (e.g. network
|
||||
// error), otherwise callers hang forever and the rejection is unhandled.
|
||||
for (const item of queue) {
|
||||
item.reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a list of requests to batch according to max batch size.
|
||||
* @private
|
||||
* @param {QueryBatcher} client - the client to create list of requests from from
|
||||
* @param {Options} options - the options for the batch
|
||||
*/
|
||||
function dispatchQueue(client: QueryBatcher, options: Options): void {
|
||||
const queue = client._queue
|
||||
const maxBatchSize = options.maxBatchSize || 0
|
||||
client._queue = []
|
||||
|
||||
if (maxBatchSize > 0 && maxBatchSize < queue.length) {
|
||||
for (let i = 0; i < queue.length / maxBatchSize; i++) {
|
||||
dispatchQueueBatch(
|
||||
client,
|
||||
queue.slice(i * maxBatchSize, (i + 1) * maxBatchSize),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
dispatchQueueBatch(client, queue)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Create a batcher client.
|
||||
* @param {Fetcher} fetcher - A function that can handle the network requests to graphql endpoint
|
||||
* @param {Options} options - the options to be used by client
|
||||
* @param {boolean} options.shouldBatch - should the client batch requests. (default true)
|
||||
* @param {integer} options.batchInterval - duration (in MS) of each batch window. (default 6)
|
||||
* @param {integer} options.maxBatchSize - max number of requests in a batch. (default 0)
|
||||
* @param {boolean} options.defaultHeaders - default headers to include with every request
|
||||
*
|
||||
* @example
|
||||
* const fetcher = batchedQuery => fetch('path/to/graphql', {
|
||||
* method: 'post',
|
||||
* headers: {
|
||||
* Accept: 'application/json',
|
||||
* 'Content-Type': 'application/json',
|
||||
* },
|
||||
* body: JSON.stringify(batchedQuery),
|
||||
* credentials: 'include',
|
||||
* })
|
||||
* .then(response => response.json())
|
||||
*
|
||||
* const client = new QueryBatcher(fetcher, { maxBatchSize: 10 })
|
||||
*/
|
||||
|
||||
export class QueryBatcher {
|
||||
fetcher: Fetcher
|
||||
_options: Options
|
||||
_queue: Queue
|
||||
|
||||
constructor(
|
||||
fetcher: Fetcher,
|
||||
{
|
||||
batchInterval = 6,
|
||||
shouldBatch = true,
|
||||
maxBatchSize = 0,
|
||||
}: Options = {},
|
||||
) {
|
||||
this.fetcher = fetcher
|
||||
this._options = {
|
||||
batchInterval,
|
||||
shouldBatch,
|
||||
maxBatchSize,
|
||||
}
|
||||
this._queue = []
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch will send a graphql request and return the parsed json.
|
||||
* @param {string} query - the graphql query.
|
||||
* @param {Variables} variables - any variables you wish to inject as key/value pairs.
|
||||
* @param {[string]} operationName - the graphql operationName.
|
||||
* @param {Options} overrides - the client options overrides.
|
||||
*
|
||||
* @return {promise} resolves to parsed json of server response
|
||||
*
|
||||
* @example
|
||||
* client.fetch(`
|
||||
* query getHuman($id: ID!) {
|
||||
* human(id: $id) {
|
||||
* name
|
||||
* height
|
||||
* }
|
||||
* }
|
||||
* `, { id: "1001" }, 'getHuman')
|
||||
* .then(human => {
|
||||
* // do something with human
|
||||
* console.log(human);
|
||||
* });
|
||||
*/
|
||||
fetch(
|
||||
query: string,
|
||||
variables?: Variables,
|
||||
operationName?: string,
|
||||
overrides: Options = {},
|
||||
): Promise<Result> {
|
||||
const request: GraphqlOperation = {
|
||||
query,
|
||||
}
|
||||
const options = Object.assign({}, this._options, overrides)
|
||||
|
||||
if (variables) {
|
||||
request.variables = variables
|
||||
}
|
||||
|
||||
if (operationName) {
|
||||
request.operationName = operationName
|
||||
}
|
||||
|
||||
const promise = new Promise<Result>((resolve, reject) => {
|
||||
this._queue.push({
|
||||
request,
|
||||
resolve,
|
||||
reject,
|
||||
})
|
||||
|
||||
if (this._queue.length === 1) {
|
||||
if (options.shouldBatch) {
|
||||
setTimeout(
|
||||
() => dispatchQueue(this, options),
|
||||
options.batchInterval,
|
||||
)
|
||||
} else {
|
||||
dispatchQueue(this, options)
|
||||
}
|
||||
}
|
||||
})
|
||||
return promise
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch will send a graphql request and return the parsed json.
|
||||
* @param {string} query - the graphql query.
|
||||
* @param {Variables} variables - any variables you wish to inject as key/value pairs.
|
||||
* @param {[string]} operationName - the graphql operationName.
|
||||
* @param {Options} overrides - the client options overrides.
|
||||
*
|
||||
* @return {Promise<Array<Result>>} resolves to parsed json of server response
|
||||
*
|
||||
* @example
|
||||
* client.forceFetch(`
|
||||
* query getHuman($id: ID!) {
|
||||
* human(id: $id) {
|
||||
* name
|
||||
* height
|
||||
* }
|
||||
* }
|
||||
* `, { id: "1001" }, 'getHuman')
|
||||
* .then(human => {
|
||||
* // do something with human
|
||||
* console.log(human);
|
||||
* });
|
||||
*/
|
||||
forceFetch(
|
||||
query: string,
|
||||
variables?: Variables,
|
||||
operationName?: string,
|
||||
overrides: Options = {},
|
||||
): Promise<Result> {
|
||||
const request: GraphqlOperation = {
|
||||
query,
|
||||
}
|
||||
const options = Object.assign({}, this._options, overrides, {
|
||||
shouldBatch: false,
|
||||
})
|
||||
|
||||
if (variables) {
|
||||
request.variables = variables
|
||||
}
|
||||
|
||||
if (operationName) {
|
||||
request.operationName = operationName
|
||||
}
|
||||
|
||||
const promise = new Promise<Result>((resolve, reject) => {
|
||||
const client = new QueryBatcher(this.fetcher, this._options)
|
||||
client._queue = [
|
||||
{
|
||||
request,
|
||||
resolve,
|
||||
reject,
|
||||
},
|
||||
]
|
||||
dispatchQueue(client, options)
|
||||
})
|
||||
return promise
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
|
||||
import { type BatchOptions, createFetcher } from './fetcher'
|
||||
import type { ExecutionResult, LinkedType } from './types'
|
||||
import {
|
||||
generateGraphqlOperation,
|
||||
type GraphqlOperation,
|
||||
} from './generateGraphqlOperation'
|
||||
|
||||
export type Headers =
|
||||
| HeadersInit
|
||||
| (() => HeadersInit)
|
||||
| (() => Promise<HeadersInit>)
|
||||
|
||||
export type BaseFetcher = (
|
||||
operation: GraphqlOperation | GraphqlOperation[],
|
||||
) => Promise<ExecutionResult | ExecutionResult[]>
|
||||
|
||||
export type ClientOptions = Omit<RequestInit, 'body' | 'headers'> & {
|
||||
url?: string
|
||||
batch?: BatchOptions | boolean
|
||||
fetcher?: BaseFetcher
|
||||
fetch?: Function
|
||||
headers?: Headers
|
||||
}
|
||||
|
||||
export const createClient = ({
|
||||
queryRoot,
|
||||
mutationRoot,
|
||||
subscriptionRoot,
|
||||
...options
|
||||
}: ClientOptions & {
|
||||
queryRoot?: LinkedType
|
||||
mutationRoot?: LinkedType
|
||||
subscriptionRoot?: LinkedType
|
||||
}) => {
|
||||
const fetcher = createFetcher(options)
|
||||
const client: {
|
||||
query?: Function
|
||||
mutation?: Function
|
||||
} = {}
|
||||
|
||||
if (queryRoot) {
|
||||
client.query = (request: any) => {
|
||||
if (!queryRoot) throw new Error('queryRoot argument is missing')
|
||||
|
||||
const resultPromise = fetcher(
|
||||
generateGraphqlOperation('query', queryRoot, request),
|
||||
)
|
||||
|
||||
return resultPromise
|
||||
}
|
||||
}
|
||||
if (mutationRoot) {
|
||||
client.mutation = (request: any) => {
|
||||
if (!mutationRoot)
|
||||
throw new Error('mutationRoot argument is missing')
|
||||
|
||||
const resultPromise = fetcher(
|
||||
generateGraphqlOperation('mutation', mutationRoot, request),
|
||||
)
|
||||
|
||||
return resultPromise
|
||||
}
|
||||
}
|
||||
|
||||
return client as any
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
export class GenqlError extends Error {
|
||||
errors: Array<GraphqlError> = []
|
||||
/**
|
||||
* Partial data returned by the server
|
||||
*/
|
||||
data?: any
|
||||
constructor(errors: any[], data: any) {
|
||||
let message = Array.isArray(errors)
|
||||
? errors.map((x) => x?.message || '').join('\n')
|
||||
: ''
|
||||
if (!message) {
|
||||
message = 'GraphQL error'
|
||||
}
|
||||
super(message)
|
||||
this.errors = errors
|
||||
this.data = data
|
||||
}
|
||||
}
|
||||
|
||||
interface GraphqlError {
|
||||
message: string
|
||||
locations?: Array<{
|
||||
line: number
|
||||
column: number
|
||||
}>
|
||||
path?: string[]
|
||||
extensions?: Record<string, any>
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { QueryBatcher } from './batcher'
|
||||
|
||||
import type { ClientOptions } from './createClient'
|
||||
import type { GraphqlOperation } from './generateGraphqlOperation'
|
||||
import { GenqlError } from './error'
|
||||
|
||||
export interface Fetcher {
|
||||
(gql: GraphqlOperation): Promise<any>
|
||||
}
|
||||
|
||||
export type BatchOptions = {
|
||||
batchInterval?: number // ms
|
||||
maxBatchSize?: number
|
||||
}
|
||||
|
||||
const DEFAULT_BATCH_OPTIONS = {
|
||||
maxBatchSize: 10,
|
||||
batchInterval: 40,
|
||||
}
|
||||
|
||||
export const createFetcher = ({
|
||||
url,
|
||||
headers = {},
|
||||
fetcher,
|
||||
fetch: _fetch,
|
||||
batch = false,
|
||||
...rest
|
||||
}: ClientOptions): Fetcher => {
|
||||
if (!url && !fetcher) {
|
||||
throw new Error('url or fetcher is required')
|
||||
}
|
||||
if (!fetcher) {
|
||||
fetcher = async (body) => {
|
||||
let headersObject =
|
||||
typeof headers == 'function' ? await headers() : headers
|
||||
headersObject = headersObject || {}
|
||||
if (typeof fetch === 'undefined' && !_fetch) {
|
||||
throw new Error(
|
||||
'Global `fetch` function is not available, pass a fetch polyfill to Genql `createClient`',
|
||||
)
|
||||
}
|
||||
let fetchImpl = _fetch || fetch
|
||||
const res = await fetchImpl(url!, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...headersObject,
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
...rest,
|
||||
})
|
||||
if (!res.ok) {
|
||||
throw new Error(`${res.statusText}: ${await res.text()}`)
|
||||
}
|
||||
const json = await res.json()
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
if (!batch) {
|
||||
return async (body) => {
|
||||
const json = await fetcher!(body)
|
||||
if (Array.isArray(json)) {
|
||||
return json.map((json) => {
|
||||
if (json?.errors?.length) {
|
||||
throw new GenqlError(json.errors || [], json.data)
|
||||
}
|
||||
return json.data
|
||||
})
|
||||
} else {
|
||||
if (json?.errors?.length) {
|
||||
throw new GenqlError(json.errors || [], json.data)
|
||||
}
|
||||
return json.data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const batcher = new QueryBatcher(
|
||||
async (batchedQuery) => {
|
||||
// console.log(batchedQuery) // [{ query: 'query{user{age}}', variables: {} }, ...]
|
||||
const json = await fetcher!(batchedQuery)
|
||||
return json as any
|
||||
},
|
||||
batch === true ? DEFAULT_BATCH_OPTIONS : batch,
|
||||
)
|
||||
|
||||
return async ({ query, variables }) => {
|
||||
const json = await batcher.fetch(query, variables)
|
||||
if (json?.data) {
|
||||
return json.data
|
||||
}
|
||||
throw new Error(
|
||||
'Genql batch fetcher returned unexpected result ' + JSON.stringify(json),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
import type { LinkedField, LinkedType } from './types'
|
||||
|
||||
export interface Args {
|
||||
[arg: string]: any | undefined
|
||||
}
|
||||
|
||||
export interface Fields {
|
||||
[field: string]: Request
|
||||
}
|
||||
|
||||
export type Request = boolean | number | Fields
|
||||
|
||||
export interface Variables {
|
||||
[name: string]: {
|
||||
value: any
|
||||
typing: [LinkedType, string]
|
||||
}
|
||||
}
|
||||
|
||||
export interface Context {
|
||||
root: LinkedType
|
||||
varCounter: number
|
||||
variables: Variables
|
||||
fragmentCounter: number
|
||||
fragments: string[]
|
||||
}
|
||||
|
||||
export interface GraphqlOperation {
|
||||
query: string
|
||||
variables?: { [name: string]: any }
|
||||
operationName?: string
|
||||
}
|
||||
|
||||
const parseRequest = (
|
||||
request: Request | undefined,
|
||||
ctx: Context,
|
||||
path: string[],
|
||||
): string => {
|
||||
if (typeof request === 'object' && '__args' in request) {
|
||||
const args: any = request.__args
|
||||
let fields: Request | undefined = { ...request }
|
||||
delete fields.__args
|
||||
const argNames = Object.keys(args)
|
||||
|
||||
if (argNames.length === 0) {
|
||||
return parseRequest(fields, ctx, path)
|
||||
}
|
||||
|
||||
const field = getFieldFromPath(ctx.root, path)
|
||||
|
||||
const argStrings = argNames.map((argName) => {
|
||||
ctx.varCounter++
|
||||
const varName = `v${ctx.varCounter}`
|
||||
|
||||
const typing = field.args && field.args[argName] // typeMap used here, .args
|
||||
|
||||
if (!typing) {
|
||||
throw new Error(
|
||||
`no typing defined for argument \`${argName}\` in path \`${path.join(
|
||||
'.',
|
||||
)}\``,
|
||||
)
|
||||
}
|
||||
|
||||
ctx.variables[varName] = {
|
||||
value: args[argName],
|
||||
typing,
|
||||
}
|
||||
|
||||
return `${argName}:$${varName}`
|
||||
})
|
||||
return `(${argStrings})${parseRequest(fields, ctx, path)}`
|
||||
} else if (typeof request === 'object' && Object.keys(request).length > 0) {
|
||||
const fields = request
|
||||
const fieldNames = Object.keys(fields).filter((k) => Boolean(fields[k]))
|
||||
|
||||
if (fieldNames.length === 0) {
|
||||
throw new Error(
|
||||
`field selection should not be empty: ${path.join('.')}`,
|
||||
)
|
||||
}
|
||||
|
||||
const type =
|
||||
path.length > 0 ? getFieldFromPath(ctx.root, path).type : ctx.root
|
||||
const scalarFields = type.scalar
|
||||
|
||||
let scalarFieldsFragment: string | undefined
|
||||
|
||||
if (fieldNames.includes('__scalar')) {
|
||||
const falsyFieldNames = new Set(
|
||||
Object.keys(fields).filter((k) => !Boolean(fields[k])),
|
||||
)
|
||||
if (scalarFields?.length) {
|
||||
ctx.fragmentCounter++
|
||||
scalarFieldsFragment = `f${ctx.fragmentCounter}`
|
||||
|
||||
ctx.fragments.push(
|
||||
`fragment ${scalarFieldsFragment} on ${
|
||||
type.name
|
||||
}{${scalarFields
|
||||
.filter((f) => !falsyFieldNames.has(f))
|
||||
.join(',')}}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const fieldsSelection = fieldNames
|
||||
.filter((f) => !['__scalar', '__name'].includes(f))
|
||||
.map((f) => {
|
||||
const parsed = parseRequest(fields[f], ctx, [...path, f])
|
||||
|
||||
if (f.startsWith('on_')) {
|
||||
ctx.fragmentCounter++
|
||||
const implementationFragment = `f${ctx.fragmentCounter}`
|
||||
|
||||
const typeMatch = f.match(/^on_(.+)/)
|
||||
|
||||
if (!typeMatch || !typeMatch[1])
|
||||
throw new Error('match failed')
|
||||
|
||||
ctx.fragments.push(
|
||||
`fragment ${implementationFragment} on ${typeMatch[1]}${parsed}`,
|
||||
)
|
||||
|
||||
return `...${implementationFragment}`
|
||||
} else {
|
||||
return `${f}${parsed}`
|
||||
}
|
||||
})
|
||||
.concat(scalarFieldsFragment ? [`...${scalarFieldsFragment}`] : [])
|
||||
.join(',')
|
||||
|
||||
return `{${fieldsSelection}}`
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export const generateGraphqlOperation = (
|
||||
operation: 'query' | 'mutation' | 'subscription',
|
||||
root: LinkedType,
|
||||
fields?: Fields,
|
||||
): GraphqlOperation => {
|
||||
const ctx: Context = {
|
||||
root: root,
|
||||
varCounter: 0,
|
||||
variables: {},
|
||||
fragmentCounter: 0,
|
||||
fragments: [],
|
||||
}
|
||||
const result = parseRequest(fields, ctx, [])
|
||||
|
||||
const varNames = Object.keys(ctx.variables)
|
||||
|
||||
const varsString =
|
||||
varNames.length > 0
|
||||
? `(${varNames.map((v) => {
|
||||
const variableType = ctx.variables[v].typing[1]
|
||||
return `$${v}:${variableType}`
|
||||
})})`
|
||||
: ''
|
||||
|
||||
const operationName = fields?.__name || ''
|
||||
|
||||
return {
|
||||
query: [
|
||||
`${operation} ${operationName}${varsString}${result}`,
|
||||
...ctx.fragments,
|
||||
].join(','),
|
||||
variables: Object.keys(ctx.variables).reduce<{ [name: string]: any }>(
|
||||
(r, v) => {
|
||||
r[v] = ctx.variables[v].value
|
||||
return r
|
||||
},
|
||||
{},
|
||||
),
|
||||
...(operationName ? { operationName: operationName.toString() } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
export const getFieldFromPath = (
|
||||
root: LinkedType | undefined,
|
||||
path: string[],
|
||||
) => {
|
||||
let current: LinkedField | undefined
|
||||
|
||||
if (!root) throw new Error('root type is not provided')
|
||||
|
||||
if (path.length === 0) throw new Error(`path is empty`)
|
||||
|
||||
path.forEach((f) => {
|
||||
const type = current ? current.type : root
|
||||
|
||||
if (!type.fields)
|
||||
throw new Error(`type \`${type.name}\` does not have fields`)
|
||||
|
||||
const possibleTypes = Object.keys(type.fields)
|
||||
.filter((i) => i.startsWith('on_'))
|
||||
.reduce(
|
||||
(types, fieldName) => {
|
||||
const field = type.fields && type.fields[fieldName]
|
||||
if (field) types.push(field.type)
|
||||
return types
|
||||
},
|
||||
[type],
|
||||
)
|
||||
|
||||
let field: LinkedField | null = null
|
||||
|
||||
possibleTypes.forEach((type) => {
|
||||
const found = type.fields && type.fields[f]
|
||||
if (found) field = found
|
||||
})
|
||||
|
||||
if (!field)
|
||||
throw new Error(
|
||||
`type \`${type.name}\` does not have a field \`${f}\``,
|
||||
)
|
||||
|
||||
current = field
|
||||
})
|
||||
|
||||
return current as LinkedField
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export { createClient } from './createClient'
|
||||
export type { ClientOptions } from './createClient'
|
||||
export type { FieldsSelection } from './typeSelection'
|
||||
export { generateGraphqlOperation } from './generateGraphqlOperation'
|
||||
export type { GraphqlOperation } from './generateGraphqlOperation'
|
||||
export { linkTypeMap } from './linkTypeMap'
|
||||
// export { Observable } from 'zen-observable-ts'
|
||||
export { createFetcher } from './fetcher'
|
||||
export { GenqlError } from './error'
|
||||
export const everything = {
|
||||
__scalar: true,
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import type {
|
||||
CompressedType,
|
||||
CompressedTypeMap,
|
||||
LinkedArgMap,
|
||||
LinkedField,
|
||||
LinkedType,
|
||||
LinkedTypeMap,
|
||||
} from './types'
|
||||
|
||||
export interface PartialLinkedFieldMap {
|
||||
[field: string]: {
|
||||
type: string
|
||||
args?: LinkedArgMap
|
||||
}
|
||||
}
|
||||
|
||||
export const linkTypeMap = (
|
||||
typeMap: CompressedTypeMap<number>,
|
||||
): LinkedTypeMap => {
|
||||
const indexToName: Record<number, string> = Object.assign(
|
||||
{},
|
||||
...Object.keys(typeMap.types).map((k, i) => ({ [i]: k })),
|
||||
)
|
||||
|
||||
let intermediaryTypeMap = Object.assign(
|
||||
{},
|
||||
...Object.keys(typeMap.types || {}).map(
|
||||
(k): Record<string, LinkedType> => {
|
||||
const type: CompressedType = typeMap.types[k]!
|
||||
const fields = type || {}
|
||||
return {
|
||||
[k]: {
|
||||
name: k,
|
||||
// type scalar properties
|
||||
scalar: Object.keys(fields).filter((f) => {
|
||||
const [type] = fields[f] || []
|
||||
return type && typeMap.scalars.includes(type)
|
||||
}),
|
||||
// fields with corresponding `type` and `args`
|
||||
fields: Object.assign(
|
||||
{},
|
||||
...Object.keys(fields).map(
|
||||
(f): PartialLinkedFieldMap => {
|
||||
const [typeIndex, args] = fields[f] || []
|
||||
if (typeIndex == null) {
|
||||
return {}
|
||||
}
|
||||
return {
|
||||
[f]: {
|
||||
// replace index with type name
|
||||
type: indexToName[typeIndex],
|
||||
args: Object.assign(
|
||||
{},
|
||||
...Object.keys(args || {}).map(
|
||||
(k) => {
|
||||
// if argTypeString == argTypeName, argTypeString is missing, need to readd it
|
||||
if (!args || !args[k]) {
|
||||
return
|
||||
}
|
||||
const [
|
||||
argTypeName,
|
||||
argTypeString,
|
||||
] = args[k] as any
|
||||
return {
|
||||
[k]: [
|
||||
indexToName[
|
||||
argTypeName
|
||||
],
|
||||
argTypeString ||
|
||||
indexToName[
|
||||
argTypeName
|
||||
],
|
||||
],
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
const res = resolveConcreteTypes(intermediaryTypeMap)
|
||||
return res
|
||||
}
|
||||
|
||||
// replace typename with concrete type
|
||||
export const resolveConcreteTypes = (linkedTypeMap: LinkedTypeMap) => {
|
||||
Object.keys(linkedTypeMap).forEach((typeNameFromKey) => {
|
||||
const type: LinkedType = linkedTypeMap[typeNameFromKey]!
|
||||
// type.name = typeNameFromKey
|
||||
if (!type.fields) {
|
||||
return
|
||||
}
|
||||
|
||||
const fields = type.fields
|
||||
|
||||
Object.keys(fields).forEach((f) => {
|
||||
const field: LinkedField = fields[f]!
|
||||
|
||||
if (field.args) {
|
||||
const args = field.args
|
||||
Object.keys(args).forEach((key) => {
|
||||
const arg = args[key]
|
||||
|
||||
if (arg) {
|
||||
const [typeName] = arg
|
||||
|
||||
if (typeof typeName === 'string') {
|
||||
if (!linkedTypeMap[typeName]) {
|
||||
linkedTypeMap[typeName] = { name: typeName }
|
||||
}
|
||||
|
||||
arg[0] = linkedTypeMap[typeName]!
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const typeName = field.type as LinkedType | string
|
||||
|
||||
if (typeof typeName === 'string') {
|
||||
if (!linkedTypeMap[typeName]) {
|
||||
linkedTypeMap[typeName] = { name: typeName }
|
||||
}
|
||||
|
||||
field.type = linkedTypeMap[typeName]!
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return linkedTypeMap
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
// SOME THINGS TO KNOW BEFORE DIVING IN
|
||||
/*
|
||||
0. DST is the request type, SRC is the response type
|
||||
|
||||
1. FieldsSelection uses an object because currently is impossible to make recursive types
|
||||
|
||||
2. FieldsSelection is a recursive type that makes a type based on request type and fields
|
||||
|
||||
3. HandleObject handles object types
|
||||
|
||||
4. Handle__scalar adds all scalar properties excluding non scalar props
|
||||
*/
|
||||
|
||||
export type FieldsSelection<SRC extends Anify<DST> | undefined, DST> = {
|
||||
scalar: SRC
|
||||
union: Handle__isUnion<SRC, DST>
|
||||
object: HandleObject<SRC, DST>
|
||||
array: SRC extends Nil
|
||||
? never
|
||||
: SRC extends (infer T)[]
|
||||
? Array<FieldsSelection<T, DST>>
|
||||
: never
|
||||
__scalar: Handle__scalar<SRC, DST>
|
||||
never: never
|
||||
}[DST extends Nil
|
||||
? 'never'
|
||||
: SRC extends Nil
|
||||
? 'never'
|
||||
: DST extends false | 0
|
||||
? 'never'
|
||||
: SRC extends Scalar
|
||||
? 'scalar'
|
||||
: SRC extends any[]
|
||||
? 'array'
|
||||
: SRC extends { __isUnion?: any }
|
||||
? 'union'
|
||||
: DST extends { __scalar?: any }
|
||||
? '__scalar'
|
||||
: DST extends {}
|
||||
? 'object'
|
||||
: 'never']
|
||||
|
||||
type HandleObject<SRC extends Anify<DST>, DST> = SRC extends Nil
|
||||
? never
|
||||
: Pick<
|
||||
{
|
||||
// using keyof SRC to maintain ?: relations of SRC type
|
||||
[Key in keyof SRC]: Key extends keyof DST
|
||||
? FieldsSelection<
|
||||
NonNullable<SRC[Key]>,
|
||||
NonNullable<DST[Key]>
|
||||
>
|
||||
: SRC[Key]
|
||||
},
|
||||
Exclude<keyof DST, FieldsToRemove>
|
||||
// {
|
||||
// // remove falsy values
|
||||
// [Key in keyof DST]: DST[Key] extends false | 0 ? never : Key
|
||||
// }[keyof DST]
|
||||
>
|
||||
|
||||
type Handle__scalar<SRC extends Anify<DST>, DST> = SRC extends Nil
|
||||
? never
|
||||
: Pick<
|
||||
// continue processing fields that are in DST, directly pass SRC type if not in DST
|
||||
{
|
||||
[Key in keyof SRC]: Key extends keyof DST
|
||||
? FieldsSelection<SRC[Key], DST[Key]>
|
||||
: SRC[Key]
|
||||
},
|
||||
// remove fields that are not scalars or are not in DST
|
||||
{
|
||||
[Key in keyof SRC]: SRC[Key] extends Nil
|
||||
? never
|
||||
: Key extends FieldsToRemove
|
||||
? never
|
||||
: SRC[Key] extends Scalar
|
||||
? Key
|
||||
: Key extends keyof DST
|
||||
? Key
|
||||
: never
|
||||
}[keyof SRC]
|
||||
>
|
||||
|
||||
type Handle__isUnion<SRC extends Anify<DST>, DST> = SRC extends Nil
|
||||
? never
|
||||
: Omit<SRC, FieldsToRemove> // just return the union type
|
||||
|
||||
type Scalar = string | number | Date | boolean | null | undefined
|
||||
|
||||
type Anify<T> = { [P in keyof T]?: any }
|
||||
|
||||
type FieldsToRemove = '__isUnion' | '__scalar' | '__name' | '__args'
|
||||
|
||||
type Nil = undefined | null
|
||||
@@ -0,0 +1,68 @@
|
||||
|
||||
export interface ExecutionResult<TData = { [key: string]: any }> {
|
||||
errors?: Array<Error>
|
||||
data?: TData | null
|
||||
}
|
||||
|
||||
export interface ArgMap<keyType = number> {
|
||||
[arg: string]: [keyType, string] | [keyType] | undefined
|
||||
}
|
||||
|
||||
export type CompressedField<keyType = number> = [
|
||||
type: keyType,
|
||||
args?: ArgMap<keyType>,
|
||||
]
|
||||
|
||||
export interface CompressedFieldMap<keyType = number> {
|
||||
[field: string]: CompressedField<keyType> | undefined
|
||||
}
|
||||
|
||||
export type CompressedType<keyType = number> = CompressedFieldMap<keyType>
|
||||
|
||||
export interface CompressedTypeMap<keyType = number> {
|
||||
scalars: Array<keyType>
|
||||
types: {
|
||||
[type: string]: CompressedType<keyType> | undefined
|
||||
}
|
||||
}
|
||||
|
||||
// normal types
|
||||
export type Field<keyType = number> = {
|
||||
type: keyType
|
||||
args?: ArgMap<keyType>
|
||||
}
|
||||
|
||||
export interface FieldMap<keyType = number> {
|
||||
[field: string]: Field<keyType> | undefined
|
||||
}
|
||||
|
||||
export type Type<keyType = number> = FieldMap<keyType>
|
||||
|
||||
export interface TypeMap<keyType = number> {
|
||||
scalars: Array<keyType>
|
||||
types: {
|
||||
[type: string]: Type<keyType> | undefined
|
||||
}
|
||||
}
|
||||
|
||||
export interface LinkedArgMap {
|
||||
[arg: string]: [LinkedType, string] | undefined
|
||||
}
|
||||
export interface LinkedField {
|
||||
type: LinkedType
|
||||
args?: LinkedArgMap
|
||||
}
|
||||
|
||||
export interface LinkedFieldMap {
|
||||
[field: string]: LinkedField | undefined
|
||||
}
|
||||
|
||||
export interface LinkedType {
|
||||
name: string
|
||||
fields?: LinkedFieldMap
|
||||
scalar?: string[]
|
||||
}
|
||||
|
||||
export interface LinkedTypeMap {
|
||||
[type: string]: LinkedType | undefined
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { GraphQLEnumType, GraphQLSchema, isEnumType } from 'graphql';
|
||||
import camelCase from 'lodash/camelCase';
|
||||
|
||||
import { type Config } from '../config';
|
||||
import { ensurePath, writeFileToPath } from '../helpers/files';
|
||||
import { renderClientEsm } from '../render/client/renderClient';
|
||||
import { excludedTypes } from '../render/common/excludedTypes';
|
||||
import { RenderContext } from '../render/common/RenderContext';
|
||||
import { renderRequestTypes } from '../render/requestTypes/renderRequestTypes';
|
||||
import { renderResponseTypes } from '../render/responseTypes/renderResponseTypes';
|
||||
import { renderSchema } from '../render/schema/renderSchema';
|
||||
import { renderTypeGuards } from '../render/typeGuards/renderTypeGuards';
|
||||
import { renderTypeMap } from '../render/typeMap/renderTypeMap';
|
||||
import { RUNTIME_TEMPLATE_FILES } from '../runtime-templates';
|
||||
|
||||
const schemaTypesFile = 'schema.ts';
|
||||
const schemaGqlFile = 'schema.graphql';
|
||||
const typeMapFileEsm = 'types.ts';
|
||||
const clientFileEsm = 'index.ts';
|
||||
|
||||
// Writes the generated client files to `config.output`. Upstream genql ran each
|
||||
// write as a concurrent listr task; we run them sequentially (file contents are
|
||||
// identical, and dropping listr removes a dependency).
|
||||
export const writeClientFiles = async (
|
||||
config: Config,
|
||||
schema: GraphQLSchema,
|
||||
): Promise<void> => {
|
||||
if (!config.output) {
|
||||
throw new Error('`output` must be defined in the config');
|
||||
}
|
||||
|
||||
const output = config.output;
|
||||
|
||||
await ensurePath([output], true);
|
||||
|
||||
const schemaGqlCtx = new RenderContext(schema, config);
|
||||
renderSchema(schema, schemaGqlCtx);
|
||||
await writeFileToPath([output, schemaGqlFile], schemaGqlCtx.toCode('graphql'));
|
||||
|
||||
await ensurePath([output, 'runtime']);
|
||||
for (const { name, content } of RUNTIME_TEMPLATE_FILES) {
|
||||
await writeFileToPath([output, 'runtime', name], '// @ts-nocheck\n' + content);
|
||||
}
|
||||
|
||||
const schemaTypesCtx = new RenderContext(schema, config);
|
||||
renderResponseTypes(schema, schemaTypesCtx);
|
||||
renderRequestTypes(schema, schemaTypesCtx);
|
||||
renderTypeGuards(schema, schemaTypesCtx);
|
||||
renderEnumsMaps(schema, schemaTypesCtx);
|
||||
await writeFileToPath(
|
||||
[output, schemaTypesFile],
|
||||
'// @ts-nocheck\n' + schemaTypesCtx.toCode('typescript'),
|
||||
);
|
||||
|
||||
const typeMapCtx = new RenderContext(schema, config);
|
||||
renderTypeMap(schema, typeMapCtx);
|
||||
await writeFileToPath(
|
||||
[output, typeMapFileEsm],
|
||||
`export default ${typeMapCtx.toCode()}`,
|
||||
);
|
||||
|
||||
const clientCtx = new RenderContext(schema, config);
|
||||
renderClientEsm(schema, clientCtx);
|
||||
await writeFileToPath(
|
||||
[output, clientFileEsm],
|
||||
'// @ts-nocheck\n' + clientCtx.toCode('typescript', true),
|
||||
);
|
||||
};
|
||||
|
||||
function renderEnumsMaps(schema: GraphQLSchema, ctx: RenderContext) {
|
||||
let typeMap = schema.getTypeMap();
|
||||
|
||||
const enums: GraphQLEnumType[] = [];
|
||||
for (const name in typeMap) {
|
||||
if (excludedTypes.includes(name)) continue;
|
||||
|
||||
const type = typeMap[name];
|
||||
|
||||
if (isEnumType(type)) {
|
||||
enums.push(type);
|
||||
}
|
||||
}
|
||||
if (enums.length === 0) return;
|
||||
|
||||
ctx.addCodeBlock(
|
||||
enums
|
||||
.map(
|
||||
(type) =>
|
||||
`export const ${camelCase('enum' + type.name)} = {\n` +
|
||||
type
|
||||
.getValues()
|
||||
.map((v) => {
|
||||
if (!v?.name) {
|
||||
return '';
|
||||
}
|
||||
return ` ${v.name}: '${v.name}' as const`;
|
||||
})
|
||||
.join(',\n') +
|
||||
`\n}\n`,
|
||||
)
|
||||
.join('\n'),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
assertValidSchema,
|
||||
buildSchema,
|
||||
GraphQLSchema,
|
||||
lexicographicSortSchema,
|
||||
} from 'graphql';
|
||||
|
||||
import { type Config } from '../config';
|
||||
|
||||
// Builds the schema from the config SDL string. The upstream genql codegen used
|
||||
// @graphql-tools' loadSchema and also supported fetching the schema from a live
|
||||
// endpoint (via undici/native-fetch). Twenty always passes an SDL string, so we
|
||||
// build it directly with graphql's buildSchema — no @graphql-tools or network.
|
||||
export const loadConfiguredSchema = async (
|
||||
config: Config,
|
||||
): Promise<GraphQLSchema> => {
|
||||
if (!config.schema) {
|
||||
throw new Error('`schema` must be defined in the config');
|
||||
}
|
||||
|
||||
const document = buildSchema(config.schema, { assumeValidSDL: true });
|
||||
|
||||
const schema = config.sortProperties
|
||||
? lexicographicSortSchema(document)
|
||||
: document;
|
||||
|
||||
// A schema without a Query root is still renderable (e.g. metadata-only),
|
||||
// matching upstream genql behaviour, so only run full validation — which
|
||||
// requires a Query root — when one is present. (Checking the root directly
|
||||
// avoids depending on a specific graphql error-message string.)
|
||||
if (schema.getQueryType()) {
|
||||
assertValidSchema(schema);
|
||||
}
|
||||
|
||||
return schema;
|
||||
};
|
||||
@@ -71,6 +71,12 @@ function dispatchQueueBatch(client: QueryBatcher, queue: Queue): void {
|
||||
queue[i].resolve(responses[i])
|
||||
}
|
||||
}
|
||||
}).catch((error: any) => {
|
||||
// Reject every queued request if the batched fetch fails (e.g. network
|
||||
// error), otherwise callers hang forever and the rejection is unhandled.
|
||||
for (const item of queue) {
|
||||
item.reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ export default defineConfig(() => {
|
||||
'node:fs/promises',
|
||||
'node:fs',
|
||||
'node:path',
|
||||
'node:os',
|
||||
],
|
||||
output: [
|
||||
{
|
||||
|
||||
@@ -91,7 +91,6 @@
|
||||
"uuid": "^13.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@genql/cli": "^3.0.3",
|
||||
"@prettier/sync": "^0.5.2",
|
||||
"@types/node": "^24.0.0",
|
||||
"@types/react": "^19.0.0",
|
||||
|
||||
@@ -7453,13 +7453,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@fastify/busboy@npm:^2.0.0":
|
||||
version: 2.1.1
|
||||
resolution: "@fastify/busboy@npm:2.1.1"
|
||||
checksum: 10c0/6f8027a8cba7f8f7b736718b013f5a38c0476eea67034c94a0d3c375e2b114366ad4419e6a6fa7ffc2ef9c6d3e0435d76dd584a7a1cbac23962fda7650b579e3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@fastify/otel@npm:0.18.0":
|
||||
version: 0.18.0
|
||||
resolution: "@fastify/otel@npm:0.18.0"
|
||||
@@ -7684,27 +7677,23 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@genql/cli@npm:^3.0.3":
|
||||
version: 3.0.5
|
||||
resolution: "@genql/cli@npm:3.0.5"
|
||||
"@genql/runtime@npm:^2.10.0":
|
||||
version: 2.10.0
|
||||
resolution: "@genql/runtime@npm:2.10.0"
|
||||
dependencies:
|
||||
"@graphql-tools/graphql-file-loader": "npm:^7.5.11"
|
||||
"@graphql-tools/load": "npm:^7.8.6"
|
||||
fs-extra: "npm:^10.1.0"
|
||||
graphql: "npm:^16.6.0"
|
||||
kleur: "npm:^4.1.5"
|
||||
listr: "npm:^0.14.3"
|
||||
lodash: "npm:^4.17.21"
|
||||
mkdirp: "npm:^0.5.1"
|
||||
native-fetch: "npm:^4.0.2"
|
||||
prettier: "npm:^2.8.0"
|
||||
qs: "npm:^6.11.0"
|
||||
rimraf: "npm:^2.6.3"
|
||||
undici: "npm:^5.18.0"
|
||||
yargs: "npm:^15.3.1"
|
||||
bin:
|
||||
genql: dist/cli.js
|
||||
checksum: 10c0/646d4da8986741a8f1b610bd8cfb5bc26cd9a856de671461e4ca87fb05443f37dd0edfb3b30ceaa34cd049077873afa07febfd4714dd2e0681fb07484e19a080
|
||||
"@types/qs": "npm:^6.9.0"
|
||||
"@types/ws": "npm:^6.0.1"
|
||||
graphql-query-batcher: "npm:^1.0.1"
|
||||
isomorphic-unfetch: "npm:^3.0.0"
|
||||
lodash: "npm:^4.17.20"
|
||||
subscriptions-transport-ws: "npm:^0.9.16"
|
||||
tslib: "npm:^2.0.0"
|
||||
utility-types: "npm:^3.10.0"
|
||||
ws: "npm:^6.1.4"
|
||||
zen-observable-ts: "npm:^0.8.21"
|
||||
peerDependencies:
|
||||
graphql: "*"
|
||||
checksum: 10c0/e2a886c2469c933681e2b0ddd6a5b7f4cb12932251ba460e3cf2db4246817da79313ea4ba9769ec7cbe53ab9c1cb81ad8fcce6a969cd241185b79398d2a4f3c6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8175,7 +8164,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-tools/graphql-file-loader@npm:^7.3.7, @graphql-tools/graphql-file-loader@npm:^7.5.0, @graphql-tools/graphql-file-loader@npm:^7.5.11":
|
||||
"@graphql-tools/graphql-file-loader@npm:^7.3.7, @graphql-tools/graphql-file-loader@npm:^7.5.0":
|
||||
version: 7.5.17
|
||||
resolution: "@graphql-tools/graphql-file-loader@npm:7.5.17"
|
||||
dependencies:
|
||||
@@ -8233,7 +8222,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-tools/load@npm:^7.5.5, @graphql-tools/load@npm:^7.8.0, @graphql-tools/load@npm:^7.8.6":
|
||||
"@graphql-tools/load@npm:^7.5.5, @graphql-tools/load@npm:^7.8.0":
|
||||
version: 7.8.14
|
||||
resolution: "@graphql-tools/load@npm:7.8.14"
|
||||
dependencies:
|
||||
@@ -18937,20 +18926,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@samverschueren/stream-to-observable@npm:^0.3.0":
|
||||
version: 0.3.1
|
||||
resolution: "@samverschueren/stream-to-observable@npm:0.3.1"
|
||||
dependencies:
|
||||
any-observable: "npm:^0.3.0"
|
||||
peerDependenciesMeta:
|
||||
rxjs:
|
||||
optional: true
|
||||
zen-observable:
|
||||
optional: true
|
||||
checksum: 10c0/0d874453f6bc2460d71783292291f52feb36c2a75314b1072a6ffe6206562f33e9d664a554348d565a6b54da9041d75070371052545bc329caaa52f64216987f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@scalar/api-client@npm:2.2.62":
|
||||
version: 2.2.62
|
||||
resolution: "@scalar/api-client@npm:2.2.62"
|
||||
@@ -23435,6 +23410,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/lodash@npm:^4.17.15":
|
||||
version: 4.17.24
|
||||
resolution: "@types/lodash@npm:4.17.24"
|
||||
checksum: 10c0/b72f60d4daacdad1fa643edb3faba204c02a01eb1ac00a83ff73496a6d236fc55e459c06106e8ced42277dba932d087d8fc090f8de4ef590d3f91e6d6f7ce85a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/long@npm:^4.0.0":
|
||||
version: 4.0.2
|
||||
resolution: "@types/long@npm:4.0.2"
|
||||
@@ -24304,6 +24286,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/ws@npm:^6.0.1":
|
||||
version: 6.0.4
|
||||
resolution: "@types/ws@npm:6.0.4"
|
||||
dependencies:
|
||||
"@types/node": "npm:*"
|
||||
checksum: 10c0/fa958e64596ca9487c3ed6012834de70b47f25d971f1950cfb8e6a99cb77ff340ae82ac7627744e01b58010674ef8ede07d5a2ac29ca9ad0d67a430fcc69ae14
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/ws@npm:^8.0.0":
|
||||
version: 8.5.12
|
||||
resolution: "@types/ws@npm:8.5.12"
|
||||
@@ -27600,7 +27591,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ansi-escapes@npm:^3.0.0, ansi-escapes@npm:^3.1.0":
|
||||
"ansi-escapes@npm:^3.1.0":
|
||||
version: 3.2.0
|
||||
resolution: "ansi-escapes@npm:3.2.0"
|
||||
checksum: 10c0/084e1ce38139ad2406f18a8e7efe2b850ddd06ce3c00f633392d1ce67756dab44fe290e573d09ef3c9a0cb13c12881e0e35a8f77a017d39a0a4ab85ae2fae04f
|
||||
@@ -27666,13 +27657,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ansi-regex@npm:^3.0.0":
|
||||
version: 3.0.1
|
||||
resolution: "ansi-regex@npm:3.0.1"
|
||||
checksum: 10c0/d108a7498b8568caf4a46eea4f1784ab4e0dfb2e3f3938c697dee21443d622d765c958f2b7e2b9f6b9e55e2e2af0584eaa9915d51782b89a841c28e744e7a167
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ansi-regex@npm:^4.1.0":
|
||||
version: 4.1.1
|
||||
resolution: "ansi-regex@npm:4.1.1"
|
||||
@@ -27701,13 +27685,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ansi-styles@npm:^2.2.1":
|
||||
version: 2.2.1
|
||||
resolution: "ansi-styles@npm:2.2.1"
|
||||
checksum: 10c0/7c68aed4f1857389e7a12f85537ea5b40d832656babbf511cc7ecd9efc52889b9c3e5653a71a6aade783c3c5e0aa223ad4ff8e83c27ac8a666514e6c79068cab
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ansi-styles@npm:^3.2.1":
|
||||
version: 3.2.1
|
||||
resolution: "ansi-styles@npm:3.2.1"
|
||||
@@ -27768,13 +27745,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"any-observable@npm:^0.3.0":
|
||||
version: 0.3.0
|
||||
resolution: "any-observable@npm:0.3.0"
|
||||
checksum: 10c0/104c2b79c2ac7e6c75b35f8fd62babf73015668f22bd25336c6f848350d91f9e7daf2fddbf1c1b76fe795e89fbc91b49f70a2aec5c69f1acf0562c344f36042b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"any-promise@npm:^1.0.0":
|
||||
version: 1.3.0
|
||||
resolution: "any-promise@npm:1.3.0"
|
||||
@@ -28352,6 +28322,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"async-limiter@npm:~1.0.0":
|
||||
version: 1.0.1
|
||||
resolution: "async-limiter@npm:1.0.1"
|
||||
checksum: 10c0/0693d378cfe86842a70d4c849595a0bb50dc44c11649640ca982fa90cbfc74e3cc4753b5a0847e51933f2e9c65ce8e05576e75e5e1fd963a086e673735b35969
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"async-retry@npm:1.2.3":
|
||||
version: 1.2.3
|
||||
resolution: "async-retry@npm:1.2.3"
|
||||
@@ -30186,19 +30163,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chalk@npm:^1.0.0, chalk@npm:^1.1.3":
|
||||
version: 1.1.3
|
||||
resolution: "chalk@npm:1.1.3"
|
||||
dependencies:
|
||||
ansi-styles: "npm:^2.2.1"
|
||||
escape-string-regexp: "npm:^1.0.2"
|
||||
has-ansi: "npm:^2.0.0"
|
||||
strip-ansi: "npm:^3.0.0"
|
||||
supports-color: "npm:^2.0.0"
|
||||
checksum: 10c0/28c3e399ec286bb3a7111fd4225ebedb0d7b813aef38a37bca7c498d032459c265ef43404201d5fbb8d888d29090899c95335b4c0cda13e8b126ff15c541cef8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chalk@npm:^2.3.0, chalk@npm:^2.4.1":
|
||||
version: 2.4.2
|
||||
resolution: "chalk@npm:2.4.2"
|
||||
@@ -30561,15 +30525,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cli-cursor@npm:^2.0.0, cli-cursor@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "cli-cursor@npm:2.1.0"
|
||||
dependencies:
|
||||
restore-cursor: "npm:^2.0.0"
|
||||
checksum: 10c0/09ee6d8b5b818d840bf80ec9561eaf696672197d3a02a7daee2def96d5f52ce6e0bbe7afca754ccf14f04830b5a1b4556273e983507d5029f95bba3016618eda
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cli-cursor@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "cli-cursor@npm:4.0.0"
|
||||
@@ -30662,16 +30617,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cli-truncate@npm:^0.2.1":
|
||||
version: 0.2.1
|
||||
resolution: "cli-truncate@npm:0.2.1"
|
||||
dependencies:
|
||||
slice-ansi: "npm:0.0.4"
|
||||
string-width: "npm:^1.0.1"
|
||||
checksum: 10c0/c6caa5e2b70d841c42f4a2270d6fc7129df915f8911e4afa90c79231ccc857cd819a2c90e0707fde04e51ce56b4d71646b843f6cbaff4f7cdcb3b91ed51f6e89
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cli-truncate@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "cli-truncate@npm:2.1.0"
|
||||
@@ -31928,7 +31873,6 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "create-twenty-app@workspace:packages/create-twenty-app"
|
||||
dependencies:
|
||||
"@genql/cli": "npm:^3.0.3"
|
||||
"@swc/core": "npm:^1.15.11"
|
||||
"@swc/jest": "npm:^0.2.39"
|
||||
"@types/fs-extra": "npm:^11.0.0"
|
||||
@@ -32671,13 +32615,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"date-fns@npm:^1.27.2":
|
||||
version: 1.30.1
|
||||
resolution: "date-fns@npm:1.30.1"
|
||||
checksum: 10c0/bad6ad7c15180121e15d61ad62a4a214c108d66f35b35f5eeb6ade837a3c29aa4444b9528a93a5374b95ba11231c142276351bf52f4d168676f9a1e17ce3726a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"date-fns@npm:^3.3.1, date-fns@npm:^3.6.0":
|
||||
version: 3.6.0
|
||||
resolution: "date-fns@npm:3.6.0"
|
||||
@@ -33914,13 +33851,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"elegant-spinner@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "elegant-spinner@npm:1.0.1"
|
||||
checksum: 10c0/df607c83c20fc3ce56c514175dd5d1ee7f667da00cee13d04d32c70d55e76555091fa236689e691cf7dedba17b0020fec635e499cdde84dbea2ef8639314e5f8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"elliptic@npm:^6.5.3, elliptic@npm:^6.5.5":
|
||||
version: 6.6.1
|
||||
resolution: "elliptic@npm:6.6.1"
|
||||
@@ -35993,25 +35923,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"figures@npm:^1.7.0":
|
||||
version: 1.7.0
|
||||
resolution: "figures@npm:1.7.0"
|
||||
dependencies:
|
||||
escape-string-regexp: "npm:^1.0.5"
|
||||
object-assign: "npm:^4.1.0"
|
||||
checksum: 10c0/a10942b0eec3372bf61822ab130d2bbecdf527d551b0b013fbe7175b7a0238ead644ee8930a1a3cb872fb9ab2ec27df30e303765a3b70b97852e2e9ee43bdff3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"figures@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "figures@npm:2.0.0"
|
||||
dependencies:
|
||||
escape-string-regexp: "npm:^1.0.5"
|
||||
checksum: 10c0/5dc5a75fec3e7e04ae65d6ce51d28b3e70d4656c51b06996b6fdb2cb5b542df512e3b3c04482f5193a964edddafa5521479ff948fa84e12ff556e53e094ab4ce
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"file-saver@npm:^2.0.5":
|
||||
version: 2.0.5
|
||||
resolution: "file-saver@npm:2.0.5"
|
||||
@@ -37591,6 +37502,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"graphql-query-batcher@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "graphql-query-batcher@npm:1.0.1"
|
||||
checksum: 10c0/804d0f4064721a2116a16b9eac422e9233e85f4ab5b250cb8f83662725658ffde35779a8ae8211037f3dd2f9717de8cb63b394ad8057ce22171a83db2471196a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"graphql-redis-subscriptions@npm:2.7.0":
|
||||
version: 2.7.0
|
||||
resolution: "graphql-redis-subscriptions@npm:2.7.0"
|
||||
@@ -37895,15 +37813,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"has-ansi@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "has-ansi@npm:2.0.0"
|
||||
dependencies:
|
||||
ansi-regex: "npm:^2.0.0"
|
||||
checksum: 10c0/f54e4887b9f8f3c4bfefd649c48825b3c093987c92c27880ee9898539e6f01aed261e82e73153c3f920fde0db5bf6ebd58deb498ed1debabcb4bc40113ccdf05
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"has-bigints@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "has-bigints@npm:1.0.2"
|
||||
@@ -39248,7 +39157,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"indent-string@npm:^3.0.0, indent-string@npm:^3.2.0":
|
||||
"indent-string@npm:^3.2.0":
|
||||
version: 3.2.0
|
||||
resolution: "indent-string@npm:3.2.0"
|
||||
checksum: 10c0/91b6d61621d24944c5c4d365d6f1ff4a490264ccaf1162a602faa0d323e69231db2180ad4ccc092c2f49cf8888cdb3da7b73e904cc0fdeec40d0bfb41ceb9478
|
||||
@@ -39922,13 +39831,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-fullwidth-code-point@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "is-fullwidth-code-point@npm:2.0.0"
|
||||
checksum: 10c0/e58f3e4a601fc0500d8b2677e26e9fe0cd450980e66adb29d85b6addf7969731e38f8e43ed2ec868a09c101a55ac3d8b78902209269f38c5286bc98f5bc1b4d9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-fullwidth-code-point@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "is-fullwidth-code-point@npm:3.0.0"
|
||||
@@ -40169,15 +40071,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-observable@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "is-observable@npm:1.1.0"
|
||||
dependencies:
|
||||
symbol-observable: "npm:^1.1.0"
|
||||
checksum: 10c0/cf3166b0822f70ad06e7851e09430166ce658349d54aaa64c93a03320420b9285735821b23164bdce741ff83a86730ac3e53035ce4e2511ed843dbff4105bfa2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-online@npm:^10.0.0":
|
||||
version: 10.0.0
|
||||
resolution: "is-online@npm:10.0.0"
|
||||
@@ -40563,6 +40456,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"isomorphic-unfetch@npm:^3.0.0":
|
||||
version: 3.1.0
|
||||
resolution: "isomorphic-unfetch@npm:3.1.0"
|
||||
dependencies:
|
||||
node-fetch: "npm:^2.6.1"
|
||||
unfetch: "npm:^4.2.0"
|
||||
checksum: 10c0/d3b61fca06304db692b7f76bdfd3a00f410e42cfa7403c3b250546bf71589d18cf2f355922f57198e4cc4a9872d3647b20397a5c3edf1a347c90d57c83cf2a89
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"isomorphic-ws@npm:5.0.0, isomorphic-ws@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "isomorphic-ws@npm:5.0.0"
|
||||
@@ -42745,43 +42648,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"listr-silent-renderer@npm:^1.1.1":
|
||||
version: 1.1.1
|
||||
resolution: "listr-silent-renderer@npm:1.1.1"
|
||||
checksum: 10c0/a13e08ebf863516a757bce4887f05290070772113d89095e9f51a07cf0b11a43a7563a67ff3b287c752c08f6d781fdb2123b02957534e3e0675fb564f2a42e1b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"listr-update-renderer@npm:^0.5.0":
|
||||
version: 0.5.0
|
||||
resolution: "listr-update-renderer@npm:0.5.0"
|
||||
dependencies:
|
||||
chalk: "npm:^1.1.3"
|
||||
cli-truncate: "npm:^0.2.1"
|
||||
elegant-spinner: "npm:^1.0.1"
|
||||
figures: "npm:^1.7.0"
|
||||
indent-string: "npm:^3.0.0"
|
||||
log-symbols: "npm:^1.0.2"
|
||||
log-update: "npm:^2.3.0"
|
||||
strip-ansi: "npm:^3.0.1"
|
||||
peerDependencies:
|
||||
listr: ^0.14.2
|
||||
checksum: 10c0/8ade44bf3dc6146c8e0178000619439e8889792c4689b66be6ce82bd459f5fe462ecb34b05147fb206a8ad60e6d4e6f34c9f48038e18366f867fd972688b8edc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"listr-verbose-renderer@npm:^0.5.0":
|
||||
version: 0.5.0
|
||||
resolution: "listr-verbose-renderer@npm:0.5.0"
|
||||
dependencies:
|
||||
chalk: "npm:^2.4.1"
|
||||
cli-cursor: "npm:^2.1.0"
|
||||
date-fns: "npm:^1.27.2"
|
||||
figures: "npm:^2.0.0"
|
||||
checksum: 10c0/041cd1e82da7054f27ae0a914e98b40d15faf9f950ef850578fc6241d3fff3c2d7158a4f6226006e566b4c47bf445be2d254dd1ce5c16569a3a5dcd575bec656
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"listr2@npm:^4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "listr2@npm:4.0.5"
|
||||
@@ -42817,23 +42683,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"listr@npm:^0.14.3":
|
||||
version: 0.14.3
|
||||
resolution: "listr@npm:0.14.3"
|
||||
dependencies:
|
||||
"@samverschueren/stream-to-observable": "npm:^0.3.0"
|
||||
is-observable: "npm:^1.1.0"
|
||||
is-promise: "npm:^2.1.0"
|
||||
is-stream: "npm:^1.1.0"
|
||||
listr-silent-renderer: "npm:^1.1.1"
|
||||
listr-update-renderer: "npm:^0.5.0"
|
||||
listr-verbose-renderer: "npm:^0.5.0"
|
||||
p-map: "npm:^2.0.0"
|
||||
rxjs: "npm:^6.3.3"
|
||||
checksum: 10c0/753d518218c423f46bee8eeacccecadfd2e414ba9c0f602e7f85fe3f6fa18404dfab0812433aeda4683ee2548358488f597ac1a3d321196baec5d3149b200b10
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"load-esm@npm:1.0.3":
|
||||
version: 1.0.3
|
||||
resolution: "load-esm@npm:1.0.3"
|
||||
@@ -43260,15 +43109,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"log-symbols@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "log-symbols@npm:1.0.2"
|
||||
dependencies:
|
||||
chalk: "npm:^1.0.0"
|
||||
checksum: 10c0/c64e1fe41d0d043840f8b592d043b8607a836b846506f525a53d99d578561f02f97b2cba1d2b3c30bae5311d64b308d5a392a9930d252b906a9042fc2877da7a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"log-symbols@npm:^4.0.0, log-symbols@npm:^4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "log-symbols@npm:4.1.0"
|
||||
@@ -43299,17 +43139,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"log-update@npm:^2.3.0":
|
||||
version: 2.3.0
|
||||
resolution: "log-update@npm:2.3.0"
|
||||
dependencies:
|
||||
ansi-escapes: "npm:^3.0.0"
|
||||
cli-cursor: "npm:^2.0.0"
|
||||
wrap-ansi: "npm:^3.0.1"
|
||||
checksum: 10c0/9bf21b138801ab4770a2bfa735161cf005b869360eaf5003a84ba64ddc5f5c3ce7217f4f1fa79d9c1f510d792213b2c9800327228e94df05859d19b716215d90
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"log-update@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "log-update@npm:4.0.0"
|
||||
@@ -44962,13 +44791,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mimic-fn@npm:^1.0.0":
|
||||
version: 1.2.0
|
||||
resolution: "mimic-fn@npm:1.2.0"
|
||||
checksum: 10c0/ad55214aec6094c0af4c0beec1a13787556f8116ed88807cf3f05828500f21f93a9482326bcd5a077ae91e3e8795b4e76b5b4c8bb12237ff0e4043a365516cba
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mimic-fn@npm:^2.0.0, mimic-fn@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "mimic-fn@npm:2.1.0"
|
||||
@@ -45813,15 +45635,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"native-fetch@npm:^4.0.2":
|
||||
version: 4.0.2
|
||||
resolution: "native-fetch@npm:4.0.2"
|
||||
peerDependencies:
|
||||
undici: "*"
|
||||
checksum: 10c0/e3b824721daaa628086d9dcd02e8eb12f0a6c5e13a1d182682bae238d80c9bbf3dfd6314a94692ebe20316aa354476804b4df148201d066b46fc552a5794cfab
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"natural-compare@npm:^1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "natural-compare@npm:1.4.0"
|
||||
@@ -46977,15 +46790,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"onetime@npm:^2.0.0":
|
||||
version: 2.0.1
|
||||
resolution: "onetime@npm:2.0.1"
|
||||
dependencies:
|
||||
mimic-fn: "npm:^1.0.0"
|
||||
checksum: 10c0/b4e44a8c34e70e02251bfb578a6e26d6de6eedbed106cd78211d2fd64d28b6281d54924696554e4e966559644243753ac5df73c87f283b0927533d3315696215
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"onetime@npm:^5.1.0, onetime@npm:^5.1.2":
|
||||
version: 5.1.2
|
||||
resolution: "onetime@npm:5.1.2"
|
||||
@@ -47564,13 +47368,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"p-map@npm:^2.0.0":
|
||||
version: 2.1.0
|
||||
resolution: "p-map@npm:2.1.0"
|
||||
checksum: 10c0/735dae87badd4737a2dd582b6d8f93e49a1b79eabbc9815a4d63a528d5e3523e978e127a21d784cccb637010e32103a40d2aaa3ab23ae60250b1a820ca752043
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"p-map@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "p-map@npm:4.0.0"
|
||||
@@ -48243,13 +48040,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"path-to-regexp@npm:8.4.2, path-to-regexp@npm:^8.0.0, path-to-regexp@npm:^8.4.0":
|
||||
"path-to-regexp@npm:8.4.2, path-to-regexp@npm:^8.4.0":
|
||||
version: 8.4.2
|
||||
resolution: "path-to-regexp@npm:8.4.2"
|
||||
checksum: 10c0/05b115c49b47ad252ce05faa32930f643f23769c68b8bcfe78ad833545140c48bbffb3266986d6c8d5db13a64cf12e07e0d72d9882cab830efeefa553533ebaf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"path-to-regexp@npm:^8.0.0":
|
||||
version: 8.3.0
|
||||
resolution: "path-to-regexp@npm:8.3.0"
|
||||
checksum: 10c0/ee1544a73a3f294a97a4c663b0ce71bbf1621d732d80c9c9ed201b3e911a86cb628ebad691b9d40f40a3742fe22011e5a059d8eed2cf63ec2cb94f6fb4efe67c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"path-to-regexp@npm:~0.1.12":
|
||||
version: 0.1.12
|
||||
resolution: "path-to-regexp@npm:0.1.12"
|
||||
@@ -49126,7 +48930,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"prettier@npm:2.8.8, prettier@npm:^2.0.0, prettier@npm:^2.8.0":
|
||||
"prettier@npm:2.8.8, prettier@npm:^2.0.0, prettier@npm:^2.8.8":
|
||||
version: 2.8.8
|
||||
resolution: "prettier@npm:2.8.8"
|
||||
bin:
|
||||
@@ -51928,16 +51732,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"restore-cursor@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "restore-cursor@npm:2.0.0"
|
||||
dependencies:
|
||||
onetime: "npm:^2.0.0"
|
||||
signal-exit: "npm:^3.0.2"
|
||||
checksum: 10c0/f5b335bee06f440445e976a7031a3ef53691f9b7c4a9d42a469a0edaf8a5508158a0d561ff2b26a1f4f38783bcca2c0e5c3a44f927326f6694d5b44d7a4993e6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"restore-cursor@npm:^3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "restore-cursor@npm:3.1.0"
|
||||
@@ -52055,17 +51849,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rimraf@npm:^2.6.3":
|
||||
version: 2.7.1
|
||||
resolution: "rimraf@npm:2.7.1"
|
||||
dependencies:
|
||||
glob: "npm:^7.1.3"
|
||||
bin:
|
||||
rimraf: ./bin.js
|
||||
checksum: 10c0/4eef73d406c6940927479a3a9dee551e14a54faf54b31ef861250ac815172bade86cc6f7d64a4dc5e98b65e4b18a2e1c9ff3b68d296be0c748413f092bb0dd40
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rimraf@npm:^3.0.0, rimraf@npm:^3.0.2":
|
||||
version: 3.0.2
|
||||
resolution: "rimraf@npm:3.0.2"
|
||||
@@ -52472,7 +52255,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rxjs@npm:^6.3.3, rxjs@npm:^6.6.0":
|
||||
"rxjs@npm:^6.6.0":
|
||||
version: 6.6.7
|
||||
resolution: "rxjs@npm:6.6.7"
|
||||
dependencies:
|
||||
@@ -53793,13 +53576,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"slice-ansi@npm:0.0.4":
|
||||
version: 0.0.4
|
||||
resolution: "slice-ansi@npm:0.0.4"
|
||||
checksum: 10c0/997d4cc73e34aa8c0f60bdb71701b16c062cc4acd7a95e3b10e8c05d790eb5e735d9b470270dc6f443b1ba21492db7ceb849d5c93011d1256061bf7ed7216c7a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"slice-ansi@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "slice-ansi@npm:3.0.0"
|
||||
@@ -54579,16 +54355,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"string-width@npm:^2.1.1":
|
||||
version: 2.1.1
|
||||
resolution: "string-width@npm:2.1.1"
|
||||
dependencies:
|
||||
is-fullwidth-code-point: "npm:^2.0.0"
|
||||
strip-ansi: "npm:^4.0.0"
|
||||
checksum: 10c0/e5f2b169fcf8a4257a399f95d069522f056e92ec97dbdcb9b0cdf14d688b7ca0b1b1439a1c7b9773cd79446cbafd582727279d6bfdd9f8edd306ea5e90e5b610
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"string-width@npm:^5.0.0, string-width@npm:^5.0.1, string-width@npm:^5.1.2":
|
||||
version: 5.1.2
|
||||
resolution: "string-width@npm:5.1.2"
|
||||
@@ -54716,15 +54482,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"strip-ansi@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "strip-ansi@npm:4.0.0"
|
||||
dependencies:
|
||||
ansi-regex: "npm:^3.0.0"
|
||||
checksum: 10c0/d75d9681e0637ea316ddbd7d4d3be010b1895a17e885155e0ed6a39755ae0fd7ef46e14b22162e66a62db122d3a98ab7917794e255532ab461bb0a04feb03e7d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"strip-ansi@npm:^5.0.0, strip-ansi@npm:^5.2.0":
|
||||
version: 5.2.0
|
||||
resolution: "strip-ansi@npm:5.2.0"
|
||||
@@ -55072,6 +54829,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"subscriptions-transport-ws@npm:^0.9.16":
|
||||
version: 0.9.19
|
||||
resolution: "subscriptions-transport-ws@npm:0.9.19"
|
||||
dependencies:
|
||||
backo2: "npm:^1.0.2"
|
||||
eventemitter3: "npm:^3.1.0"
|
||||
iterall: "npm:^1.2.1"
|
||||
symbol-observable: "npm:^1.0.4"
|
||||
ws: "npm:^5.2.0 || ^6.0.0 || ^7.0.0"
|
||||
peerDependencies:
|
||||
graphql: ">=0.10.0"
|
||||
checksum: 10c0/6f2ade56865f0ba291d3ff82c79781b051c2374873bac853286fedfdbc05001b8c4018ab7cba44af667ead7f573e48d18892d58a8f9ca8d90dfb4bff5c125045
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sucrase@npm:^3.35.0":
|
||||
version: 3.35.0
|
||||
resolution: "sucrase@npm:3.35.0"
|
||||
@@ -55143,13 +54915,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"supports-color@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "supports-color@npm:2.0.0"
|
||||
checksum: 10c0/570e0b63be36cccdd25186350a6cb2eaad332a95ff162fa06d9499982315f2fe4217e69dd98e862fbcd9c81eaff300a825a1fe7bf5cc752e5b84dfed042b0dda
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"supports-color@npm:^5.0.0, supports-color@npm:^5.3.0, supports-color@npm:^5.4.0, supports-color@npm:^5.5.0":
|
||||
version: 5.5.0
|
||||
resolution: "supports-color@npm:5.5.0"
|
||||
@@ -55259,7 +55024,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"symbol-observable@npm:^1.0.4, symbol-observable@npm:^1.1.0":
|
||||
"symbol-observable@npm:^1.0.4":
|
||||
version: 1.2.0
|
||||
resolution: "symbol-observable@npm:1.2.0"
|
||||
checksum: 10c0/009fee50798ef80ed4b8195048288f108b03de162db07493f2e1fd993b33fafa72d659e832b584da5a2427daa78e5a738fb2a9ab027ee9454252e0bedbcd1fdc
|
||||
@@ -56411,10 +56176,13 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "twenty-client-sdk@workspace:packages/twenty-client-sdk"
|
||||
dependencies:
|
||||
"@genql/cli": "npm:^3.0.3"
|
||||
"@genql/runtime": "npm:^2.10.0"
|
||||
"@types/lodash": "npm:^4.17.15"
|
||||
"@typescript/native-preview": "npm:^7.0.0-dev.20260116.1"
|
||||
esbuild: "npm:^0.28.0"
|
||||
graphql: "npm:^16.8.1"
|
||||
lodash: "npm:^4.17.21"
|
||||
prettier: "npm:^2.8.8"
|
||||
tsc-alias: "npm:^1.8.16"
|
||||
twenty-shared: "workspace:*"
|
||||
typescript: "npm:^5.9.3"
|
||||
@@ -56740,7 +56508,6 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "twenty-sdk@workspace:packages/twenty-sdk"
|
||||
dependencies:
|
||||
"@genql/cli": "npm:^3.0.3"
|
||||
"@prettier/sync": "npm:^0.5.2"
|
||||
"@sniptt/guards": "npm:^0.2.0"
|
||||
"@types/node": "npm:^24.0.0"
|
||||
@@ -57744,15 +57511,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"undici@npm:^5.18.0":
|
||||
version: 5.29.0
|
||||
resolution: "undici@npm:5.29.0"
|
||||
dependencies:
|
||||
"@fastify/busboy": "npm:^2.0.0"
|
||||
checksum: 10c0/e4e4d631ca54ee0ad82d2e90e7798fa00a106e27e6c880687e445cc2f13b4bc87c5eba2a88c266c3eecffb18f26e227b778412da74a23acc374fca7caccec49b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unenv@npm:2.0.0-rc.24":
|
||||
version: 2.0.0-rc.24
|
||||
resolution: "unenv@npm:2.0.0-rc.24"
|
||||
@@ -57762,6 +57520,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unfetch@npm:^4.2.0":
|
||||
version: 4.2.0
|
||||
resolution: "unfetch@npm:4.2.0"
|
||||
checksum: 10c0/a5c0a896a6f09f278b868075aea65652ad185db30e827cb7df45826fe5ab850124bf9c44c4dafca4bf0c55a0844b17031e8243467fcc38dd7a7d435007151f1b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unhead@npm:1.11.20":
|
||||
version: 1.11.20
|
||||
resolution: "unhead@npm:1.11.20"
|
||||
@@ -60059,16 +59824,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"wrap-ansi@npm:^3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "wrap-ansi@npm:3.0.1"
|
||||
dependencies:
|
||||
string-width: "npm:^2.1.1"
|
||||
strip-ansi: "npm:^4.0.0"
|
||||
checksum: 10c0/ad6fed8f242c26755badaf452da154122d0d862f8b7aab56e758466857f230efafdc5fbffca026650b947ac3fc0eb563df5c05b9e2190a52a4a68f4eef3d4555
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"wrap-ansi@npm:^6.0.1, wrap-ansi@npm:^6.2.0":
|
||||
version: 6.2.0
|
||||
resolution: "wrap-ansi@npm:6.2.0"
|
||||
@@ -60212,6 +59967,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ws@npm:^6.1.4":
|
||||
version: 6.2.4
|
||||
resolution: "ws@npm:6.2.4"
|
||||
dependencies:
|
||||
async-limiter: "npm:~1.0.0"
|
||||
checksum: 10c0/5c2b9474164f9cb68c7776a1d10b0461c186f3a69bffb1028fca33eba5ab7206a09173fb0b311d6c5a81c8cf148406f8deb0b7d899542ab8ca67407d99717dad
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ws@npm:^8.12.0, ws@npm:^8.13.0, ws@npm:^8.18.0, ws@npm:^8.18.3, ws@npm:^8.19.0":
|
||||
version: 8.21.0
|
||||
resolution: "ws@npm:8.21.0"
|
||||
@@ -60885,6 +60649,23 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zen-observable-ts@npm:^0.8.21":
|
||||
version: 0.8.21
|
||||
resolution: "zen-observable-ts@npm:0.8.21"
|
||||
dependencies:
|
||||
tslib: "npm:^1.9.3"
|
||||
zen-observable: "npm:^0.8.0"
|
||||
checksum: 10c0/fe4a02f862b5f7e8ae0f86230c37b84c7d5611f5c206981afb4043e732d04cf7067a6cbe1ba82d20f18b735a3387937195a12542158a631d308ae3959a1d93c4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zen-observable@npm:^0.8.0":
|
||||
version: 0.8.15
|
||||
resolution: "zen-observable@npm:0.8.15"
|
||||
checksum: 10c0/71cc2f2bbb537300c3f569e25693d37b3bc91f225cefce251a71c30bc6bb3e7f8e9420ca0eb57f2ac9e492b085b8dfa075fd1e8195c40b83c951dd59c6e4fbf8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zhead@npm:^2.2.4":
|
||||
version: 2.2.4
|
||||
resolution: "zhead@npm:2.2.4"
|
||||
|
||||
Reference in New Issue
Block a user