@tailor-platform/sdk 0.16.3 → 0.18.0
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.
- package/CHANGELOG.md +29 -0
- package/README.md +75 -8
- package/dist/cli/api.d.mts +35 -31
- package/dist/cli/api.mjs +2 -2
- package/dist/cli/api.mjs.map +1 -1
- package/dist/cli/index.mjs +51 -75
- package/dist/cli/index.mjs.map +1 -1
- package/dist/configure/index.d.mts +3 -3
- package/dist/{index-Bin7-j3v.d.mts → index-BWqIQ4iC.d.mts} +2 -2
- package/dist/job-CL8myeqs.mjs.map +1 -1
- package/dist/{resume-kyHIaNvK.mjs → resume-ChDChtAZ.mjs} +200 -137
- package/dist/{resume-kyHIaNvK.mjs.map → resume-ChDChtAZ.mjs.map} +1 -1
- package/dist/{types-Da_WnvA0.d.mts → types-DgaCdTug.d.mts} +21 -13
- package/dist/utils/test/index.d.mts +9 -3
- package/dist/utils/test/index.mjs +8 -6
- package/dist/utils/test/index.mjs.map +1 -1
- package/docs/cli/application.md +136 -0
- package/docs/cli/auth.md +110 -0
- package/docs/cli/secret.md +125 -0
- package/docs/cli/user.md +183 -0
- package/docs/cli/workflow.md +144 -0
- package/docs/cli/workspace.md +122 -0
- package/docs/cli-reference.md +80 -801
- package/docs/configuration.md +62 -32
- package/docs/generator/builtin.md +194 -0
- package/docs/generator/custom.md +150 -0
- package/docs/generator/index.md +56 -0
- package/docs/quickstart.md +9 -4
- package/docs/services/auth.md +244 -0
- package/docs/services/executor.md +304 -0
- package/docs/services/idp.md +106 -0
- package/docs/services/resolver.md +213 -0
- package/docs/services/secret.md +116 -0
- package/docs/services/staticwebsite.md +132 -0
- package/docs/services/tailordb.md +325 -0
- package/docs/services/workflow.md +176 -0
- package/docs/testing.md +3 -1
- package/package.json +9 -8
- package/docs/core-concepts.md +0 -609
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Resolver
|
|
2
|
+
|
|
3
|
+
Resolvers are custom GraphQL endpoints with business logic that execute on the Tailor Platform.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Resolvers provide:
|
|
8
|
+
|
|
9
|
+
- Custom GraphQL queries and mutations
|
|
10
|
+
- Type-safe input/output schemas
|
|
11
|
+
- Access to TailorDB via Kysely query builder
|
|
12
|
+
- User context for authentication/authorization
|
|
13
|
+
|
|
14
|
+
## Comparison with Tailor Platform Pipeline Resolver
|
|
15
|
+
|
|
16
|
+
The SDK's Resolver is a simplified version of Tailor Platform's [Pipeline Resolver](https://docs.tailor.tech/guides/pipeline).
|
|
17
|
+
|
|
18
|
+
| Pipeline Resolver | SDK Resolver |
|
|
19
|
+
| ---------------------------------------- | --------------------------------- |
|
|
20
|
+
| Multiple steps with different operations | Single `body` function |
|
|
21
|
+
| Declarative step configuration | Imperative TypeScript code |
|
|
22
|
+
| Built-in TailorDB/GraphQL steps | Direct database access via Kysely |
|
|
23
|
+
| CEL expressions for data transformation | Native TypeScript transformations |
|
|
24
|
+
|
|
25
|
+
### Example Comparison
|
|
26
|
+
|
|
27
|
+
**Pipeline Resolver (Tailor Platform native):**
|
|
28
|
+
|
|
29
|
+
```yaml
|
|
30
|
+
steps:
|
|
31
|
+
- name: getUser
|
|
32
|
+
operation: tailordb.query
|
|
33
|
+
params:
|
|
34
|
+
type: User
|
|
35
|
+
filter:
|
|
36
|
+
email: { eq: "{{ input.email }}" }
|
|
37
|
+
- name: updateAge
|
|
38
|
+
operation: tailordb.mutation
|
|
39
|
+
params:
|
|
40
|
+
type: User
|
|
41
|
+
id: "{{ steps.getUser.id }}"
|
|
42
|
+
input:
|
|
43
|
+
age: "{{ steps.getUser.age + 1 }}"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Resolver (SDK):**
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
createResolver({
|
|
50
|
+
name: "incrementUserAge",
|
|
51
|
+
operation: "mutation",
|
|
52
|
+
input: { email: t.string() },
|
|
53
|
+
body: async (context) => {
|
|
54
|
+
const db = getDB("tailordb");
|
|
55
|
+
const user = await db
|
|
56
|
+
.selectFrom("User")
|
|
57
|
+
.selectAll()
|
|
58
|
+
.where("email", "=", context.input.email)
|
|
59
|
+
.executeTakeFirstOrThrow();
|
|
60
|
+
|
|
61
|
+
await db
|
|
62
|
+
.updateTable("User")
|
|
63
|
+
.set({ age: user.age + 1 })
|
|
64
|
+
.where("id", "=", user.id)
|
|
65
|
+
.execute();
|
|
66
|
+
|
|
67
|
+
return { oldAge: user.age, newAge: user.age + 1 };
|
|
68
|
+
},
|
|
69
|
+
output: t.object({ oldAge: t.int(), newAge: t.int() }),
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Creating a Resolver
|
|
74
|
+
|
|
75
|
+
Define resolvers in files matching glob patterns specified in `tailor.config.ts`.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { createResolver, t } from "@tailor-platform/sdk";
|
|
79
|
+
|
|
80
|
+
export default createResolver({
|
|
81
|
+
name: "add",
|
|
82
|
+
operation: "query",
|
|
83
|
+
input: {
|
|
84
|
+
left: t.int(),
|
|
85
|
+
right: t.int(),
|
|
86
|
+
},
|
|
87
|
+
body: (context) => {
|
|
88
|
+
return {
|
|
89
|
+
result: context.input.left + context.input.right,
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
output: t.object({
|
|
93
|
+
result: t.int(),
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Input/Output Schemas
|
|
99
|
+
|
|
100
|
+
Define input/output schemas using methods of `t` object. Basic usage and supported field types are the same as TailorDB. TailorDB-specific options (e.g., index, relation) are not supported.
|
|
101
|
+
|
|
102
|
+
You can reuse fields defined with `db` object, but note that unsupported options will be ignored:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
const user = db.type("User", {
|
|
106
|
+
name: db.string().unique(),
|
|
107
|
+
age: db.int(),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
createResolver({
|
|
111
|
+
input: {
|
|
112
|
+
name: user.fields.name,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Input Validation
|
|
118
|
+
|
|
119
|
+
Add validation rules to input fields using the `validate` method:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
createResolver({
|
|
123
|
+
name: "createUser",
|
|
124
|
+
operation: "mutation",
|
|
125
|
+
input: {
|
|
126
|
+
email: t
|
|
127
|
+
.string()
|
|
128
|
+
.validate(
|
|
129
|
+
({ value }) => value.includes("@"),
|
|
130
|
+
[
|
|
131
|
+
({ value }) => value.length <= 255,
|
|
132
|
+
"Email must be 255 characters or less",
|
|
133
|
+
],
|
|
134
|
+
),
|
|
135
|
+
age: t.int().validate(({ value }) => value >= 0 && value <= 150),
|
|
136
|
+
},
|
|
137
|
+
body: (context) => {
|
|
138
|
+
// Input is validated before body executes
|
|
139
|
+
return { email: context.input.email };
|
|
140
|
+
},
|
|
141
|
+
output: t.object({ email: t.string() }),
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Validation functions receive:
|
|
146
|
+
|
|
147
|
+
- `value` - The field value being validated
|
|
148
|
+
- `data` - The entire input object
|
|
149
|
+
- `user` - The user performing the operation
|
|
150
|
+
|
|
151
|
+
You can specify validation as:
|
|
152
|
+
|
|
153
|
+
- A function returning `boolean` (uses default error message)
|
|
154
|
+
- A tuple of `[function, errorMessage]` for custom error messages
|
|
155
|
+
- Multiple validators (pass multiple arguments to `validate`)
|
|
156
|
+
|
|
157
|
+
## Body Function
|
|
158
|
+
|
|
159
|
+
Define actual resolver logic in the `body` function. Function arguments include:
|
|
160
|
+
|
|
161
|
+
- `input` - Input data from GraphQL request
|
|
162
|
+
- `user` - User performing the operation
|
|
163
|
+
|
|
164
|
+
### Using Kysely for Database Access
|
|
165
|
+
|
|
166
|
+
If you're generating Kysely types with a generator, you can use `getDB` to execute typed queries:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { getDB } from "../generated/tailordb";
|
|
170
|
+
|
|
171
|
+
createResolver({
|
|
172
|
+
name: "getUser",
|
|
173
|
+
operation: "query",
|
|
174
|
+
input: {
|
|
175
|
+
name: t.string(),
|
|
176
|
+
},
|
|
177
|
+
body: async (context) => {
|
|
178
|
+
const db = getDB("tailordb");
|
|
179
|
+
const result = await db
|
|
180
|
+
.selectFrom("User")
|
|
181
|
+
.select("id")
|
|
182
|
+
.where("name", "=", context.input.name)
|
|
183
|
+
.limit(1)
|
|
184
|
+
.executeTakeFirstOrThrow();
|
|
185
|
+
return {
|
|
186
|
+
result: result.id,
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
output: t.object({
|
|
190
|
+
result: t.uuid(),
|
|
191
|
+
}),
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Query vs Mutation
|
|
196
|
+
|
|
197
|
+
Use `operation: "query"` for read operations and `operation: "mutation"` for write operations:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// Query - for reading data
|
|
201
|
+
createResolver({
|
|
202
|
+
name: "getUsers",
|
|
203
|
+
operation: "query",
|
|
204
|
+
// ...
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Mutation - for creating, updating, or deleting data
|
|
208
|
+
createResolver({
|
|
209
|
+
name: "createUser",
|
|
210
|
+
operation: "mutation",
|
|
211
|
+
// ...
|
|
212
|
+
});
|
|
213
|
+
```
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Secret Manager
|
|
2
|
+
|
|
3
|
+
Secret Manager provides secure storage for sensitive values like API keys, tokens, and credentials that your application needs at runtime.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Secret Manager provides:
|
|
8
|
+
|
|
9
|
+
- Secure storage for sensitive configuration values
|
|
10
|
+
- Organized secrets within named vaults
|
|
11
|
+
- Runtime access from executors and workflows
|
|
12
|
+
- CLI management for secrets lifecycle
|
|
13
|
+
|
|
14
|
+
## Concepts
|
|
15
|
+
|
|
16
|
+
### Vaults
|
|
17
|
+
|
|
18
|
+
Vaults are containers that group related secrets together. Each workspace can have multiple vaults, typically organized by purpose or environment.
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
workspace/
|
|
22
|
+
├── vault: api-keys
|
|
23
|
+
│ ├── stripe-secret-key
|
|
24
|
+
│ ├── sendgrid-api-key
|
|
25
|
+
│ └── external-service-token
|
|
26
|
+
└── vault: database
|
|
27
|
+
├── read-replica-password
|
|
28
|
+
└── analytics-connection-string
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Secrets
|
|
32
|
+
|
|
33
|
+
Secrets are key-value pairs stored within a vault. Secret values are encrypted at rest and only accessible at runtime by authorized services.
|
|
34
|
+
|
|
35
|
+
## Using Secrets
|
|
36
|
+
|
|
37
|
+
### In Webhook Operations
|
|
38
|
+
|
|
39
|
+
Reference secrets in webhook headers using the vault/key syntax:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { createExecutor, recordCreatedTrigger } from "@tailor-platform/sdk";
|
|
43
|
+
import { order } from "../tailordb/order";
|
|
44
|
+
|
|
45
|
+
export default createExecutor({
|
|
46
|
+
name: "notify-external-service",
|
|
47
|
+
trigger: recordCreatedTrigger({ type: order }),
|
|
48
|
+
operation: {
|
|
49
|
+
kind: "webhook",
|
|
50
|
+
url: "https://api.example.com/orders",
|
|
51
|
+
headers: {
|
|
52
|
+
"Content-Type": "application/json",
|
|
53
|
+
Authorization: { vault: "api-keys", key: "external-api-token" },
|
|
54
|
+
"X-API-Key": { vault: "api-keys", key: "api-secret" },
|
|
55
|
+
},
|
|
56
|
+
requestBody: ({ newRecord }) => ({
|
|
57
|
+
orderId: newRecord.id,
|
|
58
|
+
amount: newRecord.total,
|
|
59
|
+
}),
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The secret reference format:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
{ vault: "vault-name", key: "secret-name" }
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
At runtime, these references are replaced with the actual secret values.
|
|
71
|
+
|
|
72
|
+
## CLI Management
|
|
73
|
+
|
|
74
|
+
### Create a Vault
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
tailor-sdk secret vault create --name api-keys
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Add Secrets
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Create a secret
|
|
84
|
+
tailor-sdk secret create \
|
|
85
|
+
--vault-name api-keys \
|
|
86
|
+
--name stripe-secret-key \
|
|
87
|
+
--value sk_live_xxxxx
|
|
88
|
+
|
|
89
|
+
# Update a secret
|
|
90
|
+
tailor-sdk secret update \
|
|
91
|
+
--vault-name api-keys \
|
|
92
|
+
--name stripe-secret-key \
|
|
93
|
+
--value sk_live_yyyyy
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### List Secrets
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# List vaults
|
|
100
|
+
tailor-sdk secret vault list
|
|
101
|
+
|
|
102
|
+
# List secrets in a vault (values are hidden)
|
|
103
|
+
tailor-sdk secret list --vault-name api-keys
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Delete Secrets
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Delete a secret
|
|
110
|
+
tailor-sdk secret delete --vault-name api-keys --name old-key --yes
|
|
111
|
+
|
|
112
|
+
# Delete a vault (must be empty)
|
|
113
|
+
tailor-sdk secret vault delete --name old-vault --yes
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
See [Secret CLI Commands](../cli/secret.md) for full documentation.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Static Website
|
|
2
|
+
|
|
3
|
+
Static Website is a service for hosting static web applications on the Tailor Platform.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Static Website provides:
|
|
8
|
+
|
|
9
|
+
- Static file hosting
|
|
10
|
+
- Type-safe URL references for configuration
|
|
11
|
+
- IP address restrictions
|
|
12
|
+
|
|
13
|
+
For the official Tailor Platform documentation, see [Static Website Guide](https://docs.tailor.tech/guides/static-website-hosting).
|
|
14
|
+
|
|
15
|
+
## Configuration
|
|
16
|
+
|
|
17
|
+
Configure static website hosting using `defineStaticWebSite()`:
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { defineStaticWebSite, defineConfig } from "@tailor-platform/sdk";
|
|
21
|
+
|
|
22
|
+
const website = defineStaticWebSite("my-website", {
|
|
23
|
+
description: "My Static Website",
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export default defineConfig({
|
|
27
|
+
staticWebsites: [website],
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Options
|
|
32
|
+
|
|
33
|
+
### description
|
|
34
|
+
|
|
35
|
+
A description of the static website:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
defineStaticWebSite("my-website", {
|
|
39
|
+
description: "Frontend application for my service",
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### allowedIPAddresses
|
|
44
|
+
|
|
45
|
+
Restrict access to specific IP addresses in CIDR format:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
defineStaticWebSite("my-website", {
|
|
49
|
+
allowedIPAddresses: ["192.168.0.0/24", "10.0.0.0/8"],
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Type-safe URL References
|
|
54
|
+
|
|
55
|
+
The returned website object provides a `url` property that resolves to the actual URL at deployment time. Use this for type-safe configuration:
|
|
56
|
+
|
|
57
|
+
### CORS Settings
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
const website = defineStaticWebSite("my-frontend", {
|
|
61
|
+
description: "Frontend application",
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export default defineConfig({
|
|
65
|
+
cors: [website.url], // Resolved at deployment
|
|
66
|
+
staticWebsites: [website],
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### OAuth2 Redirect URIs
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const website = defineStaticWebSite("my-frontend", {
|
|
74
|
+
description: "Frontend application",
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const auth = defineAuth("my-auth", {
|
|
78
|
+
oauth2Clients: {
|
|
79
|
+
"my-client": {
|
|
80
|
+
redirectURIs: [
|
|
81
|
+
`${website.url}/callback`, // https://my-frontend.example.com/callback
|
|
82
|
+
`${website.url}/auth/complete`, // https://my-frontend.example.com/auth/complete
|
|
83
|
+
],
|
|
84
|
+
grantTypes: ["authorization_code", "refresh_token"],
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Complete Example
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import {
|
|
94
|
+
defineConfig,
|
|
95
|
+
defineAuth,
|
|
96
|
+
defineIdp,
|
|
97
|
+
defineStaticWebSite,
|
|
98
|
+
} from "@tailor-platform/sdk";
|
|
99
|
+
import { user } from "./tailordb/user";
|
|
100
|
+
|
|
101
|
+
const website = defineStaticWebSite("my-frontend", {
|
|
102
|
+
description: "Frontend application",
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const idp = defineIdp("my-idp", {
|
|
106
|
+
authorization: "loggedIn",
|
|
107
|
+
clients: ["default-client"],
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const auth = defineAuth("my-auth", {
|
|
111
|
+
userProfile: {
|
|
112
|
+
type: user,
|
|
113
|
+
usernameField: "email",
|
|
114
|
+
attributes: { role: true },
|
|
115
|
+
},
|
|
116
|
+
oauth2Clients: {
|
|
117
|
+
"frontend-client": {
|
|
118
|
+
redirectURIs: [`${website.url}/callback`],
|
|
119
|
+
grantTypes: ["authorization_code", "refresh_token"],
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
idProvider: idp.provider("default", "default-client"),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
export default defineConfig({
|
|
126
|
+
name: "my-app",
|
|
127
|
+
cors: [website.url],
|
|
128
|
+
idp: [idp],
|
|
129
|
+
auth,
|
|
130
|
+
staticWebsites: [website],
|
|
131
|
+
});
|
|
132
|
+
```
|