Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/graphql-dev-dependency.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tailor-platform/sdk": patch
---

The SDK no longer installs `graphql`, `@urql/core`, or `wonka` as dependencies. The `DocumentNode` type that HTTP adapter `input` handlers and the executor `query` option accept is now included in the SDK's type declarations, so a project without `graphql` still gets `query` type-checked, and documents typed with `graphql` 16 or 17, including `TypedDocumentNode`, keep type-checking. If your project imports `graphql`, `@urql/core`, or `wonka` without declaring it, which only works when your package manager hoists it, add it to your own dependencies. If you use `graphql` 15 with `skipLibCheck: true`, passing a `parse()` result to the executor `query` option is now a type error, as it already was for HTTP adapters.
6 changes: 4 additions & 2 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@
"types": "./dist/runtime/logger.d.mts",
"import": "./dist/runtime/logger.mjs",
"default": "./dist/runtime/logger.mjs"
},
"./vendor/graphql/*": {
"types": "./dist/vendor/graphql/*.d.mts"
}
},
"scripts": {
Expand Down Expand Up @@ -204,7 +207,6 @@
"@standard-schema/spec": "1.1.0",
"@tailor-platform/function-kysely-tailordb": "0.1.3",
"@toiroakr/read-multiline": "0.4.3",
"@urql/core": "6.0.3",
"amaro": "1.3.0",
"confbox": "0.3.1",
"date-fns": "4.4.0",
Expand All @@ -213,7 +215,6 @@
"get-east-asian-width": "1.7.0",
"get-tsconfig": "4.14.3",
"globals": "17.13.0",
"graphql": "17.0.2",
"inflection": "3.0.2",
"kysely": "0.29.7",
"lines-db": "0.18.1",
Expand Down Expand Up @@ -244,6 +245,7 @@
"@types/semver": "7.8.0",
"@vitest/coverage-v8": "5.0.5",
"eslint-plugin-zod": "5.2.2",
"graphql": "17.0.2",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without graphql installed, the executor's operation.query (kind: "graphql") is no longer type-checked. Its type comes from @urql/core, whose document types rely on @0no-co/graphql.web's /*@ts-ignore*/ import * as GraphQL from 'graphql'; once graphql can't be resolved they accept any value. Until now the SDK's own graphql resolved that import.

createExecutor({
  name: "bad",
  trigger: scheduleTrigger({ cron: "0 0 * * *" }),
  operation: { kind: "graphql", query: 1 },
});

In an npm or pnpm project that installs the packed SDK without graphql, tsc --strict rejects this on the base (Type 'number' is not assignable to type 'DocumentInput<unknown, AnyVariables>') and accepts it on this PR. Typing query with the inlined DocumentNode, as the HTTP adapter does, would avoid relying on graphql being resolvable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed by b460618: query is now typed string | DocumentNode with the bundled types, and @urql/core is removed.
With the packed SDK installed without graphql, your query: 1 example now fails tsc --strict with TS2769 (Type 'number' is not assignable to type 'string | DocumentNode').
A string query compiles cleanly, and the published .d.mts files have no references to graphql or @urql.

"oxfmt": "0.75.0",
"oxlint": "1.90.0",
"oxlint-tsgolint": "7.0.2005",
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/scripts/check-public-api-jsdoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ function entryPoints(): string[] {
const points: string[] = [];
for (const [key, value] of Object.entries(pkg.exports)) {
if (
key.includes("*") ||
typeof value !== "object" ||
value === null ||
!("types" in value) ||
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/scripts/check-zod-isolation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ interface Violation {
const violations: Violation[] = [];

for (const [entry, target] of Object.entries(pkg.exports)) {
if (ZOD_ALLOWED_ENTRIES.has(entry)) {
if (ZOD_ALLOWED_ENTRIES.has(entry) || entry.includes("*")) {
continue;
}
if (typeof target !== "object" || target === null) {
Expand Down
9 changes: 4 additions & 5 deletions packages/sdk/src/configure/services/executor/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,19 @@ import type {
WebhookOperation as ParserWebhookOperation,
WorkflowOperation as ParserWorkflowOperation,
} from "#/types/executor.generated";
import type { Client } from "@urql/core";
import type { DocumentNode } from "graphql";

/** Function-based executor operation. The body receives the trigger args and the `invoker`. */
export type FunctionOperation<Args> = Omit<ParserFunctionOperation, "body" | "invoker"> & {
body: (args: Args & { invoker: TailorPrincipal | null }) => void | Promise<void>;
invoker?: MachineUserName;
};

type UrqlOperationArgs = Parameters<Client["query"] | Client["mutation"]>;

/** GraphQL-based executor operation. Executes a GraphQL query or mutation. */
export type GqlOperation<Args> = Omit<ParserGqlOperation, "query" | "variables" | "invoker"> & {
query: UrqlOperationArgs[0];
variables?: (args: Args) => UrqlOperationArgs[1];
query: string | DocumentNode;
// oxlint-disable-next-line no-explicit-any
variables?: (args: Args) => { [prop: string]: any } | void | undefined;
invoker?: MachineUserName;
};

Expand Down
45 changes: 43 additions & 2 deletions packages/sdk/tsdown.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { cpSync } from "node:fs";
import { cpSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
import path from "node:path";
import Sonda from "sonda/rolldown";
import { defineConfig } from "tsdown";
import { entry } from "./scripts/build-entries.mjs";
import type { OutputOptions, PreRenderedChunk } from "rolldown";

function copyToOutDir(outDir: string, source: string, dest: string): void {
cpSync(path.resolve(source), path.join(outDir, dest));
Expand All @@ -17,6 +18,24 @@ const jsPlugins = [
}),
];

const GRAPHQL_DIR = "node_modules/graphql/";
const VENDOR_GRAPHQL_DIR = "vendor/graphql";
const UP_TO_PACKAGES_DIR = /^(?:.*\/)?packages\//;

function stableChunkName(name: string): string {
const graphqlAt = name.indexOf(GRAPHQL_DIR);
if (graphqlAt !== -1)
return `${VENDOR_GRAPHQL_DIR}/${name.slice(graphqlAt + GRAPHQL_DIR.length)}`;
return name.replace(UP_TO_PACKAGES_DIR, "");
}

function renameChunk(template: OutputOptions["entryFileNames"]) {
return (chunk: PreRenderedChunk): string => {
const resolved = typeof template === "function" ? template(chunk) : (template ?? "[name].js");
return resolved.replace("[name]", stableChunkName(chunk.name.replace(/\.d$/, "")));
};
}

const externalDeps = ["vite", "vitest", /^@tailor-platform\/sdk$/];

const sharedOptions = {
Expand Down Expand Up @@ -62,6 +81,28 @@ export default defineConfig([
},
unbundle: true,
root: "src",
deps: { neverBundle: externalDeps },
deps: { neverBundle: externalDeps, dts: { alwaysBundle: [/^graphql(\/|$)/] } },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inlined types under dist/vendor/graphql aren't reachable through exports, so with declaration: true a project can no longer emit an inferred type that reaches them, even when it installs graphql itself:

declare const doc: HttpAdapterTypedDocumentNode<{ user: { id: string } }, { id: string }>;
export const definitions = doc.definitions;

With graphql@17.0.2 installed via pnpm, this emits on the base and fails on this PR with TS2883 (The inferred type of 'definitions' cannot be named without a reference to 'DefinitionNode' from './node_modules/@tailor-platform/sdk/dist/vendor/graphql/language/ast.mjs').

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

b460618 adds ./vendor/graphql/* as a types-only export.
With the packed SDK, your definitions example with declaration: true now emits import("@tailor-platform/sdk/vendor/graphql/language/ast").DefinitionNode[] instead of failing with TS2883.

outputOptions: (options) => ({
...options,
entryFileNames: renameChunk(options.entryFileNames),
chunkFileNames: renameChunk(options.chunkFileNames),
}),
onSuccess: (config) => {
const vendorDir = path.join(config.outDir, VENDOR_GRAPHQL_DIR);
mkdirSync(vendorDir, { recursive: true });
copyToOutDir(config.outDir, `${GRAPHQL_DIR}LICENSE`, `${VENDOR_GRAPHQL_DIR}/LICENSE`);
for (const file of readdirSync(vendorDir, { recursive: true, encoding: "utf8" })) {
if (!file.endsWith(".d.mts")) continue;
const target = path.join(vendorDir, file);
writeFileSync(
target,
readFileSync(target, "utf8").replace(/^\/\/#region (.*)$/gm, (line, region: string) => {
const graphqlAt = region.indexOf(GRAPHQL_DIR);
if (graphqlAt === -1) return line;
return `//#region graphql/${region.slice(graphqlAt + GRAPHQL_DIR.length)}`;
}),
);
}
},
},
]);
46 changes: 24 additions & 22 deletions packages/sdk/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,30 @@ const packageJson = JSON.parse(

const escapeRegExp = (value: string): string => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");

const sdkSourceAliases = Object.entries(packageJson.exports).map(([exportName, target]) => {
const publicImport =
exportName === "." ? "@tailor-platform/sdk" : `@tailor-platform/sdk/${exportName.slice(2)}`;
const distImport = target.import ?? target.default;

if (!distImport?.startsWith("./dist/") || !distImport.endsWith(".mjs")) {
throw new Error(
`Unsupported @tailor-platform/sdk export ${exportName}: expected ./dist/*.mjs import target`,
);
}

return {
find: new RegExp(`^${escapeRegExp(publicImport)}$`),
replacement: path.resolve(
import.meta.dirname,
distImport
.replace(/^\.\//, "")
.replace(/^dist\//, "src/")
.replace(/\.mjs$/, ".ts"),
),
};
});
const sdkSourceAliases = Object.entries(packageJson.exports)
.filter(([, target]) => target.import ?? target.default)
.map(([exportName, target]) => {
const publicImport =
exportName === "." ? "@tailor-platform/sdk" : `@tailor-platform/sdk/${exportName.slice(2)}`;
const distImport = target.import ?? target.default;

if (!distImport?.startsWith("./dist/") || !distImport.endsWith(".mjs")) {
throw new Error(
`Unsupported @tailor-platform/sdk export ${exportName}: expected ./dist/*.mjs import target`,
);
}

return {
find: new RegExp(`^${escapeRegExp(publicImport)}$`),
replacement: path.resolve(
import.meta.dirname,
distImport
.replace(/^\.\//, "")
.replace(/^dist\//, "src/")
.replace(/\.mjs$/, ".ts"),
),
};
});

// E2e files whose measured V8 coverage contribution justifies running them in
// the coverage CI job ("e2e-coverage" project below): deploy.test.ts calls
Expand Down
Loading
Loading