@tailor-platform/sdk 1.11.0 → 1.12.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 +34 -0
- package/dist/application-DM4zTgXU.mjs +4 -0
- package/dist/{application-BKBo5tGD.mjs → application-DnWZVbDO.mjs} +164 -26
- package/dist/application-DnWZVbDO.mjs.map +1 -0
- package/dist/cli/index.mjs +13 -19
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lib.d.mts +149 -17
- package/dist/cli/lib.mjs +132 -4
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/configure/index.d.mts +4 -3
- package/dist/configure/index.mjs +16 -757
- package/dist/configure/index.mjs.map +1 -1
- package/dist/env-4RO7szrH.d.mts +66 -0
- package/dist/{index-D8zIUsWi.d.mts → index-BBr_q3vB.d.mts} +20 -11
- package/dist/{index-DcOTucF6.d.mts → index-Bid18Opo.d.mts} +473 -84
- package/dist/{jiti-ygK9KoRA.mjs → jiti-DuCiUfMj.mjs} +2 -2
- package/dist/{jiti-ygK9KoRA.mjs.map → jiti-DuCiUfMj.mjs.map} +1 -1
- package/dist/{job-l-pIR9IY.mjs → job-zGAXCidt.mjs} +1 -1
- package/dist/{job-l-pIR9IY.mjs.map → job-zGAXCidt.mjs.map} +1 -1
- package/dist/kysely/index.d.mts +25 -0
- package/dist/kysely/index.mjs +27 -0
- package/dist/kysely/index.mjs.map +1 -0
- package/dist/plugin/index.d.mts +105 -0
- package/dist/plugin/index.mjs +45 -0
- package/dist/plugin/index.mjs.map +1 -0
- package/dist/schema-BmKdDzr1.mjs +784 -0
- package/dist/schema-BmKdDzr1.mjs.map +1 -0
- package/dist/{src-CG8kJBI9.mjs → src-QNTCsO6J.mjs} +2 -2
- package/dist/{src-CG8kJBI9.mjs.map → src-QNTCsO6J.mjs.map} +1 -1
- package/dist/types-r-ZratAg.mjs +13 -0
- package/dist/types-r-ZratAg.mjs.map +1 -0
- package/dist/{update-D0muqqOP.mjs → update-B_W-UQnS.mjs} +1626 -390
- package/dist/update-B_W-UQnS.mjs.map +1 -0
- package/dist/utils/test/index.d.mts +2 -2
- package/dist/utils/test/index.mjs +1 -1
- package/docs/cli/tailordb.md +0 -12
- package/docs/generator/builtin.md +2 -2
- package/docs/plugin/custom.md +389 -0
- package/docs/plugin/index.md +112 -0
- package/docs/services/workflow.md +28 -0
- package/package.json +16 -23
- package/dist/application-BKBo5tGD.mjs.map +0 -1
- package/dist/application-a12-7TT3.mjs +0 -4
- package/dist/update-D0muqqOP.mjs.map +0 -1
- /package/dist/{chunk-CIV_ash9.mjs → chunk-C3Kl5s5P.mjs} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference path="./../../user-defined.d.ts" />
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { Ft as TailorField, rt as TailorDBType } from "../../index-Bid18Opo.mjs";
|
|
3
|
+
import { G as WORKFLOW_TEST_ENV_KEY, n as output } from "../../index-BBr_q3vB.mjs";
|
|
4
4
|
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
5
5
|
|
|
6
6
|
//#region src/utils/test/index.d.ts
|
package/docs/cli/tailordb.md
CHANGED
|
@@ -651,18 +651,6 @@ tailor-sdk apply --no-schema-check
|
|
|
651
651
|
**Notes:**
|
|
652
652
|
|
|
653
653
|
- This command is a beta feature and may introduce breaking changes in future releases
|
|
654
|
-
- `@liam-hq/cli` is required for `export`, `serve`, and `deploy`
|
|
655
|
-
- `serve` is required only for `tailordb erd serve`
|
|
656
|
-
|
|
657
|
-
Install dependencies:
|
|
658
|
-
|
|
659
|
-
```bash
|
|
660
|
-
npm i -D @liam-hq/cli serve
|
|
661
|
-
# OR
|
|
662
|
-
yarn add -D @liam-hq/cli serve
|
|
663
|
-
# OR
|
|
664
|
-
pnpm add -D @liam-hq/cli serve
|
|
665
|
-
```
|
|
666
654
|
|
|
667
655
|
**Usage Examples:**
|
|
668
656
|
|
|
@@ -18,10 +18,10 @@ Generates Kysely type definitions and the `getDB()` function for type-safe datab
|
|
|
18
18
|
|
|
19
19
|
### Prerequisites
|
|
20
20
|
|
|
21
|
-
Install the required
|
|
21
|
+
Install the required dev dependency for type definitions:
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
pnpm add -D @tailor-platform/function-
|
|
24
|
+
pnpm add -D @tailor-platform/function-types
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
### Output
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# Custom Plugins (Beta)
|
|
2
|
+
|
|
3
|
+
> **Beta Feature**: The custom plugin API is in beta and may change in future releases.
|
|
4
|
+
|
|
5
|
+
Create your own plugins by implementing the `PluginBase` interface.
|
|
6
|
+
|
|
7
|
+
## PluginBase Interface
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
interface PluginBase<PluginConfig = unknown> {
|
|
11
|
+
/** Unique identifier for the plugin (e.g., "@my-company/soft-delete") */
|
|
12
|
+
readonly id: string;
|
|
13
|
+
|
|
14
|
+
/** Human-readable description */
|
|
15
|
+
readonly description: string;
|
|
16
|
+
|
|
17
|
+
/** Import path for generated code to reference */
|
|
18
|
+
readonly importPath: string;
|
|
19
|
+
|
|
20
|
+
/** Schema for per-type configuration via .plugin() (required when using process) */
|
|
21
|
+
readonly configSchema?: TailorAnyField;
|
|
22
|
+
|
|
23
|
+
/** Schema for plugin-level configuration via definePlugins() (optional) */
|
|
24
|
+
readonly pluginConfigSchema?: TailorAnyField;
|
|
25
|
+
|
|
26
|
+
/** Controls whether per-type config is required when attaching via .plugin() */
|
|
27
|
+
readonly typeConfigRequired?: boolean | ((pluginConfig?: PluginConfig) => boolean);
|
|
28
|
+
|
|
29
|
+
/** Plugin-level config passed via definePlugins() */
|
|
30
|
+
readonly pluginConfig?: PluginConfig;
|
|
31
|
+
|
|
32
|
+
/** Optional template for generating PluginConfigs typing */
|
|
33
|
+
readonly configTypeTemplate?: string;
|
|
34
|
+
|
|
35
|
+
/** Process a type with this plugin attached */
|
|
36
|
+
process?(context: PluginProcessContext): PluginOutput | Promise<PluginOutput>;
|
|
37
|
+
|
|
38
|
+
/** Process a namespace (plugins without a source type) */
|
|
39
|
+
processNamespace?(
|
|
40
|
+
context: PluginNamespaceProcessContext,
|
|
41
|
+
): NamespacePluginOutput | Promise<NamespacePluginOutput>;
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Notes:
|
|
46
|
+
|
|
47
|
+
- `importPath` should be resolvable from the directory containing `tailor.config.ts`. Code generators use it to import plugin APIs such as `getGeneratedType` and executor modules.
|
|
48
|
+
- If you want to attach a plugin via `.plugin()`, you must provide `configSchema` and `process`.
|
|
49
|
+
- Namespace-only plugins can omit `configSchema` and implement `processNamespace` instead.
|
|
50
|
+
- `pluginConfig` stores the plugin-level config so it can be read later during processing. If you prefer not to set it manually, you can pass config as a tuple to `definePlugins([plugin, config])`.
|
|
51
|
+
- For custom plugins, `pluginConfig` is the expected pattern. The tuple form can also be used to pass config.
|
|
52
|
+
- `resolve` should return a dynamic import; relative specifiers are resolved from the plugin module.
|
|
53
|
+
- Per-type config is optional by default. Use `typeConfigRequired: true` to make it mandatory.
|
|
54
|
+
- To toggle optional/required based on plugin config, provide a function for `typeConfigRequired`.
|
|
55
|
+
|
|
56
|
+
## PluginProcessContext
|
|
57
|
+
|
|
58
|
+
Context passed to the `process` method:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
interface PluginProcessContext<Config = unknown, PluginConfig = unknown> {
|
|
62
|
+
/** The TailorDB type being processed */
|
|
63
|
+
type: TailorAnyDBType;
|
|
64
|
+
|
|
65
|
+
/** Per-type configuration from .plugin({ pluginId: config }) */
|
|
66
|
+
config: Config;
|
|
67
|
+
|
|
68
|
+
/** Plugin-level configuration from definePlugins() */
|
|
69
|
+
pluginConfig: PluginConfig;
|
|
70
|
+
|
|
71
|
+
/** Namespace of the TailorDB type */
|
|
72
|
+
namespace: string;
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## PluginNamespaceProcessContext
|
|
77
|
+
|
|
78
|
+
Context passed to the `processNamespace` method:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
interface PluginNamespaceProcessContext<Config = unknown> {
|
|
82
|
+
/** Plugin-level configuration from definePlugins() */
|
|
83
|
+
pluginConfig: Config;
|
|
84
|
+
|
|
85
|
+
/** Namespace of the TailorDB types */
|
|
86
|
+
namespace: string;
|
|
87
|
+
|
|
88
|
+
/** TailorDB types in the namespace (after type-attached processing) */
|
|
89
|
+
types: TailorAnyDBType[];
|
|
90
|
+
|
|
91
|
+
/** Plugin-generated types for type-attached plugins in the namespace */
|
|
92
|
+
generatedTypes: Array<{
|
|
93
|
+
type: TailorAnyDBType;
|
|
94
|
+
pluginId: string;
|
|
95
|
+
generatedTypeKind?: string;
|
|
96
|
+
originalType: TailorAnyDBType;
|
|
97
|
+
}>;
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`generatedTypes` includes only type-attached plugin-generated types (so `originalType` is always present), and `types` contains only user-defined types.
|
|
102
|
+
For example:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
const changeRequestTypes = context.generatedTypes.filter(
|
|
106
|
+
(entry) => entry.pluginId === "@example/change-request",
|
|
107
|
+
);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## PluginOutput
|
|
111
|
+
|
|
112
|
+
Return value from `process`:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
interface PluginOutput {
|
|
116
|
+
/** Additional TailorDB types to generate */
|
|
117
|
+
types?: Record<string, TailorAnyDBType>;
|
|
118
|
+
|
|
119
|
+
/** Additional resolvers to generate */
|
|
120
|
+
resolvers?: PluginGeneratedResolver[];
|
|
121
|
+
|
|
122
|
+
/** Additional executors to generate */
|
|
123
|
+
executors?: PluginGeneratedExecutor[];
|
|
124
|
+
|
|
125
|
+
/** Extensions to apply to the source type */
|
|
126
|
+
extends?: {
|
|
127
|
+
/** Fields to add to the source type */
|
|
128
|
+
fields?: Record<string, TailorAnyField>;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`processNamespace` returns `NamespacePluginOutput` (same shape as `PluginOutput` but without `extends`):
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
type NamespacePluginOutput = Omit<PluginOutput, "extends">;
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Example: Soft Delete Plugin
|
|
140
|
+
|
|
141
|
+
A complete example of a plugin that adds soft delete functionality:
|
|
142
|
+
|
|
143
|
+
### Plugin Definition
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// plugins/soft-delete/plugin.ts
|
|
147
|
+
import { db, t } from "@tailor-platform/sdk";
|
|
148
|
+
import type { PluginBase, PluginProcessContext, PluginOutput } from "@tailor-platform/sdk";
|
|
149
|
+
|
|
150
|
+
interface SoftDeleteConfig {
|
|
151
|
+
archiveReason?: boolean;
|
|
152
|
+
retentionDays?: number;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
interface SoftDeletePluginConfig {
|
|
156
|
+
archiveTablePrefix?: string;
|
|
157
|
+
defaultRetentionDays?: number;
|
|
158
|
+
requireTypeConfig?: boolean;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const configSchema = t.object({
|
|
162
|
+
archiveReason: t.bool({ optional: true }),
|
|
163
|
+
retentionDays: t.int({ optional: true }),
|
|
164
|
+
// Use { required: true } to mark fields as required in plugin configs.
|
|
165
|
+
// By default, plugin config fields are optional.
|
|
166
|
+
// token: t.string({ required: true }),
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const pluginConfigSchema = t.object({
|
|
170
|
+
archiveTablePrefix: t.string({ optional: true }),
|
|
171
|
+
defaultRetentionDays: t.int({ optional: true }),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
function processSoftDelete(
|
|
175
|
+
context: PluginProcessContext<SoftDeleteConfig, SoftDeletePluginConfig>,
|
|
176
|
+
): PluginOutput {
|
|
177
|
+
const { type, config, pluginConfig, namespace } = context;
|
|
178
|
+
const prefix = pluginConfig?.archiveTablePrefix ?? "Deleted_";
|
|
179
|
+
|
|
180
|
+
// Generate archive type
|
|
181
|
+
const archiveType = db
|
|
182
|
+
.type(`${prefix}${type.name}`, {
|
|
183
|
+
originalId: db.uuid().description("ID of the deleted record"),
|
|
184
|
+
originalData: db.string().description("JSON snapshot of deleted record"),
|
|
185
|
+
deletedAt: db.datetime().description("When the record was deleted"),
|
|
186
|
+
deletedBy: db.uuid().description("User who deleted the record"),
|
|
187
|
+
...(config.archiveReason && {
|
|
188
|
+
reason: db.string({ optional: true }).description("Reason for deletion"),
|
|
189
|
+
}),
|
|
190
|
+
...db.fields.timestamps(),
|
|
191
|
+
})
|
|
192
|
+
.description(`Archive for deleted ${type.name} records`);
|
|
193
|
+
|
|
194
|
+
// Extend source type with deletedAt field
|
|
195
|
+
const extendFields = {
|
|
196
|
+
deletedAt: db.datetime({ optional: true }).description("Soft delete timestamp"),
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
types: { archive: archiveType },
|
|
201
|
+
extends: { fields: extendFields },
|
|
202
|
+
executors: [
|
|
203
|
+
{
|
|
204
|
+
name: `${type.name.toLowerCase()}-on-delete`,
|
|
205
|
+
resolve: async () => await import("./executors/on-delete"),
|
|
206
|
+
context: {
|
|
207
|
+
sourceType: type,
|
|
208
|
+
archiveType,
|
|
209
|
+
namespace,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Factory function for plugins with namespace config
|
|
217
|
+
export function softDeletePlugin(pluginConfig?: SoftDeletePluginConfig): PluginBase {
|
|
218
|
+
return {
|
|
219
|
+
id: "@example/soft-delete",
|
|
220
|
+
description: "Adds soft delete with archive functionality",
|
|
221
|
+
importPath: "./plugins/soft-delete",
|
|
222
|
+
configSchema,
|
|
223
|
+
pluginConfigSchema,
|
|
224
|
+
pluginConfig,
|
|
225
|
+
typeConfigRequired: (config) => config?.requireTypeConfig === true,
|
|
226
|
+
process: processSoftDelete,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Executor with Context
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
// plugins/soft-delete/executors/on-delete.ts
|
|
235
|
+
import { createExecutor, recordDeletedTrigger, withPluginContext } from "@tailor-platform/sdk";
|
|
236
|
+
import type { TailorAnyDBType } from "@tailor-platform/sdk";
|
|
237
|
+
import { getDB } from "generated/tailordb";
|
|
238
|
+
|
|
239
|
+
interface SoftDeleteContext {
|
|
240
|
+
sourceType: TailorAnyDBType;
|
|
241
|
+
archiveType: TailorAnyDBType;
|
|
242
|
+
namespace: string;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export default withPluginContext((ctx: SoftDeleteContext) => {
|
|
246
|
+
const { sourceType, archiveType, namespace } = ctx;
|
|
247
|
+
|
|
248
|
+
return createExecutor({
|
|
249
|
+
name: `${sourceType.name.toLowerCase()}-on-delete`,
|
|
250
|
+
description: `Archives deleted ${sourceType.name} records`,
|
|
251
|
+
trigger: recordDeletedTrigger({ type: sourceType }),
|
|
252
|
+
operation: {
|
|
253
|
+
kind: "function",
|
|
254
|
+
body: async ({ oldRecord, user }) => {
|
|
255
|
+
const db = getDB(namespace as "tailordb");
|
|
256
|
+
await db
|
|
257
|
+
.insertInto(archiveType.name)
|
|
258
|
+
.values({
|
|
259
|
+
originalId: oldRecord.id,
|
|
260
|
+
originalData: JSON.stringify(oldRecord),
|
|
261
|
+
deletedAt: new Date(),
|
|
262
|
+
deletedBy: user?.id ?? "system",
|
|
263
|
+
})
|
|
264
|
+
.execute();
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Usage
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
// tailor.config.ts
|
|
275
|
+
import { definePlugins } from "@tailor-platform/sdk";
|
|
276
|
+
import { softDeletePlugin } from "./plugins/soft-delete";
|
|
277
|
+
|
|
278
|
+
export const plugins = definePlugins(
|
|
279
|
+
softDeletePlugin({
|
|
280
|
+
archiveTablePrefix: "Deleted_",
|
|
281
|
+
defaultRetentionDays: 90,
|
|
282
|
+
}),
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
// tailordb/customer.ts
|
|
286
|
+
export const customer = db
|
|
287
|
+
.type("Customer", {
|
|
288
|
+
name: db.string(),
|
|
289
|
+
email: db.string(),
|
|
290
|
+
})
|
|
291
|
+
.plugin({
|
|
292
|
+
"@example/soft-delete": {
|
|
293
|
+
archiveReason: true,
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
If your plugin uses `typeConfigRequired` as a function, you can toggle whether per-type config
|
|
299
|
+
is required via `pluginConfig`:
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
export const plugins = definePlugins(
|
|
303
|
+
softDeletePlugin({
|
|
304
|
+
archiveTablePrefix: "Deleted_",
|
|
305
|
+
requireTypeConfig: true,
|
|
306
|
+
}),
|
|
307
|
+
);
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Adding Type Safety
|
|
311
|
+
|
|
312
|
+
To enable type checking for your plugin's configuration, add a declaration merge:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
// user-defined.d.ts or your plugin's types.ts
|
|
316
|
+
declare module "@tailor-platform/sdk" {
|
|
317
|
+
interface PluginConfigs<Fields extends string> {
|
|
318
|
+
"@example/soft-delete": {
|
|
319
|
+
archiveReason?: boolean;
|
|
320
|
+
retentionDays?: number;
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
The `Fields` type parameter provides field names from the type being configured, enabling field-aware configurations:
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
declare module "@tailor-platform/sdk" {
|
|
330
|
+
interface PluginConfigs<Fields extends string> {
|
|
331
|
+
"@example/i18n": {
|
|
332
|
+
labels: Partial<Record<Fields, { ja: string; en: string }>>;
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Plugin Types
|
|
339
|
+
|
|
340
|
+
### Type-Attached Plugins
|
|
341
|
+
|
|
342
|
+
Implement `process` to handle types with the plugin attached:
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
const plugin: PluginBase = {
|
|
346
|
+
id: "@example/my-plugin",
|
|
347
|
+
// ...
|
|
348
|
+
process(context) {
|
|
349
|
+
// Called for each type with .plugin({ "@example/my-plugin": config })
|
|
350
|
+
return {
|
|
351
|
+
types: {
|
|
352
|
+
/* generated types */
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Namespace Plugins
|
|
360
|
+
|
|
361
|
+
Implement `processNamespace` for plugins that generate types independently:
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
const plugin: PluginBase = {
|
|
365
|
+
id: "@example/audit-log",
|
|
366
|
+
// ...
|
|
367
|
+
processNamespace(context) {
|
|
368
|
+
// Called once per namespace, with namespace-level types available
|
|
369
|
+
return { types: { auditLog: /* generated type */ } };
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Hybrid Plugins
|
|
375
|
+
|
|
376
|
+
Implement both methods for plugins that support both modes:
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
const plugin: PluginBase = {
|
|
380
|
+
id: "@example/hybrid",
|
|
381
|
+
// ...
|
|
382
|
+
process(context) {
|
|
383
|
+
// Handle type attachments
|
|
384
|
+
},
|
|
385
|
+
processNamespace(context) {
|
|
386
|
+
// Handle namespace generation
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
```
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Plugins (Beta)
|
|
2
|
+
|
|
3
|
+
> **Beta Feature**: The plugin system is currently in beta. APIs may change in future releases.
|
|
4
|
+
|
|
5
|
+
Plugins extend TailorDB types by automatically generating additional types and executors based on your type definitions.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
When you run `tailor-sdk generate`, the SDK:
|
|
10
|
+
|
|
11
|
+
1. Loads all TailorDB types with plugin attachments
|
|
12
|
+
2. Passes each type to the attached plugins
|
|
13
|
+
3. Generates additional types and executors based on plugin output
|
|
14
|
+
4. Writes all generated files to the appropriate locations
|
|
15
|
+
|
|
16
|
+
This enables plugins to create derived functionality based on your application's schema.
|
|
17
|
+
|
|
18
|
+
## Configuration
|
|
19
|
+
|
|
20
|
+
### Registering Plugins
|
|
21
|
+
|
|
22
|
+
Define plugins in `tailor.config.ts` using `definePlugins()`:
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { defineConfig, definePlugins } from "@tailor-platform/sdk";
|
|
26
|
+
import { myPlugin } from "./plugins/my-plugin";
|
|
27
|
+
|
|
28
|
+
export const plugins = definePlugins(myPlugin());
|
|
29
|
+
|
|
30
|
+
export default defineConfig({
|
|
31
|
+
name: "my-app",
|
|
32
|
+
// ...
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Important**: The `plugins` export must be a named export (not default).
|
|
37
|
+
|
|
38
|
+
### Attaching Plugins to Types
|
|
39
|
+
|
|
40
|
+
Use the `.plugin()` method to attach plugins to specific types:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { db } from "@tailor-platform/sdk";
|
|
44
|
+
|
|
45
|
+
export const user = db
|
|
46
|
+
.type("User", {
|
|
47
|
+
name: db.string(),
|
|
48
|
+
email: db.string(),
|
|
49
|
+
})
|
|
50
|
+
.plugin({
|
|
51
|
+
"@example/my-plugin": {},
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Plugin Configuration
|
|
56
|
+
|
|
57
|
+
Some plugins accept per-type configuration:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
export const customer = db
|
|
61
|
+
.type("Customer", {
|
|
62
|
+
name: db.string(),
|
|
63
|
+
// ...
|
|
64
|
+
})
|
|
65
|
+
.plugin({
|
|
66
|
+
"@example/soft-delete": {
|
|
67
|
+
archiveReason: true,
|
|
68
|
+
retentionDays: 90,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Per-type Config Requirement
|
|
74
|
+
|
|
75
|
+
Per-type config is optional by default. Plugin authors can change this with
|
|
76
|
+
`typeConfigRequired` (boolean or function). When a function is used, it receives
|
|
77
|
+
the plugin-level config from `definePlugins()`.
|
|
78
|
+
|
|
79
|
+
### Global Plugin Configuration
|
|
80
|
+
|
|
81
|
+
Plugins can also accept global configuration via `definePlugins()`:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { definePlugins } from "@tailor-platform/sdk";
|
|
85
|
+
import { softDeletePlugin } from "./plugins/soft-delete";
|
|
86
|
+
|
|
87
|
+
export const plugins = definePlugins(
|
|
88
|
+
// Custom plugin with global config (factory function)
|
|
89
|
+
softDeletePlugin({
|
|
90
|
+
archiveTablePrefix: "Deleted_",
|
|
91
|
+
defaultRetentionDays: 90,
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Generated Output
|
|
97
|
+
|
|
98
|
+
Plugins can generate:
|
|
99
|
+
|
|
100
|
+
- **Types**: Additional TailorDB types (e.g., `CustomerHistory`, `Deleted_Customer`)
|
|
101
|
+
- **Executors**: Event handlers triggered by record changes
|
|
102
|
+
- **Field Extensions**: Additional fields added to the source type
|
|
103
|
+
|
|
104
|
+
Generated files are placed under `.tailor-sdk/<plugin-id>/` (the plugin ID is sanitized,
|
|
105
|
+
e.g. `@example/soft-delete` → `example-soft-delete`), such as:
|
|
106
|
+
|
|
107
|
+
- `.tailor-sdk/example-soft-delete/types`
|
|
108
|
+
- `.tailor-sdk/example-soft-delete/executors`
|
|
109
|
+
|
|
110
|
+
## Creating Custom Plugins
|
|
111
|
+
|
|
112
|
+
See [Custom Plugins](./custom.md) for how to create your own plugins.
|
|
@@ -56,6 +56,34 @@ export const fetchCustomer = createWorkflowJob({
|
|
|
56
56
|
});
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
+
## Input and Output Type Constraints
|
|
60
|
+
|
|
61
|
+
Workflow job inputs and outputs are serialized as JSON when passed between jobs. This imposes type constraints:
|
|
62
|
+
|
|
63
|
+
**Input types** must be JSON-compatible — only primitives (`string`, `number`, `boolean`, `null`), arrays, and plain objects are allowed. `Date`, `Map`, `Set`, functions, and other non-serializable types cannot be used.
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// OK
|
|
67
|
+
export const myJob = createWorkflowJob({
|
|
68
|
+
name: "my-job",
|
|
69
|
+
body: async (input: { id: string; count: number; tags: string[] }) => {
|
|
70
|
+
// ...
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Compile error — Date is not allowed in input
|
|
75
|
+
export const badJob = createWorkflowJob({
|
|
76
|
+
name: "bad-job",
|
|
77
|
+
body: async (input: { createdAt: Date }) => {
|
|
78
|
+
// ...
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Output types** are more permissive — `Date` and objects with `toJSON()` are allowed because they are serialized via `JSON.stringify` at runtime (e.g., `Date` becomes a string).
|
|
84
|
+
|
|
85
|
+
These constraints are enforced at compile time — you will get a type error if you use an unsupported type.
|
|
86
|
+
|
|
59
87
|
## Triggering Jobs
|
|
60
88
|
|
|
61
89
|
Use `.trigger()` to start other jobs from within a job.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tailor-platform/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "Tailor Platform SDK - The SDK to work with Tailor Platform",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -36,6 +36,16 @@
|
|
|
36
36
|
"types": "./dist/utils/test/index.d.mts",
|
|
37
37
|
"import": "./dist/utils/test/index.mjs",
|
|
38
38
|
"default": "./dist/utils/test/index.mjs"
|
|
39
|
+
},
|
|
40
|
+
"./kysely": {
|
|
41
|
+
"types": "./dist/kysely/index.d.mts",
|
|
42
|
+
"import": "./dist/kysely/index.mjs",
|
|
43
|
+
"default": "./dist/kysely/index.mjs"
|
|
44
|
+
},
|
|
45
|
+
"./plugin": {
|
|
46
|
+
"types": "./dist/plugin/index.d.mts",
|
|
47
|
+
"import": "./dist/plugin/index.mjs",
|
|
48
|
+
"default": "./dist/plugin/index.mjs"
|
|
39
49
|
}
|
|
40
50
|
},
|
|
41
51
|
"dependencies": {
|
|
@@ -43,16 +53,20 @@
|
|
|
43
53
|
"@bufbuild/protobuf": "2.10.2",
|
|
44
54
|
"@connectrpc/connect": "2.1.1",
|
|
45
55
|
"@connectrpc/connect-node": "2.1.1",
|
|
56
|
+
"@liam-hq/cli": "0.7.24",
|
|
46
57
|
"@oxc-project/types": "0.108.0",
|
|
47
58
|
"@standard-schema/spec": "1.1.0",
|
|
59
|
+
"@tailor-platform/function-kysely-tailordb": "0.1.3",
|
|
48
60
|
"@urql/core": "6.0.1",
|
|
49
61
|
"chalk": "5.6.2",
|
|
50
62
|
"chokidar": "5.0.0",
|
|
51
63
|
"confbox": "0.2.2",
|
|
52
64
|
"consola": "3.4.2",
|
|
53
65
|
"date-fns": "4.1.0",
|
|
66
|
+
"es-toolkit": "^1.44.0",
|
|
54
67
|
"find-up-simple": "1.0.1",
|
|
55
68
|
"inflection": "3.0.2",
|
|
69
|
+
"kysely": "0.28.10",
|
|
56
70
|
"madge": "8.0.0",
|
|
57
71
|
"mime-types": "3.0.2",
|
|
58
72
|
"multiline-ts": "4.0.1",
|
|
@@ -64,6 +78,7 @@
|
|
|
64
78
|
"pkg-types": "2.3.0",
|
|
65
79
|
"politty": "^0.1.1",
|
|
66
80
|
"rolldown": "1.0.0-beta.60",
|
|
81
|
+
"serve": "14.2.5",
|
|
67
82
|
"std-env": "3.10.0",
|
|
68
83
|
"table": "6.9.0",
|
|
69
84
|
"ts-cron-validator": "1.1.5",
|
|
@@ -74,7 +89,6 @@
|
|
|
74
89
|
},
|
|
75
90
|
"devDependencies": {
|
|
76
91
|
"@eslint/js": "9.39.2",
|
|
77
|
-
"@tailor-platform/function-kysely-tailordb": "0.1.3",
|
|
78
92
|
"@tailor-platform/function-types": "0.8.0",
|
|
79
93
|
"@toiroakr/lines-db": "0.6.1",
|
|
80
94
|
"@types/madge": "5.0.3",
|
|
@@ -87,7 +101,6 @@
|
|
|
87
101
|
"eslint-plugin-jsdoc": "62.1.0",
|
|
88
102
|
"eslint-plugin-oxlint": "1.39.0",
|
|
89
103
|
"globals": "17.0.0",
|
|
90
|
-
"kysely": "0.28.2",
|
|
91
104
|
"oxlint": "1.39.0",
|
|
92
105
|
"oxlint-tsgolint": "0.11.1",
|
|
93
106
|
"sonda": "0.10.1",
|
|
@@ -96,26 +109,6 @@
|
|
|
96
109
|
"typescript-eslint": "8.53.0",
|
|
97
110
|
"vitest": "4.0.17"
|
|
98
111
|
},
|
|
99
|
-
"peerDependencies": {
|
|
100
|
-
"@liam-hq/cli": "*",
|
|
101
|
-
"@tailor-platform/function-kysely-tailordb": "*",
|
|
102
|
-
"kysely": "*",
|
|
103
|
-
"serve": "*"
|
|
104
|
-
},
|
|
105
|
-
"peerDependenciesMeta": {
|
|
106
|
-
"@liam-hq/cli": {
|
|
107
|
-
"optional": true
|
|
108
|
-
},
|
|
109
|
-
"@tailor-platform/function-kysely-tailordb": {
|
|
110
|
-
"optional": true
|
|
111
|
-
},
|
|
112
|
-
"kysely": {
|
|
113
|
-
"optional": true
|
|
114
|
-
},
|
|
115
|
-
"serve": {
|
|
116
|
-
"optional": true
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
112
|
"scripts": {
|
|
120
113
|
"test": "vitest",
|
|
121
114
|
"test:unit": "vitest --project=unit",
|