From 3de34bf2623bd5f8d32d58b60b8b43852e63ef05 Mon Sep 17 00:00:00 2001 From: Akira HIGUCHI Date: Thu, 5 Feb 2026 16:12:57 +0900 Subject: [PATCH 1/7] feat(sdk): add toResolverOutput function for TailorDBType conversion toResolverOutput() converts a TailorDBType to a resolver output type that matches the TailorDB auto-generated query's return type. Co-Authored-By: Claude Opus 4.5 --- .changeset/funny-planes-admire.md | 8 +++ packages/sdk/docs/services/resolver.md | 56 +++++++++++++++++++ .../src/configure/services/resolver/index.ts | 2 +- .../configure/services/resolver/resolver.ts | 16 ++++++ 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 .changeset/funny-planes-admire.md diff --git a/.changeset/funny-planes-admire.md b/.changeset/funny-planes-admire.md new file mode 100644 index 000000000..77b1840cf --- /dev/null +++ b/.changeset/funny-planes-admire.md @@ -0,0 +1,8 @@ +--- +"@tailor-platform/sdk": minor +--- + +Add `toResolverOutput` function to convert TailorDBType to resolver output field + +- `toResolverOutput(type)` converts a TailorDBType to generated query output type +- Simplifies using TailorDB types as resolver outputs with proper type names diff --git a/packages/sdk/docs/services/resolver.md b/packages/sdk/docs/services/resolver.md index e472d7ef2..235ead959 100644 --- a/packages/sdk/docs/services/resolver.md +++ b/packages/sdk/docs/services/resolver.md @@ -120,6 +120,62 @@ createResolver({ }); ``` +## Custom Type Names + +### Using `typeName()` for nested objects + +When defining nested objects in input or output schemas, you can specify a custom GraphQL type name using the `typeName()` method. This is useful when you want to control the exact type name that appears in the GraphQL schema. + +```typescript +createResolver({ + name: "createProfile", + operation: "mutation", + input: { + profile: t + .object({ + name: t.string(), + email: t.string(), + }) + .typeName("ProfileInput"), // GraphQL type will be "ProfileInput" + }, + body: (context) => context.input.profile, + output: t + .object({ + name: t.string(), + email: t.string(), + }) + .typeName("ProfileOutput"), // GraphQL type will be "ProfileOutput" +}); +``` + +Without `typeName()`, the SDK generates type names automatically (e.g., `CreateProfileProfile` for input). + +### Using `toResolverOutput()` for TailorDB types + +`toResolverOutput()` makes the resolver's output type match the TailorDB auto-generated query's return type. For example, if you have a `User` type in TailorDB, `toResolverOutput(user)` produces the same GraphQL type as the auto-generated `user` and `users` queries. + +```typescript +import { createResolver, t, toResolverOutput } from "@tailor-platform/sdk"; +import { user } from "../tailordb/user"; + +export default createResolver({ + name: "getUser", + operation: "query", + input: { + id: t.uuid(), + }, + body: async (context) => { + const db = getDB("tailordb"); + return await db + .selectFrom("User") + .selectAll() + .where("id", "=", context.input.id) + .executeTakeFirstOrThrow(); + }, + output: toResolverOutput(user), // Same type as TailorDB's `user` query returns +}); +``` + ## Input Validation Add validation rules to input fields using the `validate` method: diff --git a/packages/sdk/src/configure/services/resolver/index.ts b/packages/sdk/src/configure/services/resolver/index.ts index 8e01d1f04..e78427fbe 100644 --- a/packages/sdk/src/configure/services/resolver/index.ts +++ b/packages/sdk/src/configure/services/resolver/index.ts @@ -1,4 +1,4 @@ -export { createResolver } from "./resolver"; +export { createResolver, toResolverOutput } from "./resolver"; export type { QueryType, diff --git a/packages/sdk/src/configure/services/resolver/resolver.ts b/packages/sdk/src/configure/services/resolver/resolver.ts index 32d7a256a..dca4c1d0b 100644 --- a/packages/sdk/src/configure/services/resolver/resolver.ts +++ b/packages/sdk/src/configure/services/resolver/resolver.ts @@ -1,4 +1,5 @@ import { t } from "@/configure/types/type"; +import type { TailorDBType, TailorAnyDBField } from "@/configure/services/tailordb/schema"; import type { TailorAnyField, TailorUser } from "@/configure/types"; import type { TailorEnv } from "@/configure/types/env"; import type { InferFieldsOutput, output } from "@/configure/types/helpers"; @@ -78,3 +79,18 @@ export function createResolver< // A loose config alias for userland use-cases // oxlint-disable-next-line no-explicit-any export type ResolverConfig = ReturnType>; + +/** + * Convert a TailorDBType to a TailorField for use as resolver output. + * Equivalent to: t.object(type.fields).typeName(type.name) + * @param type - The TailorDBType to convert + * @returns A TailorField with the type's fields and name as typeName + */ +export function toResolverOutput>( + type: TailorDBType, +): TailorField<{ type: "nested"; array: false; typeName: true }, InferFieldsOutput> { + return t.object(type.fields).typeName(type.name) as TailorField< + { type: "nested"; array: false; typeName: true }, + InferFieldsOutput + >; +} From 98a30e47d4efb473f58a1b6f63d03709a53b926e Mon Sep 17 00:00:00 2001 From: Akira HIGUCHI Date: Fri, 6 Feb 2026 02:02:17 +0900 Subject: [PATCH 2/7] test: add e2e tests for toResolverOutput with all field types Test that toResolverOutput produces types matching TailorDB introspection: - Nested objects (userInfo, metadata) - File fields (avatar) - Forward relations (ownerID, owner) - Backward relations (detail from ProfileDetail, comments from ProfileComment) Co-Authored-By: Claude Opus 4.5 --- example/e2e/resolver.test.ts | 139 ++++++++++++++++++++ example/generated/files.ts | 4 + example/generated/tailordb.ts | 17 +++ example/resolvers/passThrough.ts | 6 +- example/seed/data/NestedProfile.schema.ts | 5 + example/seed/data/ProfileComment.jsonl | 0 example/seed/data/ProfileComment.schema.ts | 20 +++ example/seed/data/ProfileDetail.jsonl | 0 example/seed/data/ProfileDetail.schema.ts | 23 ++++ example/seed/exec.mjs | 6 +- example/seed/graphql/ProfileComment.graphql | 5 + example/seed/graphql/ProfileDetail.graphql | 5 + example/seed/mappings/ProfileComment.json | 8 ++ example/seed/mappings/ProfileDetail.json | 8 ++ example/tailordb/nested.ts | 6 + example/tailordb/profileReference.ts | 36 +++++ example/tests/fixtures/expected/db.ts | 17 +++ 17 files changed, 301 insertions(+), 4 deletions(-) create mode 100644 example/seed/data/ProfileComment.jsonl create mode 100644 example/seed/data/ProfileComment.schema.ts create mode 100644 example/seed/data/ProfileDetail.jsonl create mode 100644 example/seed/data/ProfileDetail.schema.ts create mode 100644 example/seed/graphql/ProfileComment.graphql create mode 100644 example/seed/graphql/ProfileDetail.graphql create mode 100644 example/seed/mappings/ProfileComment.json create mode 100644 example/seed/mappings/ProfileDetail.json create mode 100644 example/tailordb/profileReference.ts diff --git a/example/e2e/resolver.test.ts b/example/e2e/resolver.test.ts index 46e82f678..add1c6a9d 100644 --- a/example/e2e/resolver.test.ts +++ b/example/e2e/resolver.test.ts @@ -151,6 +151,7 @@ describe("controlplane", async () => { // Verify field descriptions from TailorDBField const inputType = passThrough?.inputs?.find((i) => i.name === "input"); expect(inputType?.type?.kind).toBe("UserDefined"); + expect(inputType?.type?.name).toBe("PassThroughInput"); // Verify response field descriptions const userInfoResponse = passThrough?.response?.type?.fields?.find( @@ -385,6 +386,144 @@ describe("dataplane", () => { const result = await graphQLClient.rawRequest(query); expect(result.errors).toBeDefined(); }); + + test("verifies output typeName via GraphQL introspection", async () => { + const introspectionQuery = gql` + query { + __type(name: "Query") { + fields { + name + type { + name + kind + ofType { + name + } + } + } + } + } + `; + const result = await graphQLClient.rawRequest(introspectionQuery); + expect(result.errors).toBeUndefined(); + + const queryType = result.data as { + __type: { + fields: { + name: string; + type: { name: string | null; kind: string; ofType: { name: string } | null }; + }[]; + }; + }; + const passThroughField = queryType.__type.fields.find((f) => f.name === "passThrough"); + expect(passThroughField).toBeDefined(); + + // Verify the output type name + const outputTypeName = passThroughField?.type.name ?? passThroughField?.type.ofType?.name; + expect(outputTypeName).toBe("NestedProfile"); + }); + + test("toResolverOutput produces types matching TailorDB introspection", async () => { + // Helper to get field type name + const getTypeName = ( + field: { type: { name: string | null; ofType: { name: string } | null } } | undefined, + ) => field?.type?.name ?? field?.type?.ofType?.name; + + // Get the passThrough resolver's output type name + const schemaQuery = gql` + query { + __schema { + queryType { + fields { + name + type { + name + ofType { + name + } + } + } + } + } + } + `; + const schemaResult = await graphQLClient.rawRequest(schemaQuery); + const queryFields = ( + schemaResult.data as { + __schema: { + queryType: { + fields: { + name: string; + type: { name: string | null; ofType: { name: string } | null }; + }[]; + }; + }; + } + ).__schema.queryType.fields; + const passThroughField = queryFields.find((f) => f.name === "passThrough"); + const passThroughTypeName = getTypeName(passThroughField); + + // Get nested field types for passThrough output + const typeQuery = gql` + query GetType($name: String!) { + __type(name: $name) { + fields { + name + type { + name + ofType { + name + } + } + } + } + } + `; + const passThroughTypeResult = await graphQLClient.rawRequest(typeQuery, { + name: passThroughTypeName, + }); + const passThroughFields = ( + passThroughTypeResult.data as { + __type: { + fields: { + name: string; + type: { name: string | null; ofType: { name: string } | null }; + }[]; + }; + } + ).__type.fields; + + // Get TailorDB NestedProfile type for comparison + const tailorDbResult = await graphQLClient.rawRequest(typeQuery, { name: "NestedProfile" }); + const tailorDbFields = ( + tailorDbResult.data as { + __type: { + fields: { + name: string; + type: { name: string | null; ofType: { name: string } | null }; + }[]; + }; + } + ).__type.fields; + + // Verify field types match (including backward relations from new types) + const fieldsToCheck = [ + "userInfo", // nested object + "metadata", // nested object + "avatar", // file field + "ownerID", // n-1 relation (foreign key) + "owner", // n-1 relation (navigation property) + "detail", // 1-1 backward relation (from ProfileDetail) + "comments", // n-1 backward relation (from ProfileComment) + ]; + for (const fieldName of fieldsToCheck) { + const passThroughFieldType = getTypeName( + passThroughFields.find((f) => f.name === fieldName), + ); + const tailorDbFieldType = getTypeName(tailorDbFields.find((f) => f.name === fieldName)); + expect(passThroughFieldType).toBe(tailorDbFieldType); + } + }); }); test("env", async () => { diff --git a/example/generated/files.ts b/example/generated/files.ts index 052806afd..3d680bb79 100644 --- a/example/generated/files.ts +++ b/example/generated/files.ts @@ -1,4 +1,7 @@ export interface TypeWithFiles { + NestedProfile: { + fields: "avatar"; + }; SalesOrder: { fields: "receipt" | "form"; }; @@ -11,6 +14,7 @@ export interface TypeWithFiles { } const namespaces: Record = { + NestedProfile: "tailordb", SalesOrder: "tailordb", User: "tailordb", Event: "analyticsdb", diff --git a/example/generated/tailordb.ts b/example/generated/tailordb.ts index 123311590..e4b145d76 100644 --- a/example/generated/tailordb.ts +++ b/example/generated/tailordb.ts @@ -55,6 +55,23 @@ export interface Namespace { version: number; }; archived: boolean | null; + ownerID: string | null; + createdAt: Generated; + updatedAt: Timestamp | null; + } + + ProfileComment: { + id: Generated; + content: string; + profileID: string; + createdAt: Generated; + updatedAt: Timestamp | null; + } + + ProfileDetail: { + id: Generated; + bio: string | null; + profileID: string; createdAt: Generated; updatedAt: Timestamp | null; } diff --git a/example/resolvers/passThrough.ts b/example/resolvers/passThrough.ts index adeaa78e7..0dae642cb 100644 --- a/example/resolvers/passThrough.ts +++ b/example/resolvers/passThrough.ts @@ -1,4 +1,4 @@ -import { createResolver, t } from "@tailor-platform/sdk"; +import { createResolver, t, toResolverOutput } from "@tailor-platform/sdk"; import { nestedProfile } from "../tailordb/nested"; const inputFields = { @@ -11,12 +11,12 @@ export default createResolver({ description: "Pass Through - Nested Profile Type(Create)", input: { id: t.uuid({ optional: true }), - input: t.object(inputFields), + input: t.object(inputFields).typeName("PassThroughInput"), }, body: ({ input }) => ({ ...input.input, id: input.id ?? crypto.randomUUID(), createdAt: new Date(), }), - output: nestedProfile.fields, + output: toResolverOutput(nestedProfile), }); diff --git a/example/seed/data/NestedProfile.schema.ts b/example/seed/data/NestedProfile.schema.ts index c45955780..f57178cb5 100644 --- a/example/seed/data/NestedProfile.schema.ts +++ b/example/seed/data/NestedProfile.schema.ts @@ -12,4 +12,9 @@ const hook = createTailorDBHook(nestedProfile); export const schema = defineSchema( createStandardSchema(schemaType, hook), + { + foreignKeys: [ + {"column":"ownerID","references":{"table":"User","column":"id"}}, + ], + } ); diff --git a/example/seed/data/ProfileComment.jsonl b/example/seed/data/ProfileComment.jsonl new file mode 100644 index 000000000..e69de29bb diff --git a/example/seed/data/ProfileComment.schema.ts b/example/seed/data/ProfileComment.schema.ts new file mode 100644 index 000000000..81a8d8c04 --- /dev/null +++ b/example/seed/data/ProfileComment.schema.ts @@ -0,0 +1,20 @@ +import { t } from "@tailor-platform/sdk"; +import { createTailorDBHook, createStandardSchema } from "@tailor-platform/sdk/test"; +import { defineSchema } from "@toiroakr/lines-db"; +import { profileComment } from "../../tailordb/profileReference"; + +const schemaType = t.object({ + ...profileComment.pickFields(["id","createdAt"], { optional: true }), + ...profileComment.omitFields(["id","createdAt"]), +}); + +const hook = createTailorDBHook(profileComment); + +export const schema = defineSchema( + createStandardSchema(schemaType, hook), + { + foreignKeys: [ + {"column":"profileID","references":{"table":"NestedProfile","column":"id"}}, + ], + } +); diff --git a/example/seed/data/ProfileDetail.jsonl b/example/seed/data/ProfileDetail.jsonl new file mode 100644 index 000000000..e69de29bb diff --git a/example/seed/data/ProfileDetail.schema.ts b/example/seed/data/ProfileDetail.schema.ts new file mode 100644 index 000000000..8d7c528ce --- /dev/null +++ b/example/seed/data/ProfileDetail.schema.ts @@ -0,0 +1,23 @@ +import { t } from "@tailor-platform/sdk"; +import { createTailorDBHook, createStandardSchema } from "@tailor-platform/sdk/test"; +import { defineSchema } from "@toiroakr/lines-db"; +import { profileDetail } from "../../tailordb/profileReference"; + +const schemaType = t.object({ + ...profileDetail.pickFields(["id","createdAt"], { optional: true }), + ...profileDetail.omitFields(["id","createdAt"]), +}); + +const hook = createTailorDBHook(profileDetail); + +export const schema = defineSchema( + createStandardSchema(schemaType, hook), + { + foreignKeys: [ + {"column":"profileID","references":{"table":"NestedProfile","column":"id"}}, + ], + indexes: [ + {"name":"profiledetail_profileID_unique_idx","columns":["profileID"],"unique":true}, + ], + } +); diff --git a/example/seed/exec.mjs b/example/seed/exec.mjs index 178c429cb..8d6f448b2 100644 --- a/example/seed/exec.mjs +++ b/example/seed/exec.mjs @@ -87,6 +87,8 @@ const namespaceEntities = { "Customer", "Invoice", "NestedProfile", + "ProfileComment", + "ProfileDetail", "PurchaseOrder", "SalesOrder", "SalesOrderCreated", @@ -104,7 +106,9 @@ const namespaceDeps = { "tailordb": { "Customer": [], "Invoice": ["SalesOrder"], - "NestedProfile": [], + "NestedProfile": ["User"], + "ProfileComment": ["NestedProfile"], + "ProfileDetail": ["NestedProfile"], "PurchaseOrder": ["Supplier"], "SalesOrder": ["Customer", "User"], "SalesOrderCreated": [], diff --git a/example/seed/graphql/ProfileComment.graphql b/example/seed/graphql/ProfileComment.graphql new file mode 100644 index 000000000..c53348739 --- /dev/null +++ b/example/seed/graphql/ProfileComment.graphql @@ -0,0 +1,5 @@ +mutation CreateProfileComment($input: ProfileCommentCreateInput!) { + createProfileComment(input: $input) { + id + } +} diff --git a/example/seed/graphql/ProfileDetail.graphql b/example/seed/graphql/ProfileDetail.graphql new file mode 100644 index 000000000..53611ecc1 --- /dev/null +++ b/example/seed/graphql/ProfileDetail.graphql @@ -0,0 +1,5 @@ +mutation CreateProfileDetail($input: ProfileDetailCreateInput!) { + createProfileDetail(input: $input) { + id + } +} diff --git a/example/seed/mappings/ProfileComment.json b/example/seed/mappings/ProfileComment.json new file mode 100644 index 000000000..afd268487 --- /dev/null +++ b/example/seed/mappings/ProfileComment.json @@ -0,0 +1,8 @@ +{ + "dataFile": "data/ProfileComment.jsonl", + "dataFormat": "jsonl", + "graphqlFile": "graphql/ProfileComment.graphql", + "mapping": { + "input": "$" + } +} diff --git a/example/seed/mappings/ProfileDetail.json b/example/seed/mappings/ProfileDetail.json new file mode 100644 index 000000000..9a4b41dba --- /dev/null +++ b/example/seed/mappings/ProfileDetail.json @@ -0,0 +1,8 @@ +{ + "dataFile": "data/ProfileDetail.jsonl", + "dataFormat": "jsonl", + "graphqlFile": "graphql/ProfileDetail.graphql", + "mapping": { + "input": "$" + } +} diff --git a/example/tailordb/nested.ts b/example/tailordb/nested.ts index b3fa800ba..2f3e3aa6d 100644 --- a/example/tailordb/nested.ts +++ b/example/tailordb/nested.ts @@ -1,5 +1,6 @@ import { db } from "@tailor-platform/sdk"; import { defaultGqlPermission, defaultPermission } from "./permissions"; +import { user } from "./user"; export const nestedProfile = db .type("NestedProfile", "Nested Profile Type", { @@ -20,7 +21,12 @@ export const nestedProfile = db }) .description("Profile metadata"), archived: db.bool({ optional: true }).description("Archive status"), + ownerID: db.uuid({ optional: true }).relation({ + type: "n-1", + toward: { type: user, as: "owner" }, + }), ...db.fields.timestamps(), }) + .files({ avatar: "profile avatar image" }) .permission(defaultPermission) .gqlPermission(defaultGqlPermission); diff --git a/example/tailordb/profileReference.ts b/example/tailordb/profileReference.ts new file mode 100644 index 000000000..9cf58bd50 --- /dev/null +++ b/example/tailordb/profileReference.ts @@ -0,0 +1,36 @@ +import { db } from "@tailor-platform/sdk"; +import { nestedProfile } from "./nested"; +import { defaultPermission } from "./permissions"; + +// Test type for backward relation testing +// n-1 relation to NestedProfile (creates backward relation on NestedProfile) +export const profileComment = db + .type("ProfileComment", "Comment on a profile", { + content: db.string().description("Comment content"), + profileID: db + .uuid() + .relation({ + type: "n-1", + toward: { type: nestedProfile, as: "profile" }, + backward: "comments", + }) + .description("Referenced profile"), + ...db.fields.timestamps(), + }) + .permission(defaultPermission); + +// 1-1 relation to NestedProfile (creates backward relation on NestedProfile) +export const profileDetail = db + .type("ProfileDetail", "Additional detail for a profile", { + bio: db.string({ optional: true }).description("Extended biography"), + profileID: db + .uuid() + .relation({ + type: "1-1", + toward: { type: nestedProfile, as: "profile" }, + backward: "detail", + }) + .description("Referenced profile"), + ...db.fields.timestamps(), + }) + .permission(defaultPermission); diff --git a/example/tests/fixtures/expected/db.ts b/example/tests/fixtures/expected/db.ts index 123311590..e4b145d76 100644 --- a/example/tests/fixtures/expected/db.ts +++ b/example/tests/fixtures/expected/db.ts @@ -55,6 +55,23 @@ export interface Namespace { version: number; }; archived: boolean | null; + ownerID: string | null; + createdAt: Generated; + updatedAt: Timestamp | null; + } + + ProfileComment: { + id: Generated; + content: string; + profileID: string; + createdAt: Generated; + updatedAt: Timestamp | null; + } + + ProfileDetail: { + id: Generated; + bio: string | null; + profileID: string; createdAt: Generated; updatedAt: Timestamp | null; } From 58f3358ce92e20c7202712be9d74dc22cb316cd9 Mon Sep 17 00:00:00 2001 From: Akira HIGUCHI Date: Fri, 6 Feb 2026 02:08:53 +0900 Subject: [PATCH 3/7] chore: add migration 0008 for new types and fields Add migration for: - ProfileComment type (n-1 relation to NestedProfile) - ProfileDetail type (1-1 relation to NestedProfile) - NestedProfile.ownerID field (n-1 relation to User) - NestedProfile.avatar file field Co-Authored-By: Claude Opus 4.5 --- example/migrations/0008/diff.json | 107 ++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 example/migrations/0008/diff.json diff --git a/example/migrations/0008/diff.json b/example/migrations/0008/diff.json new file mode 100644 index 000000000..5cfea1b3d --- /dev/null +++ b/example/migrations/0008/diff.json @@ -0,0 +1,107 @@ +{ + "version": 1, + "namespace": "tailordb", + "createdAt": "2026-02-05T02:04:29.299Z", + "changes": [ + { + "kind": "type_added", + "typeName": "ProfileComment", + "after": { + "name": "ProfileComment", + "fields": { + "id": { + "type": "uuid", + "required": true + }, + "content": { + "type": "string", + "required": true + }, + "profileID": { + "type": "uuid", + "required": true, + "index": true, + "foreignKey": true, + "foreignKeyType": "NestedProfile", + "foreignKeyField": "id" + }, + "createdAt": { + "type": "datetime", + "required": true + }, + "updatedAt": { + "type": "datetime", + "required": false + } + }, + "pluralForm": "ProfileComments", + "description": "Comment on a profile", + "settings": {} + } + }, + { + "kind": "type_added", + "typeName": "ProfileDetail", + "after": { + "name": "ProfileDetail", + "fields": { + "id": { + "type": "uuid", + "required": true + }, + "bio": { + "type": "string", + "required": false + }, + "profileID": { + "type": "uuid", + "required": true, + "index": true, + "unique": true, + "foreignKey": true, + "foreignKeyType": "NestedProfile", + "foreignKeyField": "id" + }, + "createdAt": { + "type": "datetime", + "required": true + }, + "updatedAt": { + "type": "datetime", + "required": false + } + }, + "pluralForm": "ProfileDetails", + "description": "Additional detail for a profile", + "settings": {} + } + }, + { + "kind": "field_added", + "typeName": "NestedProfile", + "fieldName": "ownerID", + "after": { + "type": "uuid", + "required": false, + "index": true, + "foreignKey": true, + "foreignKeyType": "User", + "foreignKeyField": "id" + } + }, + { + "kind": "type_modified", + "typeName": "NestedProfile", + "reason": "File field \"avatar\" added", + "before": {}, + "after": { + "files": { + "avatar": "profile avatar image" + } + } + } + ], + "hasBreakingChanges": false, + "breakingChanges": [], + "requiresMigrationScript": false +} From 447eddcf79b91af14de2057f7415332f29823e76 Mon Sep 17 00:00:00 2001 From: Akira HIGUCHI Date: Sat, 21 Feb 2026 13:43:37 +0900 Subject: [PATCH 4/7] fix(cli): revert withCompletionCommand to createCompletionCommand pattern withCompletionCommand returns { completion: Command } for spreading into subCommands, but was incorrectly used to wrap the entire root command, causing all CLI subcommands to be unreachable. Co-Authored-By: Claude Opus 4.6 --- packages/sdk/docs/cli/application.md | 76 ++++++------- packages/sdk/docs/cli/auth.md | 48 ++++---- packages/sdk/docs/cli/completion.md | 6 +- packages/sdk/docs/cli/executor.md | 96 ++++++++-------- packages/sdk/docs/cli/secret.md | 82 ++++++------- packages/sdk/docs/cli/staticwebsite.md | 34 +++--- packages/sdk/docs/cli/tailordb.md | 96 ++++++++-------- packages/sdk/docs/cli/user.md | 34 +++--- packages/sdk/docs/cli/workflow.md | 78 ++++++------- packages/sdk/docs/cli/workspace.md | 152 ++++++++++++------------- packages/sdk/src/cli/index.ts | 60 +++++----- 11 files changed, 381 insertions(+), 381 deletions(-) diff --git a/packages/sdk/docs/cli/application.md b/packages/sdk/docs/cli/application.md index 475956f4f..85e285ad3 100644 --- a/packages/sdk/docs/cli/application.md +++ b/packages/sdk/docs/cli/application.md @@ -22,9 +22,9 @@ tailor-sdk init [options] [name] **Options** -| Option | Alias | Description | Required | Default | -| ----------------------- | ----- | ------------- | -------- | ------- | -| `--template