@tailor-platform/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.
- package/CHANGELOG.md +790 -0
- package/LICENSE +21 -0
- package/README.md +8 -41
- package/dist/auth-Di3vQUrT.mjs +743 -0
- package/dist/cli/api.d.mts +213 -0
- package/dist/cli/api.mjs +4 -0
- package/dist/cli/index.d.mts +3 -0
- package/dist/cli/index.mjs +996 -0
- package/dist/configure/index.d.mts +5 -0
- package/dist/configure/index.mjs +108 -0
- package/dist/index-BWN4RmSt.d.mts +232 -0
- package/dist/plugin-generated.d.ts +14 -0
- package/dist/token-B9YK0eTP.mjs +5498 -0
- package/dist/types-DWQxkbYl.d.mts +1389 -0
- package/dist/utils/test/index.d.mts +40 -0
- package/dist/utils/test/index.mjs +63 -0
- package/docs/cli-reference.md +484 -0
- package/docs/configuration.md +132 -0
- package/docs/core-concepts.md +504 -0
- package/docs/quickstart.md +96 -0
- package/docs/testing.md +298 -0
- package/package.json +87 -8
- package/postinstall.mjs +87 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
# Conceptual Guides
|
|
2
|
+
|
|
3
|
+
### TailorDB Concepts
|
|
4
|
+
|
|
5
|
+
Define TailorDB Types in files matching glob patterns specified in `tailor.config.ts`.
|
|
6
|
+
|
|
7
|
+
#### Field Types
|
|
8
|
+
|
|
9
|
+
Define TailorDB Fields using methods like `db.string()`, `db.int()`, etc. All TailorDB Field types are supported:
|
|
10
|
+
|
|
11
|
+
| Method | TailorDB | TypeScript |
|
|
12
|
+
| --------------- | -------- | ---------- |
|
|
13
|
+
| `db.string()` | String | string |
|
|
14
|
+
| `db.int()` | Integer | number |
|
|
15
|
+
| `db.float()` | Float | number |
|
|
16
|
+
| `db.bool()` | Boolean | boolean |
|
|
17
|
+
| `db.date()` | Date | string |
|
|
18
|
+
| `db.datetime()` | DateTime | string |
|
|
19
|
+
| `db.time()` | Time | string |
|
|
20
|
+
| `db.uuid()` | UUID | string |
|
|
21
|
+
| `db.enum()` | Enum | string |
|
|
22
|
+
| `db.object()` | Nested | object |
|
|
23
|
+
|
|
24
|
+
**Enum fields** - specify allowed values as arguments:
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
db.enum("red", "green", "blue");
|
|
28
|
+
db.enum(
|
|
29
|
+
{ value: "active", description: "Active status" },
|
|
30
|
+
{ value: "inactive", description: "Inactive status" },
|
|
31
|
+
);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Object fields** - specify field structure as an argument:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
db.object({
|
|
38
|
+
street: db.string(),
|
|
39
|
+
city: db.string(),
|
|
40
|
+
country: db.string(),
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
#### Optional and Array Fields
|
|
45
|
+
|
|
46
|
+
Make fields optional or arrays by specifying options:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
db.string({ optional: true });
|
|
50
|
+
db.string({ array: true });
|
|
51
|
+
db.string({ optional: true, array: true });
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The `assertNonNull` option creates a field that doesn't allow null in TypeScript types but does allow null in TailorDB. This is useful for fields like createdAt where non-null values are set through hooks:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
db.string({ optional: true, assertNonNull: true }).hooks({
|
|
58
|
+
create: () => "created value",
|
|
59
|
+
update: () => "updated value",
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### Field Modifiers
|
|
64
|
+
|
|
65
|
+
**Description** - Add a description to field:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
db.string().description("User's full name");
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Index / Unique** - Add an index to field:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
db.string().index();
|
|
75
|
+
db.string().unique();
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Relations** - Add a relation to field with automatic index and foreign key constraint:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
const role = db.type("Role", {
|
|
82
|
+
name: db.string(),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const user = db.type("User", {
|
|
86
|
+
name: db.string(),
|
|
87
|
+
roleId: db.uuid().relation({
|
|
88
|
+
type: "n-1",
|
|
89
|
+
toward: { type: role },
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
For one-to-one relations, use `type: "1-1"`:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
const userProfile = db.type("UserProfile", {
|
|
98
|
+
userId: db.uuid().relation({
|
|
99
|
+
type: "1-1",
|
|
100
|
+
toward: { type: user },
|
|
101
|
+
}),
|
|
102
|
+
bio: db.string(),
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
For foreign key constraint without creating a relation, use `type: "keyOnly"`:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const user = db.type("User", {
|
|
110
|
+
roleId: db.uuid().relation({
|
|
111
|
+
type: "keyOnly",
|
|
112
|
+
toward: { type: role },
|
|
113
|
+
}),
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Create relations against different fields using `toward.key`:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
const user = db.type("User", {
|
|
121
|
+
email: db.string().unique(),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const userProfile = db.type("UserProfile", {
|
|
125
|
+
userEmail: db.string().relation({
|
|
126
|
+
type: "1-1",
|
|
127
|
+
toward: { type: user, key: "email" },
|
|
128
|
+
}),
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Customize relation names using `toward.as` / `backward` options:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
const userProfile = db.type("UserProfile", {
|
|
136
|
+
userId: db.uuid().relation({
|
|
137
|
+
type: "1-1",
|
|
138
|
+
toward: { type: user, as: "base" },
|
|
139
|
+
backward: "profile",
|
|
140
|
+
}),
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Hooks** - Add hooks to execute functions during data creation or update:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
db.datetime().hooks({
|
|
148
|
+
create: ({ value }) => new Date().toISOString(),
|
|
149
|
+
update: ({ value }) => new Date().toISOString(),
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Function arguments include: `value` (field value), `data` (entire record value), `user` (user performing the operation).
|
|
154
|
+
|
|
155
|
+
**Validation** - Add validation functions to field:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
db.string().validate(
|
|
159
|
+
({ value }) => value.length > 5,
|
|
160
|
+
[
|
|
161
|
+
({ value }) => value.length < 10,
|
|
162
|
+
"Value must be shorter than 10 characters",
|
|
163
|
+
],
|
|
164
|
+
);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Vector Search** - Enable field for vector search:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
db.string().vector();
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Serial / Auto-increment** - Enable field auto-increment:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
db.int().serial({
|
|
177
|
+
start: 0,
|
|
178
|
+
maxValue: 100,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
db.string().serial({
|
|
182
|
+
start: 0,
|
|
183
|
+
format: "CUST_%d",
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Common Fields** - Add common fields using built-in helpers:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
export const user = db.type("User", {
|
|
191
|
+
name: db.string(),
|
|
192
|
+
...db.fields.timestamps(),
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### Type Definition
|
|
197
|
+
|
|
198
|
+
Define a TailorDB Type using `db.type()` method:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
db.type("User", {
|
|
202
|
+
name: db.string(),
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Specify PluralForm by passing an array as first argument:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
db.type(["User", "UserList"], {
|
|
210
|
+
name: db.string(),
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Pass a description as second argument:
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
db.type("User", "User in the system", {
|
|
218
|
+
name: db.string(),
|
|
219
|
+
description: db.string().optional(),
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Composite Indexes** - Configure composite indexes:
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
db.type("User", {
|
|
227
|
+
firstName: db.string(),
|
|
228
|
+
lastName: db.string(),
|
|
229
|
+
}).indexes({
|
|
230
|
+
fields: ["firstName", "lastName"],
|
|
231
|
+
unique: true,
|
|
232
|
+
name: "user_name_idx",
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**File Fields** - Add file fields:
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
db.type("User", {
|
|
240
|
+
name: db.string(),
|
|
241
|
+
}).files({
|
|
242
|
+
avatar: "profile image",
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Features** - Enable additional features:
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
db.type("User", {
|
|
250
|
+
name: db.string(),
|
|
251
|
+
}).features({
|
|
252
|
+
aggregation: true,
|
|
253
|
+
bulkUpsert: true,
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Permissions** - Configure Permission and GQLPermission. For details, see the [TailorDB Permission documentation](https://docs.tailor.tech/guides/tailordb/permission).
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
db.type("User", {
|
|
261
|
+
name: db.string(),
|
|
262
|
+
role: db.enum("admin", "user").index(),
|
|
263
|
+
})
|
|
264
|
+
.permission({
|
|
265
|
+
create: [[{ user: "role" }, "=", "admin"]],
|
|
266
|
+
read: [
|
|
267
|
+
[{ user: "role" }, "=", "admin"],
|
|
268
|
+
[{ record: "id" }, "=", { user: "id" }],
|
|
269
|
+
],
|
|
270
|
+
update: [[{ user: "role" }, "=", "admin"]],
|
|
271
|
+
delete: [[{ user: "role" }, "=", "admin"]],
|
|
272
|
+
})
|
|
273
|
+
.gqlPermission([
|
|
274
|
+
{ conditions: [[{ user: "role" }, "=", "admin"]], actions: "all" },
|
|
275
|
+
{ conditions: [[{ user: "role" }, "=", "user"]], actions: ["read"] },
|
|
276
|
+
]);
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Following the secure-by-default principle, all operations are denied if permissions are not configured.
|
|
280
|
+
|
|
281
|
+
### Resolver Concepts
|
|
282
|
+
|
|
283
|
+
Define Resolvers in files matching glob patterns specified in `tailor.config.ts`.
|
|
284
|
+
|
|
285
|
+
#### Resolver Creation
|
|
286
|
+
|
|
287
|
+
Define Resolvers using `createResolver` method:
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
createResolver({
|
|
291
|
+
name: "add",
|
|
292
|
+
operation: "query",
|
|
293
|
+
input: {
|
|
294
|
+
left: t.int(),
|
|
295
|
+
right: t.int(),
|
|
296
|
+
},
|
|
297
|
+
body: (context) => {
|
|
298
|
+
return {
|
|
299
|
+
result: context.input.left + context.input.right,
|
|
300
|
+
};
|
|
301
|
+
},
|
|
302
|
+
output: t.object({
|
|
303
|
+
result: t.int(),
|
|
304
|
+
}),
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
#### Input/Output Schemas
|
|
309
|
+
|
|
310
|
+
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.
|
|
311
|
+
|
|
312
|
+
You can reuse fields defined with `db` object, but note that unsupported options will be ignored:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
const user = db.type("User", {
|
|
316
|
+
name: db.string().unique(),
|
|
317
|
+
age: db.int(),
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
createResolver({
|
|
321
|
+
input: {
|
|
322
|
+
name: user.fields.name,
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
#### Body Function
|
|
328
|
+
|
|
329
|
+
Define actual resolver logic in the `body` function. Function arguments include: `input` (input data), `user` (user performing the operation).
|
|
330
|
+
|
|
331
|
+
If you're generating Kysely types with a generator, you can use `getDB` to execute typed queries:
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
import { getDB } from "../generated/tailordb";
|
|
335
|
+
|
|
336
|
+
createResolver({
|
|
337
|
+
name: "getUser",
|
|
338
|
+
operation: "query",
|
|
339
|
+
input: {
|
|
340
|
+
name: t.string(),
|
|
341
|
+
},
|
|
342
|
+
body: async (context) => {
|
|
343
|
+
const db = getDB("tailordb");
|
|
344
|
+
const query = db
|
|
345
|
+
.selectFrom("User")
|
|
346
|
+
.select("id")
|
|
347
|
+
.where("name", "=", context.input.name)
|
|
348
|
+
.limit(1)
|
|
349
|
+
.executeTakeFirstOrThrow();
|
|
350
|
+
return {
|
|
351
|
+
result: result.id,
|
|
352
|
+
};
|
|
353
|
+
},
|
|
354
|
+
output: t.object({
|
|
355
|
+
result: t.uuid(),
|
|
356
|
+
}),
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Executor Patterns
|
|
361
|
+
|
|
362
|
+
Define Executors in files matching glob patterns specified in `tailor.config.ts`.
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
createExecutor({
|
|
366
|
+
name: "user-welcome",
|
|
367
|
+
description: "Send welcome email to new users",
|
|
368
|
+
trigger: recordCreatedTrigger({
|
|
369
|
+
type: user,
|
|
370
|
+
condition: ({ newRecord }) => !!newRecord.email && newRecord.isActive,
|
|
371
|
+
}),
|
|
372
|
+
operation: {
|
|
373
|
+
kind: "function",
|
|
374
|
+
body: async ({ newRecord }) => {
|
|
375
|
+
// Send welcome email logic here
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
#### Trigger Types
|
|
382
|
+
|
|
383
|
+
**Record Triggers** - Fire when records are created, updated, or deleted:
|
|
384
|
+
|
|
385
|
+
- `recordCreatedTrigger()`: Fires when a new record is created
|
|
386
|
+
- `recordUpdatedTrigger()`: Fires when a record is updated
|
|
387
|
+
- `recordDeletedTrigger()`: Fires when a record is deleted
|
|
388
|
+
|
|
389
|
+
Each trigger can include an optional filter function:
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
recordUpdatedTrigger({
|
|
393
|
+
type: order,
|
|
394
|
+
condition: ({ newRecord, oldRecord }) =>
|
|
395
|
+
newRecord.status === "completed" && oldRecord.status !== "completed",
|
|
396
|
+
});
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
**Schedule Trigger** - Fires on a cron schedule:
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
scheduleTrigger({ cron: "*/5 * * * *" });
|
|
403
|
+
scheduleTrigger({ cron: "0 9 * * 1" });
|
|
404
|
+
scheduleTrigger({ cron: "0 0 1 * *" });
|
|
405
|
+
scheduleTrigger({ cron: "0 * * * *", timezone: "Asia/Tokyo" });
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**Incoming Webhook Trigger** - Fires when an external webhook is received:
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
incomingWebhookTrigger<WebhookPayload>();
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
**Resolver Executed Trigger** - Fires when a resolver is executed:
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
resolverExecutedTrigger({
|
|
418
|
+
resolver: createOrderResolver,
|
|
419
|
+
condition: ({ result, error }) => !error && result?.order?.id,
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
#### Execution Targets
|
|
424
|
+
|
|
425
|
+
**executeFunction / executeJobFunction** - Execute JavaScript/TypeScript functions:
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
createExecutor({
|
|
429
|
+
operation: {
|
|
430
|
+
kind: "function",
|
|
431
|
+
body: async ({ newRecord }) => {
|
|
432
|
+
console.log("New record created:", newRecord);
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
**executeWebhook** - Call external webhooks with dynamic data:
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
createExecutor({
|
|
442
|
+
operation: {
|
|
443
|
+
kind: "webhook",
|
|
444
|
+
url: ({ newRecord }) =>
|
|
445
|
+
`https://api.example.com/webhooks/${newRecord.type}`,
|
|
446
|
+
headers: {
|
|
447
|
+
"Content-Type": "application/json",
|
|
448
|
+
"X-API-Key": { vault: "api-keys", key: "external-api" },
|
|
449
|
+
},
|
|
450
|
+
body: ({ newRecord }) => ({
|
|
451
|
+
id: newRecord.id,
|
|
452
|
+
timestamp: new Date().toISOString(),
|
|
453
|
+
data: newRecord,
|
|
454
|
+
}),
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
**executeGql** - Execute GraphQL queries and mutations:
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
createExecutor({
|
|
463
|
+
operation: {
|
|
464
|
+
kind: "graphql",
|
|
465
|
+
appName: "my-app",
|
|
466
|
+
query: gql`
|
|
467
|
+
mutation UpdateUserStatus($id: ID!, $status: String!) {
|
|
468
|
+
updateUser(id: $id, input: { status: $status }) {
|
|
469
|
+
id
|
|
470
|
+
status
|
|
471
|
+
updatedAt
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
`,
|
|
475
|
+
variables: ({ newRecord }) => ({
|
|
476
|
+
id: newRecord.userId,
|
|
477
|
+
status: "active",
|
|
478
|
+
}),
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
#### Execution Context
|
|
484
|
+
|
|
485
|
+
Context varies based on trigger type:
|
|
486
|
+
|
|
487
|
+
**Record trigger context** - For `recordCreatedTrigger`, `recordUpdatedTrigger`, and `recordDeletedTrigger`:
|
|
488
|
+
|
|
489
|
+
- `typeName`: Name of the TailorDB type
|
|
490
|
+
- `newRecord`: New record state (not available for delete triggers)
|
|
491
|
+
- `oldRecord`: Previous record state (not available for create triggers)
|
|
492
|
+
|
|
493
|
+
**Incoming webhook trigger context** - For `incomingWebhookTrigger`:
|
|
494
|
+
|
|
495
|
+
- `body`: Webhook request body
|
|
496
|
+
- `headers`: Webhook request headers
|
|
497
|
+
- `method`: HTTP method
|
|
498
|
+
- `rawBody`: Raw request body as string
|
|
499
|
+
|
|
500
|
+
**Resolver executed trigger context** - For `resolverExecutedTrigger`:
|
|
501
|
+
|
|
502
|
+
- `resolverName`: Name of the resolver
|
|
503
|
+
- `result`: Resolver's return value (when execution succeeds)
|
|
504
|
+
- `error`: Error object (when execution fails)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Tailor Platform SDK
|
|
2
|
+
|
|
3
|
+
Development kit for building type-safe applications on the Tailor Platform using TypeScript.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
In this quickstart tutorial, you'll create an app using the Tailor Platform SDK.
|
|
8
|
+
Follow the steps below to get started.
|
|
9
|
+
|
|
10
|
+
## Prerequisite
|
|
11
|
+
|
|
12
|
+
You'll need a Tailor account to start using the Tailor Platform.
|
|
13
|
+
Contact us [here](https://www.tailor.tech/demo) to get started.
|
|
14
|
+
|
|
15
|
+
### Install Node.js
|
|
16
|
+
|
|
17
|
+
The SDK requires Node.js 22 or later. Install Node.js via your package manager by following the official Node.js instructions.
|
|
18
|
+
|
|
19
|
+
### Create an Example App
|
|
20
|
+
|
|
21
|
+
The following command creates a new project with the required configuration files and example code.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm create @tailor-platform/sdk example-app --template hello-world
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Before deploying your app, you need to create a workspace:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx tailor-sdk login
|
|
31
|
+
npx tailor-sdk workspace create --name <workspace-name> --region <workspace-region>
|
|
32
|
+
npx tailor-sdk workspace list
|
|
33
|
+
|
|
34
|
+
# OR
|
|
35
|
+
# Create a new workspace using Tailor Platform Console
|
|
36
|
+
# https://console.tailor.tech/
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Deploy Your App
|
|
40
|
+
|
|
41
|
+
Run the apply command to deploy your project:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cd example-app
|
|
45
|
+
npm run deploy -- --workspace-id <your-workspace-id>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
You can now open the GraphQL Playground and execute the `hello` query:
|
|
49
|
+
|
|
50
|
+
```graphql
|
|
51
|
+
query {
|
|
52
|
+
hello(input: { name: "sdk" }) {
|
|
53
|
+
message
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Hello World Example
|
|
59
|
+
|
|
60
|
+
Here's a simple query resolver from the hello-world template:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { createResolver, t } from "@tailor-platform/sdk";
|
|
64
|
+
|
|
65
|
+
export default createResolver({
|
|
66
|
+
name: "hello",
|
|
67
|
+
operation: "query",
|
|
68
|
+
input: {
|
|
69
|
+
name: t.string().description("Name to greet"),
|
|
70
|
+
},
|
|
71
|
+
body: (context) => {
|
|
72
|
+
return {
|
|
73
|
+
message: `Hello, ${context.input.name}!`,
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
output: t
|
|
77
|
+
.object({
|
|
78
|
+
message: t.string().description("Greeting message"),
|
|
79
|
+
})
|
|
80
|
+
.description("Greeting response"),
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
You can edit `src/resolvers/hello.ts` to customize the message:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
export default createResolver({
|
|
88
|
+
body: (context) => {
|
|
89
|
+
return {
|
|
90
|
+
message: `Goodbye, ${context.input.name}!`,
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Deploy again to see the response.
|