@tailor-platform/create-sdk 0.0.1 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +448 -0
  2. package/LICENSE +21 -0
  3. package/README.md +23 -33
  4. package/dist/index.js +180 -0
  5. package/package.json +41 -8
  6. package/templates/hello-world/.gitignore +3 -0
  7. package/templates/hello-world/.prettierignore +1 -0
  8. package/templates/hello-world/.prettierrc +1 -0
  9. package/templates/hello-world/README.md +59 -0
  10. package/templates/hello-world/eslint.config.js +27 -0
  11. package/templates/hello-world/package.json +22 -0
  12. package/templates/hello-world/src/resolvers/hello.ts +19 -0
  13. package/templates/hello-world/tailor.config.ts +6 -0
  14. package/templates/hello-world/tsconfig.json +15 -0
  15. package/templates/inventory-management/.gitignore +3 -0
  16. package/templates/inventory-management/.prettierignore +2 -0
  17. package/templates/inventory-management/.prettierrc +1 -0
  18. package/templates/inventory-management/README.md +246 -0
  19. package/templates/inventory-management/eslint.config.js +33 -0
  20. package/templates/inventory-management/package.json +28 -0
  21. package/templates/inventory-management/src/db/category.ts +13 -0
  22. package/templates/inventory-management/src/db/common/permission.ts +59 -0
  23. package/templates/inventory-management/src/db/contact.ts +17 -0
  24. package/templates/inventory-management/src/db/inventory.ts +18 -0
  25. package/templates/inventory-management/src/db/notification.ts +21 -0
  26. package/templates/inventory-management/src/db/order.ts +20 -0
  27. package/templates/inventory-management/src/db/orderItem.ts +36 -0
  28. package/templates/inventory-management/src/db/product.ts +18 -0
  29. package/templates/inventory-management/src/db/user.ts +12 -0
  30. package/templates/inventory-management/src/executor/checkInventory.ts +28 -0
  31. package/templates/inventory-management/src/generated/kysely-tailordb.ts +92 -0
  32. package/templates/inventory-management/src/pipeline/registerOrder.ts +100 -0
  33. package/templates/inventory-management/tailor.config.ts +35 -0
  34. package/templates/inventory-management/tsconfig.json +16 -0
  35. package/templates/testing/.gitignore +3 -0
  36. package/templates/testing/.prettierignore +2 -0
  37. package/templates/testing/.prettierrc +1 -0
  38. package/templates/testing/README.md +130 -0
  39. package/templates/testing/e2e/globalSetup.ts +65 -0
  40. package/templates/testing/e2e/resolver.test.ts +57 -0
  41. package/templates/testing/eslint.config.js +27 -0
  42. package/templates/testing/package.json +33 -0
  43. package/templates/testing/src/db/user.ts +22 -0
  44. package/templates/testing/src/generated/db.ts +28 -0
  45. package/templates/testing/src/resolver/mockTailordb.test.ts +71 -0
  46. package/templates/testing/src/resolver/mockTailordb.ts +44 -0
  47. package/templates/testing/src/resolver/simple.test.ts +13 -0
  48. package/templates/testing/src/resolver/simple.ts +16 -0
  49. package/templates/testing/src/resolver/wrapTailordb.test.ts +53 -0
  50. package/templates/testing/src/resolver/wrapTailordb.ts +80 -0
  51. package/templates/testing/tailor.config.ts +23 -0
  52. package/templates/testing/tsconfig.json +16 -0
  53. package/templates/testing/vitest.config.ts +22 -0
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "inventory-management",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "deploy": "tailor-sdk apply",
7
+ "generate": "tailor-sdk generate",
8
+ "format": "prettier --write .",
9
+ "format:check": "prettier --check .",
10
+ "lint": "eslint --cache .",
11
+ "lint:fix": "eslint --cache --fix .",
12
+ "typecheck": "tsc --noEmit"
13
+ },
14
+ "dependencies": {
15
+ "@tailor-platform/function-kysely-tailordb": "0.1.3",
16
+ "kysely": "0.28.7"
17
+ },
18
+ "devDependencies": {
19
+ "@eslint/js": "9.39.1",
20
+ "@tailor-platform/function-types": "0.7.2",
21
+ "@tailor-platform/sdk": "0.8.1",
22
+ "@types/node": "22.19.0",
23
+ "eslint": "9.39.1",
24
+ "prettier": "3.6.2",
25
+ "typescript": "5.9.3",
26
+ "typescript-eslint": "8.46.3"
27
+ }
28
+ }
@@ -0,0 +1,13 @@
1
+ import { db } from "@tailor-platform/sdk";
2
+ import { gqlPermissionManager, permissionManager } from "./common/permission";
3
+
4
+ export const category = db
5
+ .type("Category", {
6
+ name: db.string().description("Name of the category").unique(),
7
+ description: db
8
+ .string({ optional: true })
9
+ .description("Description of the category"),
10
+ ...db.fields.timestamps(),
11
+ })
12
+ .permission(permissionManager)
13
+ .gqlPermission(gqlPermissionManager);
@@ -0,0 +1,59 @@
1
+ import type {
2
+ PermissionCondition,
3
+ TailorTypeGqlPermission,
4
+ TailorTypePermission,
5
+ } from "@tailor-platform/sdk";
6
+
7
+ export interface User {
8
+ role: string;
9
+ }
10
+
11
+ export const managerRole = [
12
+ { user: "role" },
13
+ "=",
14
+ "MANAGER",
15
+ ] as const satisfies PermissionCondition;
16
+ export const loggedIn = [
17
+ { user: "_loggedIn" },
18
+ "=",
19
+ true,
20
+ ] as const satisfies PermissionCondition;
21
+
22
+ // Manager can do anything, Staff can only read.
23
+ export const permissionManager = {
24
+ create: [managerRole],
25
+ read: [loggedIn],
26
+ update: [managerRole],
27
+ delete: [managerRole],
28
+ } as const satisfies TailorTypePermission;
29
+
30
+ // Manager can perform any GraphQL operations, Staff can only read.
31
+ export const gqlPermissionManager = [
32
+ {
33
+ conditions: [managerRole],
34
+ actions: "all",
35
+ permit: true,
36
+ },
37
+ {
38
+ conditions: [loggedIn],
39
+ actions: ["read"],
40
+ permit: true,
41
+ },
42
+ ] as const satisfies TailorTypeGqlPermission;
43
+
44
+ // Any logged-in user can do anything.
45
+ export const permissionLoggedIn = {
46
+ create: [loggedIn],
47
+ read: [loggedIn],
48
+ update: [loggedIn],
49
+ delete: [loggedIn],
50
+ } as const satisfies TailorTypePermission;
51
+
52
+ // Any logged-in user can perform read GraphQL operation.
53
+ export const gqlPermissionLoggedIn = [
54
+ {
55
+ conditions: [loggedIn],
56
+ actions: ["read"],
57
+ permit: true,
58
+ },
59
+ ] as const satisfies TailorTypeGqlPermission;
@@ -0,0 +1,17 @@
1
+ import { db } from "@tailor-platform/sdk";
2
+ import { gqlPermissionManager, permissionManager } from "./common/permission";
3
+
4
+ export const contact = db
5
+ .type("Contact", {
6
+ name: db.string().description("Name of the contact"),
7
+ email: db.string().unique().description("Email address of the contact"),
8
+ phone: db
9
+ .string({ optional: true })
10
+ .description("Phone number of the contact"),
11
+ address: db
12
+ .string({ optional: true })
13
+ .description("Address of the contact"),
14
+ ...db.fields.timestamps(),
15
+ })
16
+ .permission(permissionManager)
17
+ .gqlPermission(gqlPermissionManager);
@@ -0,0 +1,18 @@
1
+ import { db } from "@tailor-platform/sdk";
2
+ import { gqlPermissionLoggedIn, permissionLoggedIn } from "./common/permission";
3
+ import { product } from "./product";
4
+
5
+ export const inventory = db
6
+ .type("Inventory", {
7
+ productId: db
8
+ .uuid()
9
+ .description("ID of the product")
10
+ .relation({ type: "1-1", toward: { type: product } }),
11
+ quantity: db
12
+ .int()
13
+ .description("Quantity of the product in inventory")
14
+ .validate(({ value }) => value >= 0),
15
+ ...db.fields.timestamps(),
16
+ })
17
+ .permission(permissionLoggedIn)
18
+ .gqlPermission(gqlPermissionLoggedIn);
@@ -0,0 +1,21 @@
1
+ import { db } from "@tailor-platform/sdk";
2
+ import { loggedIn, managerRole, permissionManager } from "./common/permission";
3
+
4
+ export const notification = db
5
+ .type("Notification", {
6
+ message: db.string().description("Notification message"),
7
+ ...db.fields.timestamps(),
8
+ })
9
+ .permission(permissionManager)
10
+ .gqlPermission([
11
+ {
12
+ conditions: [managerRole],
13
+ actions: ["delete"],
14
+ permit: true,
15
+ },
16
+ {
17
+ conditions: [loggedIn],
18
+ actions: ["read"],
19
+ permit: true,
20
+ },
21
+ ]);
@@ -0,0 +1,20 @@
1
+ import { db } from "@tailor-platform/sdk";
2
+ import { contact } from "./contact";
3
+ import { gqlPermissionLoggedIn, permissionLoggedIn } from "./common/permission";
4
+
5
+ export const order = db
6
+ .type("Order", {
7
+ name: db.string().description("Name of the order"),
8
+ description: db
9
+ .string({ optional: true })
10
+ .description("Description of the order"),
11
+ orderDate: db.datetime().description("Date of the order"),
12
+ orderType: db.enum("PURCHASE", "SALES").description("Type of the order"),
13
+ contactId: db
14
+ .uuid()
15
+ .description("Contact associated with the order")
16
+ .relation({ type: "n-1", toward: { type: contact } }),
17
+ ...db.fields.timestamps(),
18
+ })
19
+ .permission(permissionLoggedIn)
20
+ .gqlPermission(gqlPermissionLoggedIn);
@@ -0,0 +1,36 @@
1
+ import { db } from "@tailor-platform/sdk";
2
+ import { order } from "./order";
3
+ import { product } from "./product";
4
+ import { gqlPermissionLoggedIn, permissionLoggedIn } from "./common/permission";
5
+
6
+ export const orderItem = db
7
+ .type("OrderItem", {
8
+ orderId: db
9
+ .uuid()
10
+ .description("ID of the order")
11
+ .relation({ type: "n-1", toward: { type: order } }),
12
+ productId: db
13
+ .uuid()
14
+ .description("ID of the product")
15
+ .relation({ type: "n-1", toward: { type: product } }),
16
+ quantity: db
17
+ .int()
18
+ .description("Quantity of the product")
19
+ .validate(({ value }) => value >= 0),
20
+ unitPrice: db
21
+ .float()
22
+ .description("Unit price of the product")
23
+ .validate(({ value }) => value >= 0),
24
+ totalPrice: db
25
+ .float({ optional: true, assertNonNull: true })
26
+ .description("Total price of the order item"),
27
+ ...db.fields.timestamps(),
28
+ })
29
+ .hooks({
30
+ totalPrice: {
31
+ create: ({ data }) => (data?.quantity ?? 0) * (data.unitPrice ?? 0),
32
+ update: ({ data }) => (data?.quantity ?? 0) * (data.unitPrice ?? 0),
33
+ },
34
+ })
35
+ .permission(permissionLoggedIn)
36
+ .gqlPermission(gqlPermissionLoggedIn);
@@ -0,0 +1,18 @@
1
+ import { db } from "@tailor-platform/sdk";
2
+ import { category } from "./category";
3
+ import { gqlPermissionManager, permissionManager } from "./common/permission";
4
+
5
+ export const product = db
6
+ .type("Product", {
7
+ name: db.string().description("Name of the product"),
8
+ description: db
9
+ .string({ optional: true })
10
+ .description("Description of the product"),
11
+ categoryId: db
12
+ .uuid()
13
+ .description("ID of the category the product belongs to")
14
+ .relation({ type: "n-1", toward: { type: category } }),
15
+ ...db.fields.timestamps(),
16
+ })
17
+ .permission(permissionManager)
18
+ .gqlPermission(gqlPermissionManager);
@@ -0,0 +1,12 @@
1
+ import { db } from "@tailor-platform/sdk";
2
+ import { gqlPermissionManager, permissionManager } from "./common/permission";
3
+
4
+ export const user = db
5
+ .type("User", {
6
+ name: db.string().description("Name of the user"),
7
+ email: db.string().unique().description("Email address of the user"),
8
+ role: db.enum("MANAGER", "STAFF"),
9
+ ...db.fields.timestamps(),
10
+ })
11
+ .permission(permissionManager)
12
+ .gqlPermission(gqlPermissionManager);
@@ -0,0 +1,28 @@
1
+ import { createExecutor, recordUpdatedTrigger } from "@tailor-platform/sdk";
2
+ import { inventory } from "../db/inventory";
3
+ import config from "../../tailor.config";
4
+ import { getDB } from "../generated/kysely-tailordb";
5
+
6
+ export default createExecutor({
7
+ name: "check-inventory",
8
+ description: "Notify when inventory drops below threshold",
9
+ trigger: recordUpdatedTrigger({
10
+ type: inventory,
11
+ condition: ({ oldRecord, newRecord }) =>
12
+ oldRecord.quantity >= 10 && newRecord.quantity < 10,
13
+ }),
14
+ operation: {
15
+ kind: "function",
16
+ body: async ({ newRecord }) => {
17
+ const db = getDB("main-db");
18
+
19
+ await db
20
+ .insertInto("Notification")
21
+ .values({
22
+ message: `Inventory for product ${newRecord.productId} is below threshold. Current quantity: ${newRecord.quantity}`,
23
+ })
24
+ .execute();
25
+ },
26
+ invoker: config.auth.invoker("manager"),
27
+ },
28
+ });
@@ -0,0 +1,92 @@
1
+ import { type ColumnType, Kysely } from "kysely";
2
+ import { TailordbDialect } from "@tailor-platform/function-kysely-tailordb";
3
+
4
+ type Timestamp = ColumnType<Date, Date | string, Date | string>;
5
+ type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
6
+ ? ColumnType<S, I | undefined, U>
7
+ : ColumnType<T, T | undefined, T>;
8
+ type Serial<T = string | number> = ColumnType<T, never, never>;
9
+
10
+ export interface Namespace {
11
+ "main-db": {
12
+ Category: {
13
+ id: Generated<string>;
14
+ name: string;
15
+ description: string | null;
16
+ createdAt: Generated<Timestamp>;
17
+ updatedAt: Timestamp | null;
18
+ }
19
+
20
+ Contact: {
21
+ id: Generated<string>;
22
+ name: string;
23
+ email: string;
24
+ phone: string | null;
25
+ address: string | null;
26
+ createdAt: Generated<Timestamp>;
27
+ updatedAt: Timestamp | null;
28
+ }
29
+
30
+ Inventory: {
31
+ id: Generated<string>;
32
+ productId: string;
33
+ quantity: number;
34
+ createdAt: Generated<Timestamp>;
35
+ updatedAt: Timestamp | null;
36
+ }
37
+
38
+ Notification: {
39
+ id: Generated<string>;
40
+ message: string;
41
+ createdAt: Generated<Timestamp>;
42
+ updatedAt: Timestamp | null;
43
+ }
44
+
45
+ Order: {
46
+ id: Generated<string>;
47
+ name: string;
48
+ description: string | null;
49
+ orderDate: Timestamp;
50
+ orderType: "PURCHASE" | "SALES";
51
+ contactId: string;
52
+ createdAt: Generated<Timestamp>;
53
+ updatedAt: Timestamp | null;
54
+ }
55
+
56
+ OrderItem: {
57
+ id: Generated<string>;
58
+ orderId: string;
59
+ productId: string;
60
+ quantity: number;
61
+ unitPrice: number;
62
+ totalPrice: Generated<number | null>;
63
+ createdAt: Generated<Timestamp>;
64
+ updatedAt: Timestamp | null;
65
+ }
66
+
67
+ Product: {
68
+ id: Generated<string>;
69
+ name: string;
70
+ description: string | null;
71
+ categoryId: string;
72
+ createdAt: Generated<Timestamp>;
73
+ updatedAt: Timestamp | null;
74
+ }
75
+
76
+ User: {
77
+ id: Generated<string>;
78
+ name: string;
79
+ email: string;
80
+ role: "MANAGER" | "STAFF";
81
+ createdAt: Generated<Timestamp>;
82
+ updatedAt: Timestamp | null;
83
+ }
84
+ }
85
+ }
86
+
87
+ export function getDB<const N extends keyof Namespace>(namespace: N): Kysely<Namespace[N]> {
88
+ const client = new tailordb.Client({ namespace });
89
+ return new Kysely<Namespace[N]>({ dialect: new TailordbDialect(client) });
90
+ }
91
+
92
+ export type DB<N extends keyof Namespace = keyof Namespace> = ReturnType<typeof getDB<N>>;
@@ -0,0 +1,100 @@
1
+ import { createResolver, t } from "@tailor-platform/sdk";
2
+ import { order } from "../db/order";
3
+ import { orderItem } from "../db/orderItem";
4
+ import { type DB, getDB } from "../generated/kysely-tailordb";
5
+
6
+ const input = {
7
+ order: t.object(order.omitFields(["id", "createdAt"])),
8
+ items: t.object(orderItem.omitFields(["id", "createdAt"]), { array: true }),
9
+ };
10
+ interface Input {
11
+ order: t.infer<typeof input.order>;
12
+ items: t.infer<typeof input.items>;
13
+ }
14
+
15
+ const insertOrder = async (db: DB<"main-db">, input: Input) => {
16
+ // Insert Order
17
+ const order = await db
18
+ .insertInto("Order")
19
+ .values(input.order)
20
+ .returning("id")
21
+ .executeTakeFirstOrThrow();
22
+
23
+ // Insert OrderItems
24
+ await db
25
+ .insertInto("OrderItem")
26
+ .values(
27
+ input.items.map((item) => ({
28
+ ...item,
29
+ orderId: order.id,
30
+ })),
31
+ )
32
+ .execute();
33
+ };
34
+
35
+ const updateInventory = async (db: DB<"main-db">, input: Input) => {
36
+ for (const item of input.items) {
37
+ const inventory = await db
38
+ .selectFrom("Inventory")
39
+ .selectAll()
40
+ .where("productId", "=", item.productId)
41
+ .forUpdate()
42
+ .executeTakeFirst();
43
+
44
+ // If inventory already exists, update it.
45
+ // Otherwise, create it (only for PURCHASE order).
46
+ if (inventory) {
47
+ let quantity: number;
48
+ if (input.order.orderType === "PURCHASE") {
49
+ quantity = inventory.quantity + item.quantity;
50
+ } else {
51
+ quantity = inventory.quantity - item.quantity;
52
+ }
53
+ if (quantity < 0) {
54
+ throw new Error(
55
+ `Cannot create order because inventory is not enough. productId: ${item.productId}`,
56
+ );
57
+ }
58
+ await db
59
+ .updateTable("Inventory")
60
+ .set({ quantity })
61
+ .where("id", "=", inventory.id)
62
+ .execute();
63
+ } else {
64
+ if (input.order.orderType === "PURCHASE") {
65
+ await db
66
+ .insertInto("Inventory")
67
+ .values({
68
+ productId: item.productId,
69
+ quantity: item.quantity,
70
+ })
71
+ .execute();
72
+ } else {
73
+ throw new Error(
74
+ `Cannot create order because inventory is not enough. productId: ${item.productId}`,
75
+ );
76
+ }
77
+ }
78
+ }
79
+ };
80
+
81
+ export default createResolver({
82
+ name: "registerOrder",
83
+ operation: "mutation",
84
+ input,
85
+ body: async (context) => {
86
+ const db = getDB("main-db");
87
+ await db.transaction().execute(async (trx) => {
88
+ await insertOrder(trx, context.input);
89
+ await updateInventory(trx, context.input);
90
+ });
91
+ return { success: true };
92
+ },
93
+ output: t
94
+ .object({
95
+ success: t
96
+ .bool()
97
+ .description("Whether the order was registered successfully"),
98
+ })
99
+ .description("Result of order registration"),
100
+ });
@@ -0,0 +1,35 @@
1
+ import {
2
+ defineAuth,
3
+ defineConfig,
4
+ defineGenerators,
5
+ } from "@tailor-platform/sdk";
6
+ import { user } from "./src/db/user";
7
+
8
+ export default defineConfig({
9
+ name: "inventory-management",
10
+ db: { "main-db": { files: [`./src/db/*.ts`] } },
11
+ resolver: { "main-resolver": { files: [`./src/pipeline/*.ts`] } },
12
+ auth: defineAuth("main-auth", {
13
+ userProfile: {
14
+ type: user,
15
+ usernameField: "email",
16
+ attributes: {
17
+ role: true,
18
+ },
19
+ },
20
+ machineUsers: {
21
+ manager: {
22
+ attributes: { role: "MANAGER" },
23
+ },
24
+ staff: {
25
+ attributes: { role: "STAFF" },
26
+ },
27
+ },
28
+ }),
29
+ executor: { files: ["./src/executor/*.ts"] },
30
+ });
31
+
32
+ export const generators = defineGenerators([
33
+ "@tailor-platform/kysely-type",
34
+ { distPath: `./src/generated/kysely-tailordb.ts` },
35
+ ]);
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "allowSyntheticDefaultImports": true,
7
+ "esModuleInterop": true,
8
+ "allowJs": true,
9
+ "strict": true,
10
+ "noEmit": true,
11
+ "skipLibCheck": true,
12
+ "resolveJsonModule": true,
13
+ "types": ["node", "@tailor-platform/function-types"]
14
+ },
15
+ "include": ["src/**/*", "tailor.config.ts"]
16
+ }
@@ -0,0 +1,3 @@
1
+ node_modules/
2
+ .tailor-sdk/
3
+ .eslintcache
@@ -0,0 +1,2 @@
1
+ src/generated/
2
+ pnpm-lock.yaml
@@ -0,0 +1 @@
1
+ {}
@@ -0,0 +1,130 @@
1
+ # Testing Guide
2
+
3
+ This guide covers testing patterns for Tailor Platform SDK applications using [Vitest](https://vitest.dev/).
4
+
5
+ This project was bootstrapped with [Create Tailor Platform SDK](https://www.npmjs.com/package/@tailor-platform/create-sdk).
6
+
7
+ ## Quick Start
8
+
9
+ ### Unit Tests
10
+
11
+ Run unit tests locally without deployment:
12
+
13
+ ```bash
14
+ npm run test:unit
15
+ ```
16
+
17
+ ### End-to-End (E2E) Tests
18
+
19
+ E2E tests require a deployed application.
20
+
21
+ #### Local Development
22
+
23
+ ```bash
24
+ # 1. Login
25
+ npx tailor-sdk login
26
+
27
+ # 2. Create workspace
28
+ npx tailor-sdk workspace create --name <workspace-name> --region <workspace-region>
29
+
30
+ # 3. Deploy application
31
+ export TAILOR_PLATFORM_WORKSPACE_ID=<your-workspace-id>
32
+ npm run deploy
33
+
34
+ # 4. Run E2E tests
35
+ npm run test:e2e
36
+ ```
37
+
38
+ #### CI/CD (Automated Workspace Lifecycle)
39
+
40
+ ```bash
41
+ npx tailor-sdk login
42
+ export CI=true
43
+ export TAILOR_PLATFORM_WORKSPACE_NAME=<workspace-name>
44
+ export TAILOR_PLATFORM_WORKSPACE_REGION=<workspace-region>
45
+ npm run test:e2e # Automatically creates, deploys, tests, and deletes workspace
46
+ ```
47
+
48
+ ## Testing Patterns
49
+
50
+ ### Unit Tests
51
+
52
+ Unit tests verify resolver logic without requiring deployment.
53
+
54
+ #### Simple Resolver Testing
55
+
56
+ **Example:** [src/resolver/simple.test.ts](src/resolver/simple.test.ts)
57
+
58
+ Test resolvers by directly calling `resolver.body()` with mock inputs.
59
+
60
+ **Key points:**
61
+
62
+ - Use `unauthenticatedTailorUser` for testing logic that doesn't depend on user context
63
+ - **Best for:** Calculations, data transformations without database dependencies
64
+
65
+ #### Mock TailorDB Client
66
+
67
+ **Example:** [src/resolver/mockTailordb.test.ts](src/resolver/mockTailordb.test.ts)
68
+
69
+ Mock the global `tailordb.Client` using `vi.stubGlobal()` to simulate database operations and control responses for each query.
70
+
71
+ **Key points:**
72
+
73
+ - Control exact database responses (query results, errors)
74
+ - Verify database interaction flow (transactions, queries)
75
+ - Test transaction rollback scenarios
76
+ - **Best for:** Business logic with simple database operations
77
+
78
+ #### Dependency Injection Pattern
79
+
80
+ **Example:** [src/resolver/wrapTailordb.test.ts](src/resolver/wrapTailordb.test.ts)
81
+
82
+ Extract database operations into a `DbOperations` interface, allowing business logic to be tested independently from Kysely implementation.
83
+
84
+ **Key points:**
85
+
86
+ - Test business logic independently from Kysely implementation details
87
+ - Mock high-level operations instead of low-level SQL queries
88
+ - **Best for:** Complex business logic with multiple database operations
89
+
90
+ ### End-to-End (E2E) Tests
91
+
92
+ E2E tests verify your application works correctly when deployed to Tailor Platform. They test the full stack including GraphQL API, database operations, and authentication.
93
+
94
+ **Examples:** [e2e/resolver.test.ts](e2e/resolver.test.ts), [e2e/globalSetup.ts](e2e/globalSetup.ts)
95
+
96
+ #### How It Works
97
+
98
+ **1. Global Setup** ([e2e/globalSetup.ts](e2e/globalSetup.ts))
99
+
100
+ Before running tests, `globalSetup` retrieves deployment information:
101
+
102
+ - Application URL via `show()`
103
+ - Machine user access token via `machineUserToken()`
104
+ - Provides credentials to tests via `inject("url")` and `inject("token")`
105
+
106
+ **2. Test Files** ([e2e/resolver.test.ts](e2e/resolver.test.ts))
107
+
108
+ Tests create a GraphQL client using injected credentials and send real queries/mutations to the deployed application.
109
+
110
+ **Key points:**
111
+
112
+ - Tests run against actual deployed application
113
+ - `inject("url")` and `inject("token")` provide deployment credentials automatically
114
+ - Machine user authentication enables API access without manual token management
115
+ - Verify database persistence and API contracts
116
+ - **Best for:** Integration testing, end-to-end API validation
117
+
118
+ ## Available Scripts
119
+
120
+ | Script | Description |
121
+ | -------------- | -------------------------------------------- |
122
+ | `generate` | Generate TypeScript types from configuration |
123
+ | `deploy` | Deploy application to Tailor Platform |
124
+ | `test:unit` | Run unit tests locally |
125
+ | `test:e2e` | Run E2E tests against deployed application |
126
+ | `format` | Format code with Prettier |
127
+ | `format:check` | Check code formatting |
128
+ | `lint` | Lint code with ESLint |
129
+ | `lint:fix` | Fix linting issues automatically |
130
+ | `typecheck` | Run TypeScript type checking |