@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.
Files changed (45) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/application-DM4zTgXU.mjs +4 -0
  3. package/dist/{application-BKBo5tGD.mjs → application-DnWZVbDO.mjs} +164 -26
  4. package/dist/application-DnWZVbDO.mjs.map +1 -0
  5. package/dist/cli/index.mjs +13 -19
  6. package/dist/cli/index.mjs.map +1 -1
  7. package/dist/cli/lib.d.mts +149 -17
  8. package/dist/cli/lib.mjs +132 -4
  9. package/dist/cli/lib.mjs.map +1 -1
  10. package/dist/configure/index.d.mts +4 -3
  11. package/dist/configure/index.mjs +16 -757
  12. package/dist/configure/index.mjs.map +1 -1
  13. package/dist/env-4RO7szrH.d.mts +66 -0
  14. package/dist/{index-D8zIUsWi.d.mts → index-BBr_q3vB.d.mts} +20 -11
  15. package/dist/{index-DcOTucF6.d.mts → index-Bid18Opo.d.mts} +473 -84
  16. package/dist/{jiti-ygK9KoRA.mjs → jiti-DuCiUfMj.mjs} +2 -2
  17. package/dist/{jiti-ygK9KoRA.mjs.map → jiti-DuCiUfMj.mjs.map} +1 -1
  18. package/dist/{job-l-pIR9IY.mjs → job-zGAXCidt.mjs} +1 -1
  19. package/dist/{job-l-pIR9IY.mjs.map → job-zGAXCidt.mjs.map} +1 -1
  20. package/dist/kysely/index.d.mts +25 -0
  21. package/dist/kysely/index.mjs +27 -0
  22. package/dist/kysely/index.mjs.map +1 -0
  23. package/dist/plugin/index.d.mts +105 -0
  24. package/dist/plugin/index.mjs +45 -0
  25. package/dist/plugin/index.mjs.map +1 -0
  26. package/dist/schema-BmKdDzr1.mjs +784 -0
  27. package/dist/schema-BmKdDzr1.mjs.map +1 -0
  28. package/dist/{src-CG8kJBI9.mjs → src-QNTCsO6J.mjs} +2 -2
  29. package/dist/{src-CG8kJBI9.mjs.map → src-QNTCsO6J.mjs.map} +1 -1
  30. package/dist/types-r-ZratAg.mjs +13 -0
  31. package/dist/types-r-ZratAg.mjs.map +1 -0
  32. package/dist/{update-D0muqqOP.mjs → update-B_W-UQnS.mjs} +1626 -390
  33. package/dist/update-B_W-UQnS.mjs.map +1 -0
  34. package/dist/utils/test/index.d.mts +2 -2
  35. package/dist/utils/test/index.mjs +1 -1
  36. package/docs/cli/tailordb.md +0 -12
  37. package/docs/generator/builtin.md +2 -2
  38. package/docs/plugin/custom.md +389 -0
  39. package/docs/plugin/index.md +112 -0
  40. package/docs/services/workflow.md +28 -0
  41. package/package.json +16 -23
  42. package/dist/application-BKBo5tGD.mjs.map +0 -1
  43. package/dist/application-a12-7TT3.mjs +0 -4
  44. package/dist/update-D0muqqOP.mjs.map +0 -1
  45. /package/dist/{chunk-CIV_ash9.mjs → chunk-C3Kl5s5P.mjs} +0 -0
@@ -1,5 +1,8 @@
1
- import { a as OAuth2ClientSchema, c as tailorUserMap, d as styles, f as symbols, i as ExecutorSchema, l as loadFilesWithIgnores, n as WorkflowJobSchema, o as ResolverSchema, r as WorkflowSchema, s as stringifyFunction, t as defineApplication, u as logger } from "./application-BKBo5tGD.mjs";
1
+ import { t as isPluginGeneratedType } from "./types-r-ZratAg.mjs";
2
+ import { t as db } from "./schema-BmKdDzr1.mjs";
3
+ import { a as ExecutorSchema, c as stringifyFunction, d as logger, f as styles, i as createExecutorService, l as tailorUserMap, n as WorkflowJobSchema, o as OAuth2ClientSchema, p as symbols, r as WorkflowSchema, s as ResolverSchema, t as defineApplication, u as loadFilesWithIgnores } from "./application-DnWZVbDO.mjs";
2
4
  import { createRequire } from "node:module";
5
+ import { cloneDeep } from "es-toolkit";
3
6
  import { arg, defineCommand, runCommand } from "politty";
4
7
  import { z } from "zod";
5
8
  import * as fs$2 from "node:fs";
@@ -2126,12 +2129,12 @@ async function loadExecutor(executorFilePath) {
2126
2129
  * This function:
2127
2130
  * 1. Creates entry file that extracts operation.body
2128
2131
  * 2. Bundles in a single step with tree-shaking
2129
- * @param config - Executor file loading configuration
2130
- * @param triggerContext - Trigger context for workflow/job transformations
2132
+ * @param options - Bundle executor options
2131
2133
  * @returns Promise that resolves when bundling completes
2132
2134
  */
2133
- async function bundleExecutors(config, triggerContext) {
2134
- const files = loadFilesWithIgnores(config);
2135
+ async function bundleExecutors(options) {
2136
+ const { config, triggerContext, additionalFiles = [] } = options;
2137
+ const files = [...loadFilesWithIgnores(config), ...additionalFiles];
2135
2138
  if (files.length === 0) {
2136
2139
  logger.warn(`No executor files found for patterns: ${config.files?.join(", ") ?? "(none)"}`);
2137
2140
  return;
@@ -2680,6 +2683,163 @@ function createGeneratorConfigSchema(builtinGenerators$1) {
2680
2683
  }).brand("CodeGenerator");
2681
2684
  }
2682
2685
 
2686
+ //#endregion
2687
+ //#region src/parser/plugin-config/schema.ts
2688
+ const unauthenticatedTailorUser$1 = {
2689
+ id: "00000000-0000-0000-0000-000000000000",
2690
+ type: "",
2691
+ workspaceId: "00000000-0000-0000-0000-000000000000",
2692
+ attributes: null,
2693
+ attributeList: []
2694
+ };
2695
+ const PluginGeneratedTypeSchema = z.object({
2696
+ name: z.string(),
2697
+ fields: z.record(z.string(), z.unknown())
2698
+ });
2699
+ const PluginGeneratedResolverSchema = z.object({
2700
+ name: z.string(),
2701
+ operation: z.enum(["query", "mutation"]),
2702
+ inputFields: z.record(z.string(), z.unknown()).optional(),
2703
+ outputFields: z.record(z.string(), z.unknown()),
2704
+ body: z.string()
2705
+ });
2706
+ const PluginTriggerConfigSchema = z.object({
2707
+ kind: z.enum([
2708
+ "recordCreated",
2709
+ "recordUpdated",
2710
+ "recordDeleted",
2711
+ "schedule",
2712
+ "incomingWebhook"
2713
+ ]),
2714
+ type: z.string().optional(),
2715
+ schedule: z.string().optional()
2716
+ });
2717
+ const PluginOperationConfigSchema = z.object({
2718
+ kind: z.enum([
2719
+ "function",
2720
+ "webhook",
2721
+ "graphql",
2722
+ "workflow"
2723
+ ]),
2724
+ body: z.string().optional(),
2725
+ url: z.string().optional(),
2726
+ query: z.string().optional()
2727
+ });
2728
+ const PluginGeneratedExecutorSchema = z.object({
2729
+ name: z.string(),
2730
+ description: z.string().optional(),
2731
+ trigger: PluginTriggerConfigSchema,
2732
+ operation: PluginOperationConfigSchema
2733
+ });
2734
+ z.object({
2735
+ types: z.array(PluginGeneratedTypeSchema).optional(),
2736
+ resolvers: z.array(PluginGeneratedResolverSchema).optional(),
2737
+ executors: z.array(PluginGeneratedExecutorSchema).optional()
2738
+ });
2739
+ const CustomPluginSchema = z.object({
2740
+ id: z.string(),
2741
+ description: z.string(),
2742
+ importPath: z.string(),
2743
+ configSchema: z.any().optional(),
2744
+ pluginConfigSchema: z.any().optional(),
2745
+ pluginConfig: z.any().optional(),
2746
+ process: z.any().optional(),
2747
+ processNamespace: z.any().optional()
2748
+ }).superRefine((plugin, ctx) => {
2749
+ if (plugin.process && !plugin.configSchema) ctx.addIssue({
2750
+ code: z.ZodIssueCode.custom,
2751
+ message: "process requires configSchema to be defined.",
2752
+ path: ["configSchema"]
2753
+ });
2754
+ }).passthrough();
2755
+ const CustomPluginTupleSchema = z.tuple([CustomPluginSchema, z.unknown()]);
2756
+ /**
2757
+ * Type guard to check if a value is a PluginBase object
2758
+ * @param value - Value to check
2759
+ * @returns True if value is a PluginBase object
2760
+ */
2761
+ function isPluginBase(value) {
2762
+ return CustomPluginSchema.safeParse(value).success;
2763
+ }
2764
+ function normalizePluginConfigSchema(schema) {
2765
+ const seen = /* @__PURE__ */ new Set();
2766
+ const stack = [schema];
2767
+ while (stack.length > 0) {
2768
+ const field = stack.pop();
2769
+ if (!field || seen.has(field)) continue;
2770
+ seen.add(field);
2771
+ const requiredExplicit = field._metadata.requiredExplicit === true;
2772
+ field._metadata.required = requiredExplicit;
2773
+ for (const nestedField of Object.values(field.fields)) stack.push(nestedField);
2774
+ }
2775
+ return schema;
2776
+ }
2777
+ function clonePluginConfigSchema(schema) {
2778
+ return cloneDeep(schema);
2779
+ }
2780
+ function normalizePluginBase(plugin) {
2781
+ let normalized = plugin;
2782
+ if (normalized.configSchema) {
2783
+ const clonedConfigSchema = clonePluginConfigSchema(normalized.configSchema);
2784
+ normalizePluginConfigSchema(clonedConfigSchema);
2785
+ normalized = {
2786
+ ...normalized,
2787
+ configSchema: clonedConfigSchema
2788
+ };
2789
+ }
2790
+ if (normalized.pluginConfigSchema) {
2791
+ const pluginConfigSchema = clonePluginConfigSchema(normalized.pluginConfigSchema);
2792
+ normalizePluginConfigSchema(pluginConfigSchema);
2793
+ normalized = {
2794
+ ...normalized,
2795
+ pluginConfigSchema
2796
+ };
2797
+ if (normalized.pluginConfig !== void 0) {
2798
+ const validationErrors = validatePluginConfig$1(normalized.pluginConfig, pluginConfigSchema);
2799
+ if (validationErrors.length > 0) {
2800
+ const errorDetails = validationErrors.map((e) => e.field ? `${e.field}: ${e.message}` : e.message).join("; ");
2801
+ throw new Error(`Invalid pluginConfig for plugin "${normalized.id}": ${errorDetails}`);
2802
+ }
2803
+ }
2804
+ }
2805
+ return normalized;
2806
+ }
2807
+ /**
2808
+ * Validate plugin config against its schema
2809
+ * @param config - The config object to validate
2810
+ * @param schema - The schema defining expected fields
2811
+ * @returns Array of validation errors (empty if valid)
2812
+ */
2813
+ function validatePluginConfig$1(config, schema) {
2814
+ const result = schema.parse({
2815
+ value: config,
2816
+ data: config,
2817
+ user: unauthenticatedTailorUser$1
2818
+ });
2819
+ if ("issues" in result && result.issues) return result.issues.map((issue) => ({
2820
+ field: Array.isArray(issue.path) ? issue.path.join(".") : "",
2821
+ message: issue.message
2822
+ }));
2823
+ return [];
2824
+ }
2825
+ /**
2826
+ * Creates a PluginConfigSchema for custom plugins
2827
+ * @returns Plugin config schema that validates and transforms PluginBase instances
2828
+ */
2829
+ function createPluginConfigSchema() {
2830
+ return z.union([CustomPluginSchema, CustomPluginTupleSchema]).transform((plugin) => {
2831
+ if (Array.isArray(plugin)) {
2832
+ const [first, options] = plugin;
2833
+ if (isPluginBase(first)) return normalizePluginBase({
2834
+ ...first,
2835
+ pluginConfig: options
2836
+ });
2837
+ throw new Error(`Invalid plugin configuration: expected PluginBase object`);
2838
+ }
2839
+ return normalizePluginBase(plugin);
2840
+ }).brand("Plugin");
2841
+ }
2842
+
2683
2843
  //#endregion
2684
2844
  //#region src/cli/generator/types.ts
2685
2845
  /**
@@ -3118,28 +3278,22 @@ function generateUnifiedKyselyTypes(namespaceData) {
3118
3278
  Timestamp: false,
3119
3279
  Serial: false
3120
3280
  });
3121
- const utilityTypeDeclarations = [];
3122
- if (globalUsedUtilityTypes.Timestamp) utilityTypeDeclarations.push(`type Timestamp = ColumnType<Date, Date | string, Date | string>;`);
3123
- utilityTypeDeclarations.push(ml`
3124
- type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
3125
- ? ColumnType<S, I | undefined, U>
3126
- : ColumnType<T, T | undefined, T>;
3127
- `);
3128
- if (globalUsedUtilityTypes.Serial) utilityTypeDeclarations.push(`type Serial<T = string | number> = ColumnType<T, never, never>;`);
3281
+ const utilityTypeImports = ["type Generated"];
3282
+ if (globalUsedUtilityTypes.Timestamp) utilityTypeImports.push("type Timestamp");
3283
+ if (globalUsedUtilityTypes.Serial) utilityTypeImports.push("type Serial");
3129
3284
  return [
3130
3285
  ml`
3131
3286
  import {
3132
- type ColumnType,
3133
- Kysely,
3134
- type KyselyConfig,
3135
- type Transaction as KyselyTransaction,
3136
- type Insertable as KyselyInsertable,
3137
- type Selectable as KyselySelectable,
3138
- type Updateable as KyselyUpdateable,
3139
- } from "kysely";
3140
- import { TailordbDialect } from "@tailor-platform/function-kysely-tailordb";
3141
-
3142
- ${utilityTypeDeclarations.join("\n")}
3287
+ createGetDB,
3288
+ ${utilityTypeImports.join(",\n")},
3289
+ type NamespaceDB,
3290
+ type NamespaceInsertable,
3291
+ type NamespaceSelectable,
3292
+ type NamespaceTable,
3293
+ type NamespaceTableName,
3294
+ type NamespaceTransaction,
3295
+ type NamespaceUpdateable,
3296
+ } from "@tailor-platform/sdk/kysely";
3143
3297
  `,
3144
3298
  `export interface Namespace {\n${namespaceData.map(({ namespace, types }) => {
3145
3299
  return ` "${namespace}": {\n${types.map((type) => {
@@ -3147,44 +3301,20 @@ function generateUnifiedKyselyTypes(namespaceData) {
3147
3301
  }).join("\n\n")}\n }`;
3148
3302
  }).join(",\n")}\n}`,
3149
3303
  ml`
3150
- export function getDB<const N extends keyof Namespace>(
3151
- namespace: N,
3152
- kyselyConfig?: Omit<KyselyConfig, "dialect">,
3153
- ): Kysely<Namespace[N]> {
3154
- const client = new tailordb.Client({ namespace });
3155
- return new Kysely<Namespace[N]>({
3156
- dialect: new TailordbDialect(client),
3157
- ...kyselyConfig,
3158
- });
3159
- }
3304
+ export const getDB = createGetDB<Namespace>();
3160
3305
 
3161
- export type DB<N extends keyof Namespace = keyof Namespace> = ReturnType<typeof getDB<N>>;
3306
+ export type DB<N extends keyof Namespace = keyof Namespace> = NamespaceDB<Namespace, N>;
3162
3307
  `,
3163
3308
  ml`
3164
3309
  export type Transaction<K extends keyof Namespace | DB = keyof Namespace> =
3165
- K extends DB<infer N>
3166
- ? KyselyTransaction<Namespace[N]>
3167
- : K extends keyof Namespace
3168
- ? KyselyTransaction<Namespace[K]>
3169
- : never;
3170
-
3171
- type TableName = {
3172
- [N in keyof Namespace]: keyof Namespace[N];
3173
- }[keyof Namespace];
3174
- export type Table<T extends TableName> = {
3175
- [N in keyof Namespace]: T extends keyof Namespace[N] ? Namespace[N][T]
3176
- : never;
3177
- }[keyof Namespace];
3178
-
3179
- export type Insertable<T extends keyof Namespace[keyof Namespace]> = KyselyInsertable<
3180
- Table<T>
3181
- >;
3182
- export type Selectable<T extends keyof Namespace[keyof Namespace]> = KyselySelectable<
3183
- Table<T>
3184
- >;
3185
- export type Updateable<T extends keyof Namespace[keyof Namespace]> = KyselyUpdateable<
3186
- Table<T>
3187
- >;
3310
+ NamespaceTransaction<Namespace, K>;
3311
+
3312
+ type TableName = NamespaceTableName<Namespace>;
3313
+ export type Table<T extends TableName> = NamespaceTable<Namespace, T>;
3314
+
3315
+ export type Insertable<T extends TableName> = NamespaceInsertable<Namespace, T>;
3316
+ export type Selectable<T extends TableName> = NamespaceSelectable<Namespace, T>;
3317
+ export type Updateable<T extends TableName> = NamespaceUpdateable<Namespace, T>;
3188
3318
  `
3189
3319
  ].join("\n\n") + "\n";
3190
3320
  }
@@ -3237,6 +3367,32 @@ function createKyselyGenerator(options) {
3237
3367
  };
3238
3368
  }
3239
3369
 
3370
+ //#endregion
3371
+ //#region src/cli/utils/plugin-import.ts
3372
+ /**
3373
+ * Collect base directories for resolving plugin import paths.
3374
+ * @param configPath - Path to tailor.config.ts
3375
+ * @returns Ordered list of base directories
3376
+ */
3377
+ function getPluginImportBaseDirs(configPath) {
3378
+ if (configPath) return [path.dirname(configPath)];
3379
+ return [process.cwd()];
3380
+ }
3381
+ /**
3382
+ * Resolve a relative plugin import path against candidate base directories.
3383
+ * @param pluginImportPath - Relative plugin import path
3384
+ * @param baseDirs - Candidate base directories
3385
+ * @returns Absolute path if found, otherwise null
3386
+ */
3387
+ function resolveRelativePluginImportPath(pluginImportPath, baseDirs) {
3388
+ if (!pluginImportPath.startsWith(".")) return null;
3389
+ for (const baseDir of baseDirs) {
3390
+ const absolutePath = path.resolve(baseDir, pluginImportPath);
3391
+ if (fs$2.existsSync(absolutePath)) return absolutePath;
3392
+ }
3393
+ return null;
3394
+ }
3395
+
3240
3396
  //#endregion
3241
3397
  //#region src/cli/generator/builtin/seed/idp-user-processor.ts
3242
3398
  /**
@@ -3314,7 +3470,45 @@ function generateIdpUserSchemaFile(usernameField, userTypeName) {
3314
3470
  * @returns Generated lines-db metadata
3315
3471
  */
3316
3472
  function processLinesDb(type, source) {
3317
- if (!source.filePath || !source.exportName) throw new Error(`Missing source info for type ${type.name}`);
3473
+ if (isPluginGeneratedType(source)) return processLinesDbForPluginType(type, source);
3474
+ if (!source.filePath) throw new Error(`Missing source info for type ${type.name}`);
3475
+ if (!source.exportName) throw new Error(`Missing export name for type ${type.name}`);
3476
+ const { optionalFields, omitFields, indexes, foreignKeys } = extractFieldMetadata(type);
3477
+ return {
3478
+ typeName: type.name,
3479
+ exportName: source.exportName,
3480
+ importPath: source.filePath,
3481
+ optionalFields,
3482
+ omitFields,
3483
+ foreignKeys,
3484
+ indexes
3485
+ };
3486
+ }
3487
+ /**
3488
+ * Process lines-db metadata for plugin-generated types
3489
+ * @param type - Parsed TailorDB type
3490
+ * @param source - Plugin-generated type source info
3491
+ * @returns Generated lines-db metadata with plugin source
3492
+ */
3493
+ function processLinesDbForPluginType(type, source) {
3494
+ const { optionalFields, omitFields, indexes, foreignKeys } = extractFieldMetadata(type);
3495
+ return {
3496
+ typeName: type.name,
3497
+ exportName: source.exportName,
3498
+ importPath: "",
3499
+ optionalFields,
3500
+ omitFields,
3501
+ foreignKeys,
3502
+ indexes,
3503
+ pluginSource: source
3504
+ };
3505
+ }
3506
+ /**
3507
+ * Extract field metadata from TailorDB type
3508
+ * @param type - Parsed TailorDB type
3509
+ * @returns Field metadata including optional fields, omit fields, indexes, and foreign keys
3510
+ */
3511
+ function extractFieldMetadata(type) {
3318
3512
  const optionalFields = ["id"];
3319
3513
  const omitFields = [];
3320
3514
  const indexes = [];
@@ -3341,29 +3535,19 @@ function processLinesDb(type, source) {
3341
3535
  }
3342
3536
  });
3343
3537
  return {
3344
- typeName: type.name,
3345
- exportName: source.exportName,
3346
- importPath: source.filePath,
3347
3538
  optionalFields,
3348
3539
  omitFields,
3349
- foreignKeys,
3350
- indexes
3540
+ indexes,
3541
+ foreignKeys
3351
3542
  };
3352
3543
  }
3353
3544
  /**
3354
- * Generates the schema file content for lines-db
3355
- * @param metadata - lines-db metadata
3356
- * @param importPath - Import path for the TailorDB type
3357
- * @returns Schema file contents
3545
+ * Generate schema options code for lines-db
3546
+ * @param foreignKeys - Foreign key definitions
3547
+ * @param indexes - Index definitions
3548
+ * @returns Schema options code string
3358
3549
  */
3359
- function generateLinesDbSchemaFile(metadata, importPath) {
3360
- const { exportName, optionalFields, omitFields, foreignKeys, indexes } = metadata;
3361
- const schemaTypeCode = ml`
3362
- const schemaType = t.object({
3363
- ...${exportName}.pickFields(${JSON.stringify(optionalFields)}, { optional: true }),
3364
- ...${exportName}.omitFields(${JSON.stringify([...optionalFields, ...omitFields])}),
3365
- });
3366
- `;
3550
+ function generateSchemaOptions(foreignKeys, indexes) {
3367
3551
  const schemaOptions = [];
3368
3552
  if (foreignKeys.length > 0) {
3369
3553
  schemaOptions.push(`foreignKeys: [`);
@@ -3379,22 +3563,92 @@ function generateLinesDbSchemaFile(metadata, importPath) {
3379
3563
  });
3380
3564
  schemaOptions.push("],");
3381
3565
  }
3566
+ return schemaOptions.length > 0 ? [
3567
+ "\n {",
3568
+ ...schemaOptions.map((option) => ` ${option}`),
3569
+ " }"
3570
+ ].join("\n") : "";
3571
+ }
3572
+ /**
3573
+ * Generates the schema file content for lines-db (for user-defined types with import)
3574
+ * @param metadata - lines-db metadata
3575
+ * @param importPath - Import path for the TailorDB type
3576
+ * @returns Schema file contents
3577
+ */
3578
+ function generateLinesDbSchemaFile(metadata, importPath) {
3579
+ const { exportName, optionalFields, omitFields, foreignKeys, indexes } = metadata;
3382
3580
  return ml`
3383
3581
  import { t } from "@tailor-platform/sdk";
3384
3582
  import { createTailorDBHook, createStandardSchema } from "@tailor-platform/sdk/test";
3385
3583
  import { defineSchema } from "@toiroakr/lines-db";
3386
3584
  import { ${exportName} } from "${importPath}";
3387
3585
 
3586
+ ${ml`
3587
+ const schemaType = t.object({
3588
+ ...${exportName}.pickFields(${JSON.stringify(optionalFields)}, { optional: true }),
3589
+ ...${exportName}.omitFields(${JSON.stringify([...optionalFields, ...omitFields])}),
3590
+ });
3591
+ `}
3592
+
3593
+ const hook = createTailorDBHook(${exportName});
3594
+
3595
+ export const schema = defineSchema(
3596
+ createStandardSchema(schemaType, hook),${generateSchemaOptions(foreignKeys, indexes)}
3597
+ );
3598
+
3599
+ `;
3600
+ }
3601
+ /**
3602
+ * Generates the schema file content using getGeneratedType API
3603
+ * (for plugin-generated types)
3604
+ * @param metadata - lines-db metadata (must have pluginSource)
3605
+ * @param params - Plugin import paths
3606
+ * @returns Schema file contents
3607
+ */
3608
+ function generateLinesDbSchemaFileWithPluginAPI(metadata, params) {
3609
+ const { typeName, exportName, optionalFields, omitFields, foreignKeys, indexes, pluginSource } = metadata;
3610
+ if (!pluginSource) throw new Error(`pluginSource is required for plugin-generated type "${typeName}"`);
3611
+ const { pluginImportPath, originalImportPath } = params;
3612
+ const schemaTypeCode = ml`
3613
+ const schemaType = t.object({
3614
+ ...${exportName}.pickFields(${JSON.stringify(optionalFields)}, { optional: true }),
3615
+ ...${exportName}.omitFields(${JSON.stringify([...optionalFields, ...omitFields])}),
3616
+ });
3617
+ `;
3618
+ const schemaOptionsCode = generateSchemaOptions(foreignKeys, indexes);
3619
+ if (pluginSource.originalExportName && originalImportPath && pluginSource.generatedTypeKind) return ml`
3620
+ import { t } from "@tailor-platform/sdk";
3621
+ import { createTailorDBHook, createStandardSchema } from "@tailor-platform/sdk/test";
3622
+ import { defineSchema } from "@toiroakr/lines-db";
3623
+ import { getGeneratedType } from "${pluginImportPath}";
3624
+ import { ${pluginSource.originalExportName} } from "${originalImportPath}";
3625
+
3626
+ const ${exportName} = getGeneratedType(${pluginSource.originalExportName}, "${pluginSource.generatedTypeKind}");
3627
+
3388
3628
  ${schemaTypeCode}
3389
3629
 
3390
3630
  const hook = createTailorDBHook(${exportName});
3391
3631
 
3392
3632
  export const schema = defineSchema(
3393
- createStandardSchema(schemaType, hook),${schemaOptions.length > 0 ? [
3394
- "\n {",
3395
- ...schemaOptions.map((option) => ` ${option}`),
3396
- " }"
3397
- ].join("\n") : ""}
3633
+ createStandardSchema(schemaType, hook),${schemaOptionsCode}
3634
+ );
3635
+
3636
+ `;
3637
+ if (!pluginSource.generatedTypeKind) throw new Error(`Namespace plugin "${pluginSource.pluginId}" must provide generatedTypeKind for type "${typeName}"`);
3638
+ return ml`
3639
+ import { t } from "@tailor-platform/sdk";
3640
+ import { createTailorDBHook, createStandardSchema } from "@tailor-platform/sdk/test";
3641
+ import { defineSchema } from "@toiroakr/lines-db";
3642
+ import { getGeneratedType } from "${pluginImportPath}";
3643
+
3644
+ const ${exportName} = getGeneratedType(null, "${pluginSource.generatedTypeKind}");
3645
+
3646
+ ${schemaTypeCode}
3647
+
3648
+ const hook = createTailorDBHook(${exportName});
3649
+
3650
+ export const schema = defineSchema(
3651
+ createStandardSchema(schemaType, hook),${schemaOptionsCode}
3398
3652
  );
3399
3653
 
3400
3654
  `;
@@ -3512,6 +3766,7 @@ function generateExecScript(machineUserName, relativeConfigPath, namespaceConfig
3512
3766
  getMachineUserToken,
3513
3767
  truncate,
3514
3768
  bundleSeedScript,
3769
+ chunkSeedData,
3515
3770
  executeScript,
3516
3771
  initOperatorClient,
3517
3772
  loadAccessToken,
@@ -3782,55 +4037,89 @@ ${namespaceConfigs.map(({ namespace, dependencies }) => {
3782
4037
  // Bundle seed script
3783
4038
  const bundled = await bundleSeedScript(namespace, typesWithData);
3784
4039
 
3785
- // Execute seed script
3786
- const result = await executeScript({
3787
- client: operatorClient,
3788
- workspaceId,
3789
- name: \`seed-\${namespace}.ts\`,
3790
- code: bundled.bundledCode,
3791
- arg: JSON.stringify({ data, order: sortedTypes }),
3792
- invoker: {
3793
- namespace: authNamespace,
3794
- machineUserName: "${machineUserName}",
3795
- },
4040
+ // Chunk seed data to fit within gRPC message size limits
4041
+ const chunks = chunkSeedData({
4042
+ data,
4043
+ order: sortedTypes,
4044
+ codeByteSize: new TextEncoder().encode(bundled.bundledCode).length,
3796
4045
  });
3797
4046
 
3798
- // Parse result and display logs
3799
- if (result.logs) {
3800
- for (const line of result.logs.split("\\n").filter(Boolean)) {
3801
- console.log(styleText("dim", \` \${line}\`));
3802
- }
4047
+ if (chunks.length === 0) {
4048
+ console.log(styleText("dim", \` [\${namespace}] No data to seed\`));
4049
+ return { success: true, processed: {} };
3803
4050
  }
3804
4051
 
3805
- if (result.success) {
3806
- let parsed;
3807
- try {
3808
- const parsedResult = JSON.parse(result.result || "{}");
3809
- parsed = parsedResult && typeof parsedResult === "object" ? parsedResult : {};
3810
- } catch (error) {
3811
- const message = error instanceof Error ? error.message : String(error);
3812
- console.error(styleText("red", \` ✗ Failed to parse seed result: \${message}\`));
3813
- return { success: false, error: message };
4052
+ if (chunks.length > 1) {
4053
+ console.log(styleText("dim", \` Split into \${chunks.length} chunks\`));
4054
+ }
4055
+
4056
+ const allProcessed = {};
4057
+ let hasError = false;
4058
+ const allErrors = [];
4059
+
4060
+ for (const chunk of chunks) {
4061
+ if (chunks.length > 1) {
4062
+ console.log(styleText("dim", \` Chunk \${chunk.index + 1}/\${chunk.total}: \${chunk.order.join(", ")}\`));
3814
4063
  }
3815
4064
 
3816
- const processed = parsed.processed || {};
3817
- for (const [type, count] of Object.entries(processed)) {
3818
- console.log(styleText("green", \` ✓ \${type}: \${count} rows inserted\`));
4065
+ // Execute seed script for this chunk
4066
+ const result = await executeScript({
4067
+ client: operatorClient,
4068
+ workspaceId,
4069
+ name: \`seed-\${namespace}.ts\`,
4070
+ code: bundled.bundledCode,
4071
+ arg: JSON.stringify({ data: chunk.data, order: chunk.order }),
4072
+ invoker: {
4073
+ namespace: authNamespace,
4074
+ machineUserName: "${machineUserName}",
4075
+ },
4076
+ });
4077
+
4078
+ // Parse result and display logs
4079
+ if (result.logs) {
4080
+ for (const line of result.logs.split("\\n").filter(Boolean)) {
4081
+ console.log(styleText("dim", \` \${line}\`));
4082
+ }
3819
4083
  }
3820
4084
 
3821
- if (!parsed.success) {
3822
- const errors = Array.isArray(parsed.errors) ? parsed.errors : [];
3823
- const errorMessage =
3824
- errors.length > 0 ? errors.join("\\n") : "Seed script reported failure";
3825
- console.error(styleText("red", \` ✗ Seed failed: \${errorMessage}\`));
3826
- return { success: false, error: errorMessage };
4085
+ if (result.success) {
4086
+ let parsed;
4087
+ try {
4088
+ const parsedResult = JSON.parse(result.result || "{}");
4089
+ parsed = parsedResult && typeof parsedResult === "object" ? parsedResult : {};
4090
+ } catch (error) {
4091
+ const message = error instanceof Error ? error.message : String(error);
4092
+ console.error(styleText("red", \` ✗ Failed to parse seed result: \${message}\`));
4093
+ hasError = true;
4094
+ allErrors.push(message);
4095
+ continue;
4096
+ }
4097
+
4098
+ const processed = parsed.processed || {};
4099
+ for (const [type, count] of Object.entries(processed)) {
4100
+ allProcessed[type] = (allProcessed[type] || 0) + count;
4101
+ console.log(styleText("green", \` ✓ \${type}: \${count} rows inserted\`));
4102
+ }
4103
+
4104
+ if (!parsed.success) {
4105
+ const errors = Array.isArray(parsed.errors) ? parsed.errors : [];
4106
+ const errorMessage =
4107
+ errors.length > 0 ? errors.join("\\n") : "Seed script reported failure";
4108
+ console.error(styleText("red", \` ✗ Seed failed: \${errorMessage}\`));
4109
+ hasError = true;
4110
+ allErrors.push(errorMessage);
4111
+ }
4112
+ } else {
4113
+ console.error(styleText("red", \` ✗ Seed failed: \${result.error}\`));
4114
+ hasError = true;
4115
+ allErrors.push(result.error);
3827
4116
  }
4117
+ }
3828
4118
 
3829
- return { success: true, processed };
3830
- } else {
3831
- console.error(styleText("red", \` ✗ Seed failed: \${result.error}\`));
3832
- return { success: false, error: result.error };
4119
+ if (hasError) {
4120
+ return { success: false, error: allErrors.join("\\n") };
3833
4121
  }
4122
+ return { success: true, processed: allProcessed };
3834
4123
  };
3835
4124
 
3836
4125
  ${generateIdpUserSeedFunction(hasIdpUser)}
@@ -3896,6 +4185,7 @@ function createSeedGenerator(options) {
3896
4185
  processTailorDBNamespace: ({ types }) => types,
3897
4186
  aggregate: ({ input, configPath }) => {
3898
4187
  const files = [];
4188
+ const pluginImportBaseDirs = getPluginImportBaseDirs(configPath);
3899
4189
  const namespaceConfigs = [];
3900
4190
  for (const nsResult of input.tailordb) {
3901
4191
  if (!nsResult.types) continue;
@@ -3912,12 +4202,34 @@ function createSeedGenerator(options) {
3912
4202
  skipIfExists: true
3913
4203
  });
3914
4204
  const schemaOutputPath = path.join(outputBaseDir, "data", `${linesDb.typeName}.schema.ts`);
3915
- const importPath = path.relative(path.dirname(schemaOutputPath), linesDb.importPath);
3916
- const normalizedImportPath = importPath.replace(/\.ts$/, "").startsWith(".") ? importPath.replace(/\.ts$/, "") : `./${importPath.replace(/\.ts$/, "")}`;
3917
- files.push({
3918
- path: schemaOutputPath,
3919
- content: generateLinesDbSchemaFile(linesDb, normalizedImportPath)
3920
- });
4205
+ if (linesDb.pluginSource && linesDb.pluginSource.pluginImportPath) {
4206
+ let originalImportPath;
4207
+ if (linesDb.pluginSource.originalFilePath && linesDb.pluginSource.originalExportName) {
4208
+ const relativePath = path.relative(path.dirname(schemaOutputPath), linesDb.pluginSource.originalFilePath);
4209
+ originalImportPath = relativePath.replace(/\.ts$/, "").startsWith(".") ? relativePath.replace(/\.ts$/, "") : `./${relativePath.replace(/\.ts$/, "")}`;
4210
+ }
4211
+ let pluginImportPath = linesDb.pluginSource.pluginImportPath;
4212
+ if (pluginImportPath.startsWith("./") || pluginImportPath.startsWith("../")) {
4213
+ const resolvedPluginPath = resolveRelativePluginImportPath(pluginImportPath, pluginImportBaseDirs) ?? path.resolve(pluginImportBaseDirs[0] ?? process.cwd(), pluginImportPath);
4214
+ const relativePluginPath = path.relative(path.dirname(schemaOutputPath), resolvedPluginPath);
4215
+ pluginImportPath = relativePluginPath.startsWith(".") ? relativePluginPath : `./${relativePluginPath}`;
4216
+ }
4217
+ const schemaContent = generateLinesDbSchemaFileWithPluginAPI(linesDb, {
4218
+ pluginImportPath,
4219
+ originalImportPath
4220
+ });
4221
+ files.push({
4222
+ path: schemaOutputPath,
4223
+ content: schemaContent
4224
+ });
4225
+ } else {
4226
+ const relativePath = path.relative(path.dirname(schemaOutputPath), linesDb.importPath);
4227
+ const schemaContent = generateLinesDbSchemaFile(linesDb, relativePath.replace(/\.ts$/, "").startsWith(".") ? relativePath.replace(/\.ts$/, "") : `./${relativePath.replace(/\.ts$/, "")}`);
4228
+ files.push({
4229
+ path: schemaOutputPath,
4230
+ content: schemaContent
4231
+ });
4232
+ }
3921
4233
  }
3922
4234
  namespaceConfigs.push({
3923
4235
  namespace: nsResult.namespace,
@@ -3971,10 +4283,11 @@ const builtinGenerators = new Map([
3971
4283
  [FileUtilsGeneratorID, (options) => createFileUtilsGenerator(options)]
3972
4284
  ]);
3973
4285
  const GeneratorConfigSchema = createGeneratorConfigSchema(builtinGenerators);
4286
+ const PluginConfigSchema = createPluginConfigSchema();
3974
4287
  /**
3975
- * Load Tailor configuration file and associated generators.
4288
+ * Load Tailor configuration file and associated generators and plugins.
3976
4289
  * @param configPath - Optional explicit config path
3977
- * @returns Loaded config and generators
4290
+ * @returns Loaded config, generators, plugins, and config path
3978
4291
  */
3979
4292
  async function loadConfig(configPath) {
3980
4293
  const foundPath = loadConfigPath(configPath);
@@ -3984,8 +4297,9 @@ async function loadConfig(configPath) {
3984
4297
  const configModule = await import(pathToFileURL(resolvedPath).href);
3985
4298
  if (!configModule || !configModule.default) throw new Error("Invalid Tailor config module: default export not found");
3986
4299
  const allGenerators = [];
4300
+ const allPlugins = [];
3987
4301
  for (const value of Object.values(configModule)) if (Array.isArray(value)) {
3988
- const parsed = value.reduce((acc, item) => {
4302
+ const generatorParsed = value.reduce((acc, item) => {
3989
4303
  if (!acc.success) return acc;
3990
4304
  const result = GeneratorConfigSchema.safeParse(item);
3991
4305
  if (result.success) acc.items.push(result.data);
@@ -3995,16 +4309,532 @@ async function loadConfig(configPath) {
3995
4309
  success: true,
3996
4310
  items: []
3997
4311
  });
3998
- allGenerators.push(...parsed.items);
4312
+ if (generatorParsed.success && generatorParsed.items.length > 0) {
4313
+ allGenerators.push(...generatorParsed.items);
4314
+ continue;
4315
+ }
4316
+ const pluginParsed = value.reduce((acc, item) => {
4317
+ if (!acc.success) return acc;
4318
+ const result = PluginConfigSchema.safeParse(item);
4319
+ if (result.success) acc.items.push(result.data);
4320
+ else acc.success = false;
4321
+ return acc;
4322
+ }, {
4323
+ success: true,
4324
+ items: []
4325
+ });
4326
+ if (pluginParsed.success && pluginParsed.items.length > 0) allPlugins.push(...pluginParsed.items);
3999
4327
  }
4000
4328
  return {
4001
4329
  config: {
4002
4330
  ...configModule.default,
4003
4331
  path: resolvedPath
4004
4332
  },
4005
- generators: allGenerators
4333
+ generators: allGenerators,
4334
+ plugins: allPlugins
4335
+ };
4336
+ }
4337
+
4338
+ //#endregion
4339
+ //#region src/parser/plugin-config/types.ts
4340
+ /**
4341
+ * Type guard to check if executor uses the new dynamic import format.
4342
+ * @param executor - Executor definition to check
4343
+ * @returns True if executor uses dynamic import format
4344
+ */
4345
+ function isPluginExecutorWithFile(executor) {
4346
+ return "resolve" in executor && "context" in executor;
4347
+ }
4348
+
4349
+ //#endregion
4350
+ //#region src/cli/generator/plugin-executor-generator.ts
4351
+ /**
4352
+ * Plugin Executor Generator
4353
+ *
4354
+ * Generates TypeScript files for plugin-generated executors.
4355
+ * Supports both legacy format (inline trigger/operation) and new format (executorFile/context).
4356
+ */
4357
+ /**
4358
+ * Generate TypeScript files for plugin-generated executors.
4359
+ * These files will be processed by the standard executor bundler.
4360
+ * @param executors - Array of plugin executor information
4361
+ * @param outputDir - Base output directory (e.g., .tailor-sdk)
4362
+ * @param typeGenerationResult - Result from plugin type generation (for import resolution)
4363
+ * @param sourceTypeInfoMap - Map of source type names to their source info
4364
+ * @param configPath - Path to tailor.config.ts (used for resolving plugin import paths)
4365
+ * @returns Array of generated file paths
4366
+ */
4367
+ function generatePluginExecutorFiles(executors, outputDir, typeGenerationResult, sourceTypeInfoMap, configPath) {
4368
+ if (executors.length === 0) return [];
4369
+ const generatedFiles = [];
4370
+ const baseDirs = getPluginImportBaseDirs(configPath);
4371
+ for (const info of executors) {
4372
+ const filePath = generateSingleExecutorFile(info, outputDir, typeGenerationResult, sourceTypeInfoMap, baseDirs);
4373
+ generatedFiles.push(filePath);
4374
+ const relativePath = path.relative(process.cwd(), filePath);
4375
+ logger.log(` Plugin Executor File: ${styles.success(relativePath)} from plugin ${styles.info(info.pluginId)}`);
4376
+ }
4377
+ return generatedFiles;
4378
+ }
4379
+ /**
4380
+ * Generate a single executor file.
4381
+ * @param info - Plugin executor metadata and definition
4382
+ * @param outputDir - Base output directory (e.g., .tailor-sdk)
4383
+ * @param typeGenerationResult - Result from plugin type generation
4384
+ * @param sourceTypeInfoMap - Map of source type names to their source info
4385
+ * @param baseDirs - Base directories for resolving plugin import paths
4386
+ * @returns Absolute path to the generated file
4387
+ */
4388
+ function generateSingleExecutorFile(info, outputDir, typeGenerationResult, sourceTypeInfoMap, baseDirs = []) {
4389
+ const pluginDir = sanitizePluginId$1(info.pluginId);
4390
+ const executorOutputDir = path.join(outputDir, pluginDir, "executors");
4391
+ fs$2.mkdirSync(executorOutputDir, { recursive: true });
4392
+ const fileName = sanitizeExecutorFileName(info.executor.name);
4393
+ const filePath = path.join(executorOutputDir, `${fileName}.ts`);
4394
+ let content;
4395
+ if (isPluginExecutorWithFile(info.executor)) content = generateExecutorFileContentNew(info, info.executor, outputDir, typeGenerationResult, sourceTypeInfoMap, baseDirs);
4396
+ else content = generateExecutorFileContentLegacy(info.executor);
4397
+ fs$2.writeFileSync(filePath, content);
4398
+ return filePath;
4399
+ }
4400
+ /**
4401
+ * Generate TypeScript file content for new format executor (dynamic import).
4402
+ * Uses the executor's resolve function to dynamically import the module.
4403
+ * @param info - Plugin executor information
4404
+ * @param executor - Executor definition with resolve
4405
+ * @param outputDir - Base output directory
4406
+ * @param typeGenerationResult - Result from plugin type generation
4407
+ * @param sourceTypeInfoMap - Map of source type names to their source info
4408
+ * @param baseDirs - Base directories for resolving plugin import paths
4409
+ * @returns TypeScript source code for executor file
4410
+ */
4411
+ function generateExecutorFileContentNew(info, executor, outputDir, typeGenerationResult, sourceTypeInfoMap, baseDirs = []) {
4412
+ const { resolve, context } = executor;
4413
+ const pluginDir = sanitizePluginId$1(info.pluginId);
4414
+ const executorOutputDir = path.join(outputDir, pluginDir, "executors");
4415
+ const executorImportPath = resolveExecutorImportPath(resolve, info.pluginImportPath, executorOutputDir, baseDirs);
4416
+ const typeImports = collectTypeImports(context, outputDir, info.pluginId, typeGenerationResult, sourceTypeInfoMap);
4417
+ const imports = [];
4418
+ for (const [, importInfo] of typeImports) imports.push(`import { ${importInfo.variableName} } from "${importInfo.importPath}";`);
4419
+ const contextCode = generateContextCode(context, typeImports);
4420
+ return ml`
4421
+ /**
4422
+ * Auto-generated executor by plugin: ${info.pluginId}
4423
+ * DO NOT EDIT - This file is generated by @tailor-platform/sdk
4424
+ */
4425
+ ${imports.join("\n")}
4426
+
4427
+ const { default: executorFactory } = await import(${JSON.stringify(executorImportPath)});
4428
+ if (typeof executorFactory !== "function") {
4429
+ throw new Error(
4430
+ "Plugin executor module must export a default function created by withPluginContext().",
4431
+ );
4432
+ }
4433
+ export default executorFactory(${contextCode});
4434
+ `;
4435
+ }
4436
+ /**
4437
+ * Collect type imports needed for context.
4438
+ * @param context - Executor context values from plugin
4439
+ * @param outputDir - Base output directory for generated files
4440
+ * @param pluginId - Plugin identifier used for output paths
4441
+ * @param typeGenerationResult - Result from plugin type generation
4442
+ * @param sourceTypeInfoMap - Map of source type names to their source info
4443
+ * @returns Map of context keys to their import information
4444
+ */
4445
+ function collectTypeImports(context, outputDir, pluginId, typeGenerationResult, sourceTypeInfoMap) {
4446
+ const typeImports = /* @__PURE__ */ new Map();
4447
+ const pluginDir = sanitizePluginId$1(pluginId);
4448
+ const executorDir = path.join(outputDir, pluginDir, "executors");
4449
+ for (const [key, value] of Object.entries(context)) if (isTypeObject(value)) {
4450
+ const typeName = value.name;
4451
+ const sourceInfo = sourceTypeInfoMap?.get(typeName);
4452
+ const variableName = sourceInfo?.exportName ?? toCamelCase$1(typeName);
4453
+ let importPath;
4454
+ let isGeneratedType = false;
4455
+ if (typeGenerationResult?.typeFilePaths.has(typeName)) {
4456
+ const typeFilePath = typeGenerationResult.typeFilePaths.get(typeName);
4457
+ const absoluteTypePath = path.join(outputDir, typeFilePath);
4458
+ importPath = path.relative(executorDir, absoluteTypePath).replace(/\.ts$/, "");
4459
+ if (!importPath.startsWith(".")) importPath = `./${importPath}`;
4460
+ isGeneratedType = true;
4461
+ } else if (sourceInfo) {
4462
+ const sourceFilePath = sourceInfo.filePath;
4463
+ importPath = path.relative(executorDir, sourceFilePath).replace(/\.ts$/, "");
4464
+ if (!importPath.startsWith(".")) importPath = `./${importPath}`;
4465
+ } else importPath = `../../../../tailordb/${toKebabCase$1(typeName)}`;
4466
+ typeImports.set(key, {
4467
+ variableName,
4468
+ importPath,
4469
+ isGeneratedType
4470
+ });
4471
+ }
4472
+ return typeImports;
4473
+ }
4474
+ /**
4475
+ * Generate TypeScript code for context object.
4476
+ * @param context - Executor context values from plugin
4477
+ * @param typeImports - Resolved type import information for context keys
4478
+ * @returns TypeScript object literal code
4479
+ */
4480
+ function generateContextCode(context, typeImports) {
4481
+ const entries = [];
4482
+ for (const [key, value] of Object.entries(context)) if (isTypeObject(value)) {
4483
+ const importInfo = typeImports.get(key);
4484
+ if (importInfo) entries.push(` ${key}: ${importInfo.variableName}`);
4485
+ } else if (value !== void 0) entries.push(` ${key}: ${JSON.stringify(value)}`);
4486
+ return `{\n${entries.join(",\n")},\n}`;
4487
+ }
4488
+ /**
4489
+ * Check if a value is a TailorDB type object.
4490
+ * @param value - Value to inspect
4491
+ * @returns True if value is a type object with name and fields
4492
+ */
4493
+ function isTypeObject(value) {
4494
+ return typeof value === "object" && value !== null && "name" in value && "fields" in value && typeof value.name === "string";
4495
+ }
4496
+ /**
4497
+ * Generate TypeScript file content for legacy format executor (trigger/operation).
4498
+ * @param executor - Legacy executor definition
4499
+ * @returns TypeScript source code for executor file
4500
+ */
4501
+ function generateExecutorFileContentLegacy(executor) {
4502
+ const triggerCode = generateTriggerCode(executor.trigger);
4503
+ const operationCode = generateOperationCode(executor.operation);
4504
+ const injectDeclarations = generateInjectDeclarations(executor.operation.kind === "function" ? executor.operation.inject : void 0);
4505
+ const descriptionLine = executor.description ? `\n description: ${JSON.stringify(executor.description)},` : "";
4506
+ return ml`
4507
+ /**
4508
+ * Auto-generated executor by plugin
4509
+ * DO NOT EDIT - This file is generated by @tailor-platform/sdk
4510
+ */
4511
+ import { createExecutor } from "@tailor-platform/sdk";
4512
+ ${injectDeclarations}
4513
+ export default createExecutor({
4514
+ name: ${JSON.stringify(executor.name)},${descriptionLine}
4515
+ trigger: ${triggerCode},
4516
+ operation: ${operationCode},
4517
+ });
4518
+ `;
4519
+ }
4520
+ /**
4521
+ * Generate const declarations for injected variables.
4522
+ * @param inject - Map of injected values keyed by variable name
4523
+ * @returns TypeScript const declarations or empty string
4524
+ */
4525
+ function generateInjectDeclarations(inject) {
4526
+ if (!inject || Object.keys(inject).length === 0) return "";
4527
+ return `\n// Injected variables from plugin\n${Object.entries(inject).map(([name, value]) => `const ${name} = ${JSON.stringify(value)};`).join("\n")}\n`;
4528
+ }
4529
+ /**
4530
+ * Generate TypeScript code for trigger configuration.
4531
+ * @param trigger - Trigger configuration for executor
4532
+ * @returns TypeScript code for trigger object
4533
+ */
4534
+ function generateTriggerCode(trigger) {
4535
+ switch (trigger.kind) {
4536
+ case "recordCreated":
4537
+ case "recordUpdated":
4538
+ case "recordDeleted": return `{
4539
+ kind: ${JSON.stringify(trigger.kind)},
4540
+ typeName: ${JSON.stringify(trigger.typeName)},
4541
+ }`;
4542
+ case "schedule": return `{
4543
+ kind: "schedule",
4544
+ cron: ${JSON.stringify(trigger.cron)},
4545
+ timezone: ${JSON.stringify(trigger.timezone ?? "UTC")},
4546
+ }`;
4547
+ case "incomingWebhook": return `{
4548
+ kind: "incomingWebhook",
4549
+ }`;
4550
+ default: throw new Error(`Unknown trigger kind: ${trigger.kind}`);
4551
+ }
4552
+ }
4553
+ /**
4554
+ * Generate TypeScript code for operation configuration.
4555
+ * @param operation - Operation configuration for executor
4556
+ * @returns TypeScript code for operation object
4557
+ */
4558
+ function generateOperationCode(operation) {
4559
+ switch (operation.kind) {
4560
+ case "graphql": {
4561
+ const appNameLine = operation.appName ? `\n appName: ${JSON.stringify(operation.appName)},` : "";
4562
+ const variablesLine = operation.variables ? `\n variables: ${operation.variables},` : "";
4563
+ return `{
4564
+ kind: "graphql",
4565
+ query: \`${escapeTemplateLiteral(operation.query)}\`,${appNameLine}${variablesLine}
4566
+ }`;
4567
+ }
4568
+ case "function": return `{
4569
+ kind: "function",
4570
+ body: ${operation.body},
4571
+ }`;
4572
+ case "webhook": return `{
4573
+ kind: "webhook",
4574
+ url: () => ${JSON.stringify(operation.url)},
4575
+ }`;
4576
+ case "workflow": return `{
4577
+ kind: "workflow",
4578
+ workflowName: ${JSON.stringify(operation.workflowName)},
4579
+ }`;
4580
+ default: throw new Error(`Unknown operation kind: ${operation.kind}`);
4581
+ }
4582
+ }
4583
+ /**
4584
+ * Escape special characters in template literal content.
4585
+ * @param str - Raw template literal content
4586
+ * @returns Escaped string safe for template literals
4587
+ */
4588
+ function escapeTemplateLiteral(str) {
4589
+ return str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
4590
+ }
4591
+ const require$1 = createRequire(import.meta.url);
4592
+ /**
4593
+ * Resolve the import path for a plugin executor module.
4594
+ * @param resolve - Executor resolve function
4595
+ * @param pluginImportPath - Plugin's import path
4596
+ * @param executorOutputDir - Directory where the generated executor will be written
4597
+ * @param baseDirs - Base directories for resolving plugin import paths
4598
+ * @returns Import path string for the executor module
4599
+ */
4600
+ function resolveExecutorImportPath(resolve, pluginImportPath, executorOutputDir, baseDirs) {
4601
+ const specifier = extractDynamicImportSpecifier(resolve);
4602
+ if (!specifier.startsWith(".")) return specifier;
4603
+ const pluginBaseDir = resolvePluginBaseDir(pluginImportPath, baseDirs);
4604
+ if (!pluginBaseDir) throw new Error(`Unable to resolve plugin import base for "${pluginImportPath}". Tried base dirs: ${baseDirs.join(", ") || "(none)"}. Use an absolute import specifier in resolve(), or ensure the plugin path is resolvable.`);
4605
+ const absolutePath = path.resolve(pluginBaseDir, specifier);
4606
+ let relativePath = path.relative(executorOutputDir, absolutePath).replace(/\\/g, "/");
4607
+ relativePath = stripSourceExtension(relativePath);
4608
+ if (!relativePath.startsWith(".")) relativePath = `./${relativePath}`;
4609
+ return relativePath;
4610
+ }
4611
+ /**
4612
+ * Extract the dynamic import specifier from a resolve function.
4613
+ * @param resolve - Executor resolve function
4614
+ * @returns The module specifier string
4615
+ */
4616
+ function extractDynamicImportSpecifier(resolve) {
4617
+ const match = resolve.toString().match(/import\s*\(\s*["']([^"']+)["']\s*\)/);
4618
+ if (!match) throw new Error(`resolve() must return a dynamic import, e.g. \`async () => await import("./executors/on-create")\`.`);
4619
+ return match[1];
4620
+ }
4621
+ /**
4622
+ * Resolve plugin base directory for relative imports.
4623
+ * @param pluginImportPath - Plugin import path
4624
+ * @param baseDirs - Base directories for resolving plugin import paths
4625
+ * @returns Directory path or null if not resolvable
4626
+ */
4627
+ function resolvePluginBaseDir(pluginImportPath, baseDirs) {
4628
+ if (pluginImportPath.startsWith(".")) {
4629
+ const resolvedPath = resolveRelativePluginImportPath(pluginImportPath, baseDirs) ?? path.resolve(baseDirs[0] ?? process.cwd(), pluginImportPath);
4630
+ if (fs$2.existsSync(resolvedPath)) return fs$2.statSync(resolvedPath).isDirectory() ? resolvedPath : path.dirname(resolvedPath);
4631
+ return path.extname(resolvedPath) ? path.dirname(resolvedPath) : resolvedPath;
4632
+ }
4633
+ for (const baseDir of baseDirs) try {
4634
+ const resolved = require$1.resolve(pluginImportPath, { paths: [baseDir] });
4635
+ return path.dirname(resolved);
4636
+ } catch {
4637
+ continue;
4638
+ }
4639
+ return null;
4640
+ }
4641
+ /**
4642
+ * Strip TypeScript source extensions from import paths.
4643
+ * @param importPath - Path to normalize
4644
+ * @returns Path without .ts/.tsx extension
4645
+ */
4646
+ function stripSourceExtension(importPath) {
4647
+ return importPath.replace(/\.(ts|tsx)$/, "");
4648
+ }
4649
+ /**
4650
+ * Convert plugin ID to safe directory name.
4651
+ * @param pluginId - Plugin identifier (e.g., "@scope/name")
4652
+ * @returns Safe directory name
4653
+ */
4654
+ function sanitizePluginId$1(pluginId) {
4655
+ return pluginId.replace(/^@/, "").replace(/\//g, "-");
4656
+ }
4657
+ /**
4658
+ * Convert executor name to safe filename.
4659
+ * @param executorName - Executor name
4660
+ * @returns Safe filename without extension
4661
+ */
4662
+ function sanitizeExecutorFileName(executorName) {
4663
+ const sanitized = path.basename(executorName).replace(/\.[^/.]+$/, "").replace(/[^a-zA-Z0-9_-]/g, "-");
4664
+ if (!sanitized) throw new Error(`Invalid executor name: "${executorName}"`);
4665
+ return sanitized;
4666
+ }
4667
+ /**
4668
+ * Convert string to camelCase.
4669
+ * @param str - Input string to convert
4670
+ * @returns camelCase string
4671
+ */
4672
+ function toCamelCase$1(str) {
4673
+ const result = str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "");
4674
+ return result.charAt(0).toLowerCase() + result.slice(1);
4675
+ }
4676
+ /**
4677
+ * Convert string to kebab-case.
4678
+ * @param str - Input string to convert
4679
+ * @returns kebab-case string
4680
+ */
4681
+ function toKebabCase$1(str) {
4682
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
4683
+ }
4684
+
4685
+ //#endregion
4686
+ //#region src/cli/generator/plugin-type-generator.ts
4687
+ /**
4688
+ * Plugin Type Generator
4689
+ *
4690
+ * Generates TypeScript files for plugin-generated types (TailorDB types).
4691
+ * These files can be imported by plugin executors to reference generated types.
4692
+ */
4693
+ /**
4694
+ * Generate TypeScript files for plugin-generated types.
4695
+ * These files export the type definition and can be imported by executor files.
4696
+ * @param types - Array of plugin type information
4697
+ * @param outputDir - Base output directory (e.g., .tailor-sdk)
4698
+ * @returns Generation result with file paths
4699
+ */
4700
+ function generatePluginTypeFiles(types, outputDir) {
4701
+ const typeFilePaths = /* @__PURE__ */ new Map();
4702
+ const generatedFiles = [];
4703
+ if (types.length === 0) return {
4704
+ typeFilePaths,
4705
+ generatedFiles
4706
+ };
4707
+ const seenTypeNames = /* @__PURE__ */ new Map();
4708
+ for (const info of types) {
4709
+ const existing = seenTypeNames.get(info.type.name);
4710
+ if (existing) throw new Error(`Duplicate plugin-generated type name "${info.type.name}" detected. First: plugin "${existing.pluginId}" (kind: "${existing.kind}", source type: "${existing.sourceTypeName}"), Second: plugin "${info.pluginId}" (kind: "${info.kind}", source type: "${info.sourceTypeName}"). Plugin-generated type names must be unique.`);
4711
+ seenTypeNames.set(info.type.name, info);
4712
+ const pluginDir = sanitizePluginId(info.pluginId);
4713
+ const typeOutputDir = path.join(outputDir, pluginDir, "types");
4714
+ fs$2.mkdirSync(typeOutputDir, { recursive: true });
4715
+ const fileName = `${toKebabCase(info.type.name)}.ts`;
4716
+ const filePath = path.join(typeOutputDir, fileName);
4717
+ const content = generateTypeFileContent(info);
4718
+ fs$2.writeFileSync(filePath, content);
4719
+ generatedFiles.push(filePath);
4720
+ const relativePath = path.relative(outputDir, filePath);
4721
+ typeFilePaths.set(info.type.name, relativePath);
4722
+ const displayPath = path.relative(process.cwd(), filePath);
4723
+ logger.log(` Plugin Type File: ${styles.success(displayPath)} (${styles.dim(info.kind)}) from plugin ${styles.info(info.pluginId)}`);
4724
+ }
4725
+ return {
4726
+ typeFilePaths,
4727
+ generatedFiles
4006
4728
  };
4007
4729
  }
4730
+ /**
4731
+ * Generate TypeScript file content for a single type.
4732
+ * @param info - Plugin type information
4733
+ * @returns TypeScript source code
4734
+ */
4735
+ function generateTypeFileContent(info) {
4736
+ const { type, pluginId, sourceTypeName, kind } = info;
4737
+ const variableName = toCamelCase(type.name);
4738
+ const fieldsCode = generateFieldsCode(type);
4739
+ return ml`
4740
+ /**
4741
+ * Auto-generated type by plugin: ${pluginId}
4742
+ * Source type: ${sourceTypeName}
4743
+ * Kind: ${kind}
4744
+ *
4745
+ * DO NOT EDIT - This file is generated by @tailor-platform/sdk
4746
+ */
4747
+ import { db } from "@tailor-platform/sdk";
4748
+
4749
+ export const ${variableName} = db.type(${JSON.stringify(type.name)}, ${fieldsCode});
4750
+
4751
+ export type ${type.name} = typeof ${variableName};
4752
+ `;
4753
+ }
4754
+ /**
4755
+ * Generate TypeScript code for field definitions.
4756
+ * This creates a simplified version of the type's fields.
4757
+ * @param type - TailorDB type
4758
+ * @returns TypeScript code for fields object
4759
+ */
4760
+ function generateFieldsCode(type) {
4761
+ const fieldEntries = [];
4762
+ for (const [fieldName, field] of Object.entries(type.fields)) {
4763
+ const fieldCode = generateSingleFieldCode(field);
4764
+ if (fieldCode) fieldEntries.push(` ${fieldName}: ${fieldCode}`);
4765
+ }
4766
+ return `{\n${fieldEntries.join(",\n")},\n}`;
4767
+ }
4768
+ /**
4769
+ * Map from TailorDB type to SDK method name.
4770
+ */
4771
+ const typeToMethodMap = {
4772
+ string: "string",
4773
+ integer: "int",
4774
+ float: "float",
4775
+ boolean: "bool",
4776
+ uuid: "uuid",
4777
+ datetime: "datetime",
4778
+ date: "date",
4779
+ time: "time",
4780
+ enum: "enum",
4781
+ nested: "nested"
4782
+ };
4783
+ /**
4784
+ * Generate TypeScript code for a single field definition.
4785
+ * @param field - Field definition object
4786
+ * @returns TypeScript code for the field
4787
+ */
4788
+ function generateSingleFieldCode(field) {
4789
+ if (!field || typeof field !== "object") return null;
4790
+ const fieldType = field.type;
4791
+ if (!fieldType) return null;
4792
+ const method = typeToMethodMap[fieldType] || fieldType;
4793
+ const metadata = field._metadata || field.metadata || {};
4794
+ const optionParts = [];
4795
+ if (metadata.required === false) optionParts.push("optional: true");
4796
+ const optionsArg = optionParts.length > 0 ? `{ ${optionParts.join(", ")} }` : "";
4797
+ if (fieldType === "enum") {
4798
+ const allowedValues = metadata.allowedValues;
4799
+ let enumValues = [];
4800
+ if (Array.isArray(allowedValues)) enumValues = allowedValues.map((v) => v.value);
4801
+ let code$1 = `db.enum(${JSON.stringify(enumValues)}${optionsArg ? `, ${optionsArg}` : ""})`;
4802
+ if (metadata.index) code$1 += ".index()";
4803
+ if (metadata.unique) code$1 += ".unique()";
4804
+ if (metadata.description) code$1 += `.description(${JSON.stringify(metadata.description)})`;
4805
+ return code$1;
4806
+ }
4807
+ let code = `db.${method}(${optionsArg})`;
4808
+ if (metadata.index) code += ".index()";
4809
+ if (metadata.unique) code += ".unique()";
4810
+ if (metadata.description) code += `.description(${JSON.stringify(metadata.description)})`;
4811
+ return code;
4812
+ }
4813
+ /**
4814
+ * Convert plugin ID to safe directory name.
4815
+ * @param pluginId - Plugin identifier (e.g., "@tailor-platform/change-history")
4816
+ * @returns Safe directory name (e.g., "tailor-platform-change-history")
4817
+ */
4818
+ function sanitizePluginId(pluginId) {
4819
+ return pluginId.replace(/^@/, "").replace(/\//g, "-");
4820
+ }
4821
+ /**
4822
+ * Convert string to kebab-case.
4823
+ * @param str - Input string
4824
+ * @returns kebab-case string
4825
+ */
4826
+ function toKebabCase(str) {
4827
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
4828
+ }
4829
+ /**
4830
+ * Convert string to camelCase.
4831
+ * @param str - Input string
4832
+ * @returns camelCase string
4833
+ */
4834
+ function toCamelCase(str) {
4835
+ const result = str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "");
4836
+ return result.charAt(0).toLowerCase() + result.slice(1);
4837
+ }
4008
4838
 
4009
4839
  //#endregion
4010
4840
  //#region src/cli/type-generator.ts
@@ -4022,9 +4852,10 @@ function extractAttributesFromConfig(config) {
4022
4852
  * @param attributeMap - Attribute map configuration
4023
4853
  * @param attributeList - Attribute list configuration
4024
4854
  * @param env - Environment configuration
4855
+ * @param pluginConfigs - Plugin configuration entries for PluginConfigs interface
4025
4856
  * @returns Generated type definition source
4026
4857
  */
4027
- function generateTypeDefinition(attributeMap, attributeList, env) {
4858
+ function generateTypeDefinition(attributeMap, attributeList, env, pluginConfigs) {
4028
4859
  const mapFields = attributeMap ? Object.entries(attributeMap).map(([key, value]) => ` ${key}: ${value};`).join("\n") : "";
4029
4860
  const mapBody = !attributeMap || Object.keys(attributeMap).length === 0 ? "{}" : `{
4030
4861
  ${mapFields}
@@ -4045,7 +4876,7 @@ declare module "@tailor-platform/sdk" {
4045
4876
  interface AttributeList ${listBody}
4046
4877
  interface Env ${!env || Object.keys(env).length === 0 ? "{}" : `{
4047
4878
  ${envFields}
4048
- }`}
4879
+ }`}${pluginConfigs && pluginConfigs.length > 0 ? `\n${generatePluginConfigsDefinition(pluginConfigs)}` : ""}${pluginConfigs && pluginConfigs.length > 0 ? `\n${generatePluginMethodOverload(pluginConfigs)}` : ""}
4049
4880
  }
4050
4881
 
4051
4882
  export {};
@@ -4104,11 +4935,11 @@ function resolveTypeDefinitionPath(configPath) {
4104
4935
  }
4105
4936
  /**
4106
4937
  * Generate user type definitions from the app config and write them to disk.
4107
- * @param config - Application config
4108
- * @param configPath - Path to Tailor config file
4938
+ * @param options - Generation options
4109
4939
  * @returns Promise that resolves when types are generated
4110
4940
  */
4111
- async function generateUserTypes(config, configPath) {
4941
+ async function generateUserTypes(options) {
4942
+ const { config, configPath, plugins } = options;
4112
4943
  try {
4113
4944
  const { attributeMap, attributeList } = extractAttributesFromConfig(config);
4114
4945
  if (!attributeMap && !attributeList) logger.info("No attributes found in configuration", { mode: "plain" });
@@ -4116,7 +4947,14 @@ async function generateUserTypes(config, configPath) {
4116
4947
  if (attributeList) logger.debug(`Extracted AttributeList: ${JSON.stringify(attributeList)}`);
4117
4948
  const env = config.env;
4118
4949
  if (env) logger.debug(`Extracted Env: ${JSON.stringify(env)}`);
4119
- const typeDefContent = generateTypeDefinition(attributeMap, attributeList, env);
4950
+ const pluginConfigs = plugins?.filter((p) => p.configSchema !== void 0).map((p) => ({
4951
+ id: p.id,
4952
+ configSchema: p.configSchema,
4953
+ configTypeTemplate: p.configTypeTemplate,
4954
+ typeConfigRequired: isTypeConfigRequired(p)
4955
+ }));
4956
+ if (pluginConfigs && pluginConfigs.length > 0) logger.debug(`Extracted PluginConfigs: ${pluginConfigs.map((p) => p.id).join(", ")}`);
4957
+ const typeDefContent = generateTypeDefinition(attributeMap, attributeList, env, pluginConfigs);
4120
4958
  const outputPath = resolveTypeDefinitionPath(configPath);
4121
4959
  fs$2.mkdirSync(path.dirname(outputPath), { recursive: true });
4122
4960
  fs$2.writeFileSync(outputPath, typeDefContent);
@@ -4145,6 +4983,386 @@ function resolvePackageDirectory(startDir) {
4145
4983
  return null;
4146
4984
  }
4147
4985
  }
4986
+ /**
4987
+ * Convert a ConfigSchemaField to its TypeScript type string representation.
4988
+ * @param field - The field to convert
4989
+ * @param indent - Current indentation level
4990
+ * @returns TypeScript type string
4991
+ */
4992
+ function fieldToTypeString(field, indent = 0) {
4993
+ const indentStr = " ".repeat(indent);
4994
+ const metadata = field.metadata;
4995
+ let baseType;
4996
+ switch (field.type) {
4997
+ case "string":
4998
+ case "uuid":
4999
+ case "date":
5000
+ case "datetime":
5001
+ case "time":
5002
+ baseType = "string";
5003
+ break;
5004
+ case "integer":
5005
+ case "float":
5006
+ baseType = "number";
5007
+ break;
5008
+ case "boolean":
5009
+ baseType = "boolean";
5010
+ break;
5011
+ case "enum":
5012
+ if (metadata.allowedValues && metadata.allowedValues.length > 0) baseType = metadata.allowedValues.map((v) => `"${v.value}"`).join(" | ");
5013
+ else baseType = "string";
5014
+ break;
5015
+ case "nested": {
5016
+ const fieldEntries = Object.entries(field.fields);
5017
+ if (fieldEntries.length === 0) baseType = "Record<string, unknown>";
5018
+ else baseType = `{\n${fieldEntries.map(([name, nestedField]) => {
5019
+ const nestedType = fieldToTypeString(nestedField, indent + 1);
5020
+ return `${indentStr} ${name}${!nestedField.metadata.required ? "?" : ""}: ${nestedType};`;
5021
+ }).join("\n")}\n${indentStr}}`;
5022
+ break;
5023
+ }
5024
+ default: baseType = "unknown";
5025
+ }
5026
+ if (metadata.array) baseType = `(${baseType})[]`;
5027
+ return baseType;
5028
+ }
5029
+ function isTypeConfigRequired(plugin) {
5030
+ const required = plugin.typeConfigRequired;
5031
+ if (typeof required === "function") return required(plugin.pluginConfig);
5032
+ return required === true;
5033
+ }
5034
+ /**
5035
+ * Generate PluginConfigs interface extension for user-defined.d.ts
5036
+ * @param plugins - Array of plugin configurations
5037
+ * @returns TypeScript interface extension string, or empty string if no plugins
5038
+ */
5039
+ function generatePluginConfigsDefinition(plugins) {
5040
+ if (plugins.length === 0) return "";
5041
+ return ` interface PluginConfigs<Fields extends string> {
5042
+ ${plugins.map((plugin) => {
5043
+ const typeString = plugin.configTypeTemplate ?? fieldToTypeString(plugin.configSchema, 2);
5044
+ const optionalMarker = plugin.typeConfigRequired ? "" : "?";
5045
+ return ` "${plugin.id}"${optionalMarker}: ${typeString};`;
5046
+ }).join("\n")}
5047
+ }`;
5048
+ }
5049
+ /**
5050
+ * Generate TailorDBType.plugin() method overload for IDE completion.
5051
+ * This adds an overload that uses `keyof Fields` directly for field-aware types,
5052
+ * enabling IDE autocompletion for field names in plugin configs.
5053
+ * @param plugins - Array of plugin configurations
5054
+ * @returns TypeScript interface extension string
5055
+ */
5056
+ function generatePluginMethodOverload(plugins) {
5057
+ return ` // Overload for .plugin() method to enable IDE completion for field names
5058
+ interface TailorDBType<Fields, User> {
5059
+ plugin(config: {
5060
+ ${plugins.map((plugin) => {
5061
+ const typeString = (plugin.configTypeTemplate ?? fieldToTypeString(plugin.configSchema, 3)).replace(/\bFields\b/g, "keyof Fields & string");
5062
+ const optionalMarker = plugin.typeConfigRequired ? "" : "?";
5063
+ return ` "${plugin.id}"${optionalMarker}: ${typeString};`;
5064
+ }).join("\n")}
5065
+ }): this;
5066
+ }`;
5067
+ }
5068
+
5069
+ //#endregion
5070
+ //#region src/plugin/manager.ts
5071
+ const unauthenticatedTailorUser = {
5072
+ id: "00000000-0000-0000-0000-000000000000",
5073
+ type: "",
5074
+ workspaceId: "00000000-0000-0000-0000-000000000000",
5075
+ attributes: null,
5076
+ attributeList: []
5077
+ };
5078
+ /**
5079
+ * Validate plugin config against its schema
5080
+ * @param config - The config object to validate
5081
+ * @param schema - The schema defining expected fields
5082
+ * @returns Array of validation errors (empty if valid)
5083
+ */
5084
+ function validatePluginConfig(config, schema) {
5085
+ const result = schema.parse({
5086
+ value: config,
5087
+ data: config,
5088
+ user: unauthenticatedTailorUser
5089
+ });
5090
+ if ("issues" in result && result.issues) return result.issues.map((issue) => ({
5091
+ field: Array.isArray(issue.path) ? issue.path.join(".") : "",
5092
+ message: issue.message
5093
+ }));
5094
+ return [];
5095
+ }
5096
+ /**
5097
+ * Manages plugin registration and processing
5098
+ */
5099
+ var PluginManager = class {
5100
+ plugins = /* @__PURE__ */ new Map();
5101
+ generatedExecutors = [];
5102
+ generatedTypes = [];
5103
+ namespaceGeneratedTypeKeys = /* @__PURE__ */ new Set();
5104
+ namespaceGeneratedExecutorKeys = /* @__PURE__ */ new Set();
5105
+ constructor(plugins = []) {
5106
+ for (const plugin of plugins) {
5107
+ if (this.plugins.has(plugin.id)) throw new Error(`Duplicate plugin ID "${plugin.id}" detected. Each plugin must have a unique ID.`);
5108
+ this.plugins.set(plugin.id, plugin);
5109
+ }
5110
+ }
5111
+ /**
5112
+ * Process a single plugin attachment on a raw TailorDBType.
5113
+ * This method is called during type loading before parsing.
5114
+ * @param context - Context containing the raw type, config, namespace, and plugin ID
5115
+ * @returns Result with plugin output on success, or error message on failure
5116
+ */
5117
+ async processAttachment(context) {
5118
+ const plugin = this.plugins.get(context.pluginId);
5119
+ if (!plugin) return {
5120
+ success: false,
5121
+ error: `Plugin "${context.pluginId}" not found`
5122
+ };
5123
+ const typeConfigRequired = plugin.typeConfigRequired;
5124
+ if ((typeof typeConfigRequired === "function" ? typeConfigRequired(plugin.pluginConfig) : typeConfigRequired === true) && (context.config === void 0 || context.config === null)) return {
5125
+ success: false,
5126
+ error: `Plugin "${plugin.id}" requires config, but none was provided for type "${context.type.name}".`
5127
+ };
5128
+ if (plugin.configSchema) {
5129
+ const validationErrors = validatePluginConfig(context.config, plugin.configSchema);
5130
+ if (validationErrors.length > 0) {
5131
+ const errorDetails = validationErrors.map((e) => e.field ? `${e.field}: ${e.message}` : e.message).join("; ");
5132
+ return {
5133
+ success: false,
5134
+ error: `Invalid config for plugin "${plugin.id}" on type "${context.type.name}": ${errorDetails}`
5135
+ };
5136
+ }
5137
+ }
5138
+ if (!plugin.process) return {
5139
+ success: false,
5140
+ error: `Plugin "${plugin.id}" does not support type-attached processing (missing process method). Use processNamespace via definePlugins() instead.`
5141
+ };
5142
+ let output;
5143
+ try {
5144
+ output = await plugin.process({
5145
+ type: context.type,
5146
+ config: context.config,
5147
+ pluginConfig: plugin.pluginConfig,
5148
+ namespace: context.namespace
5149
+ });
5150
+ } catch (error) {
5151
+ const message = error instanceof Error ? error.message : String(error);
5152
+ return {
5153
+ success: false,
5154
+ error: `Plugin "${plugin.id}" threw an error while processing type "${context.type.name}": ${message}`
5155
+ };
5156
+ }
5157
+ if (output.types && Object.keys(output.types).length > 0) {
5158
+ const importPath = plugin.importPath;
5159
+ for (const [kind, type] of Object.entries(output.types)) this.generatedTypes.push({
5160
+ pluginId: context.pluginId,
5161
+ pluginImportPath: importPath,
5162
+ sourceTypeName: context.type.name,
5163
+ kind,
5164
+ type
5165
+ });
5166
+ }
5167
+ if (output.executors && output.executors.length > 0) for (const executor of output.executors) this.generatedExecutors.push({
5168
+ executor,
5169
+ pluginId: context.pluginId,
5170
+ namespace: context.namespace,
5171
+ sourceTypeName: context.type.name
5172
+ });
5173
+ return {
5174
+ success: true,
5175
+ output
5176
+ };
5177
+ }
5178
+ /**
5179
+ * Process namespace plugins that don't require a source type.
5180
+ * This method is called once per namespace for plugins with processNamespace method.
5181
+ * @param namespace - The target namespace for generated types
5182
+ * @param types - TailorDB types in the namespace (after type-attached processing)
5183
+ * @param generatedTypes - Plugin-generated types in the namespace
5184
+ * @returns Array of results with plugin outputs and configs
5185
+ */
5186
+ async processNamespacePlugins(namespace, types, generatedTypes) {
5187
+ const results = [];
5188
+ for (const [pluginId, plugin] of this.plugins) {
5189
+ if (!plugin.processNamespace) continue;
5190
+ const config = plugin.pluginConfig;
5191
+ if (plugin.pluginConfigSchema && config !== void 0) {
5192
+ const validationErrors = validatePluginConfig(config, plugin.pluginConfigSchema);
5193
+ if (validationErrors.length > 0) {
5194
+ const errorDetails = validationErrors.map((e) => e.field ? `${e.field}: ${e.message}` : e.message).join("; ");
5195
+ results.push({
5196
+ pluginId,
5197
+ config,
5198
+ result: {
5199
+ success: false,
5200
+ error: `Invalid pluginConfig for plugin "${plugin.id}": ${errorDetails}`
5201
+ }
5202
+ });
5203
+ continue;
5204
+ }
5205
+ }
5206
+ const context = {
5207
+ pluginConfig: config,
5208
+ namespace,
5209
+ types,
5210
+ generatedTypes
5211
+ };
5212
+ let output;
5213
+ try {
5214
+ output = await plugin.processNamespace(context);
5215
+ } catch (error) {
5216
+ const message = error instanceof Error ? error.message : String(error);
5217
+ results.push({
5218
+ pluginId,
5219
+ config,
5220
+ result: {
5221
+ success: false,
5222
+ error: `Plugin "${plugin.id}" threw an error during namespace processing for "${namespace}": ${message}`
5223
+ }
5224
+ });
5225
+ continue;
5226
+ }
5227
+ if (output.executors && output.executors.length > 0) for (const executor of output.executors) {
5228
+ const executorKey = `${pluginId}:${executor.name}`;
5229
+ if (this.namespaceGeneratedExecutorKeys.has(executorKey)) continue;
5230
+ this.namespaceGeneratedExecutorKeys.add(executorKey);
5231
+ this.generatedExecutors.push({
5232
+ executor,
5233
+ pluginId,
5234
+ namespace
5235
+ });
5236
+ }
5237
+ if (output.types && Object.keys(output.types).length > 0) {
5238
+ const importPath = plugin.importPath;
5239
+ for (const [kind, type] of Object.entries(output.types)) {
5240
+ const typeKey = `${pluginId}:${kind}:${type.name}`;
5241
+ if (this.namespaceGeneratedTypeKeys.has(typeKey)) continue;
5242
+ this.namespaceGeneratedTypeKeys.add(typeKey);
5243
+ this.generatedTypes.push({
5244
+ pluginId,
5245
+ pluginImportPath: importPath,
5246
+ sourceTypeName: "(namespace)",
5247
+ kind,
5248
+ type
5249
+ });
5250
+ }
5251
+ }
5252
+ results.push({
5253
+ pluginId,
5254
+ config,
5255
+ result: {
5256
+ success: true,
5257
+ output
5258
+ }
5259
+ });
5260
+ }
5261
+ return results;
5262
+ }
5263
+ /**
5264
+ * Get plugins that have processNamespace method
5265
+ * @returns Array of plugin IDs that support namespace processing
5266
+ */
5267
+ getNamespacePluginIds() {
5268
+ return Array.from(this.plugins.entries()).filter(([, plugin]) => plugin.processNamespace !== void 0).map(([id]) => id);
5269
+ }
5270
+ /**
5271
+ * Get the count of registered plugins
5272
+ * @returns Number of registered plugins
5273
+ */
5274
+ get pluginCount() {
5275
+ return this.plugins.size;
5276
+ }
5277
+ /**
5278
+ * Get the import path for a plugin
5279
+ * @param pluginId - The plugin ID to look up
5280
+ * @returns The plugin's import path, or undefined if not found
5281
+ */
5282
+ getPluginImportPath(pluginId) {
5283
+ return this.plugins.get(pluginId)?.importPath;
5284
+ }
5285
+ /**
5286
+ * Get all plugin-generated executors
5287
+ * @returns Array of plugin-generated executor info
5288
+ */
5289
+ getPluginGeneratedExecutors() {
5290
+ return this.generatedExecutors;
5291
+ }
5292
+ /**
5293
+ * Get all plugin-generated executors with import paths
5294
+ * @returns Array of plugin-generated executor info with import paths
5295
+ */
5296
+ getPluginGeneratedExecutorsWithImportPath() {
5297
+ return this.generatedExecutors.map((info) => ({
5298
+ ...info,
5299
+ pluginImportPath: this.getPluginImportPath(info.pluginId) ?? ""
5300
+ }));
5301
+ }
5302
+ /**
5303
+ * Get all plugin-generated types
5304
+ * @returns Array of plugin-generated type info
5305
+ */
5306
+ getPluginGeneratedTypes() {
5307
+ return this.generatedTypes;
5308
+ }
5309
+ /**
5310
+ * Get plugin-generated executors for a specific namespace
5311
+ * @param namespace - The namespace to filter by
5312
+ * @returns Array of plugin-generated executor info for the namespace
5313
+ */
5314
+ getPluginGeneratedExecutorsForNamespace(namespace) {
5315
+ return this.generatedExecutors.filter((info) => info.namespace === namespace);
5316
+ }
5317
+ /**
5318
+ * Extend a TailorDB type with new fields.
5319
+ * This method handles the `db.type()` call and metadata copying internally.
5320
+ * @param params - Parameters for type extension
5321
+ * @returns The extended TailorDB type
5322
+ */
5323
+ extendType(params) {
5324
+ const { originalType, extendFields, pluginId } = params;
5325
+ const existingFieldNames = Object.keys(originalType.fields);
5326
+ const duplicateFields = Object.keys(extendFields).filter((name) => existingFieldNames.includes(name));
5327
+ if (duplicateFields.length > 0) throw new Error(`Plugin "${pluginId}" attempted to add fields that already exist in type "${originalType.name}": ${duplicateFields.join(", ")}. extendFields cannot overwrite existing fields.`);
5328
+ const { id: _id, ...fieldsWithoutId } = {
5329
+ ...originalType.fields,
5330
+ ...extendFields
5331
+ };
5332
+ const pluralForm = originalType.metadata.settings?.pluralForm;
5333
+ const typeName = pluralForm ? [originalType.name, pluralForm] : originalType.name;
5334
+ return copyMetadataToExtendedType(originalType, db.type(typeName, fieldsWithoutId));
5335
+ }
5336
+ };
5337
+ /**
5338
+ * Copy metadata from original type to extended type.
5339
+ * Preserves files, settings, permissions, indexes, and plugins.
5340
+ * @param original - The original TailorDB type with metadata
5341
+ * @param extended - The newly created extended type
5342
+ * @returns The extended type with copied metadata
5343
+ */
5344
+ function copyMetadataToExtendedType(original, extended) {
5345
+ let result = extended;
5346
+ if (original._description) result = result.description(original._description);
5347
+ const metadata = original.metadata;
5348
+ if (metadata.files && Object.keys(metadata.files).length > 0) result = result.files(metadata.files);
5349
+ if (metadata.settings) {
5350
+ const { pluralForm: _pluralForm, ...features } = metadata.settings;
5351
+ if (Object.keys(features).length > 0) result = result.features(features);
5352
+ }
5353
+ if (metadata.permissions?.record) result = result.permission(metadata.permissions.record);
5354
+ if (metadata.permissions?.gql) result = result.gqlPermission(metadata.permissions.gql);
5355
+ if (metadata.indexes && Object.keys(metadata.indexes).length > 0) {
5356
+ const indexDefs = Object.entries(metadata.indexes).map(([name, def]) => ({
5357
+ name,
5358
+ fields: def.fields,
5359
+ unique: def.unique
5360
+ }));
5361
+ result = result.indexes(...indexDefs);
5362
+ }
5363
+ if (original.plugins && original.plugins.length > 0) for (const plugin of original.plugins) result = result.plugin({ [plugin.pluginId]: plugin.config });
5364
+ return result;
5365
+ }
4148
5366
 
4149
5367
  //#endregion
4150
5368
  //#region src/cli/apply/services/label.ts
@@ -4188,12 +5406,14 @@ function createChangeSet(title) {
4188
5406
  const creates = [];
4189
5407
  const updates = [];
4190
5408
  const deletes = [];
4191
- const isEmpty = () => creates.length === 0 && updates.length === 0 && deletes.length === 0;
5409
+ const replaces = [];
5410
+ const isEmpty = () => creates.length === 0 && updates.length === 0 && deletes.length === 0 && replaces.length === 0;
4192
5411
  return {
4193
5412
  title,
4194
5413
  creates,
4195
5414
  updates,
4196
5415
  deletes,
5416
+ replaces,
4197
5417
  isEmpty,
4198
5418
  print: () => {
4199
5419
  if (isEmpty()) return;
@@ -4201,6 +5421,7 @@ function createChangeSet(title) {
4201
5421
  creates.forEach((item) => logger.log(` ${symbols.create} ${item.name}`));
4202
5422
  deletes.forEach((item) => logger.log(` ${symbols.delete} ${item.name}`));
4203
5423
  updates.forEach((item) => logger.log(` ${symbols.update} ${item.name}`));
5424
+ replaces.forEach((item) => logger.log(` ${symbols.replace} ${item.name}`));
4204
5425
  }
4205
5426
  };
4206
5427
  }
@@ -4654,6 +5875,11 @@ async function applyAuth(client, result, phase = "create-update") {
4654
5875
  update.request.oauth2Client.redirectUris = await resolveStaticWebsiteUrls(client, update.request.workspaceId, update.request.oauth2Client.redirectUris, "OAuth2 redirect URIs");
4655
5876
  return client.updateAuthOAuth2Client(update.request);
4656
5877
  })]);
5878
+ for (const replace of changeSet.oauth2Client.replaces) {
5879
+ await client.deleteAuthOAuth2Client(replace.deleteRequest);
5880
+ replace.createRequest.oauth2Client.redirectUris = await resolveStaticWebsiteUrls(client, replace.createRequest.workspaceId, replace.createRequest.oauth2Client.redirectUris, "OAuth2 redirect URIs");
5881
+ await client.createAuthOAuth2Client(replace.createRequest);
5882
+ }
4657
5883
  await Promise.all([...changeSet.scim.creates.map((create$1) => client.createAuthSCIMConfig(create$1.request)), ...changeSet.scim.updates.map((update) => client.updateAuthSCIMConfig(update.request))]);
4658
5884
  await Promise.all([...changeSet.scimResource.creates.map((create$1) => client.createAuthSCIMResource(create$1.request)), ...changeSet.scimResource.updates.map((update) => client.updateAuthSCIMResource(update.request))]);
4659
5885
  } else if (phase === "delete-resources") {
@@ -5193,33 +6419,47 @@ async function planOAuth2Clients(client, workspaceId, auths, deletedServices) {
5193
6419
  for (const auth of auths) {
5194
6420
  const { parsedConfig: config } = auth;
5195
6421
  const existingOAuth2Clients = await fetchOAuth2Clients(config.name);
5196
- const existingNameSet = /* @__PURE__ */ new Set();
6422
+ const existingClientsMap = /* @__PURE__ */ new Map();
5197
6423
  existingOAuth2Clients.forEach((oauth2Client) => {
5198
- existingNameSet.add(oauth2Client.name);
6424
+ existingClientsMap.set(oauth2Client.name, oauth2Client.clientType);
5199
6425
  });
5200
6426
  for (const oauth2ClientName of Object.keys(config.oauth2Clients ?? {})) {
5201
6427
  const oauth2Client = config.oauth2Clients?.[oauth2ClientName];
5202
6428
  if (!oauth2Client) continue;
5203
- if (existingNameSet.has(oauth2ClientName)) {
5204
- changeSet.updates.push({
6429
+ const newOAuth2Client = protoOAuth2Client(oauth2ClientName, oauth2Client);
6430
+ if (existingClientsMap.has(oauth2ClientName)) {
6431
+ if (existingClientsMap.get(oauth2ClientName) !== newOAuth2Client.clientType) changeSet.replaces.push({
6432
+ name: oauth2ClientName,
6433
+ deleteRequest: {
6434
+ workspaceId,
6435
+ namespaceName: config.name,
6436
+ name: oauth2ClientName
6437
+ },
6438
+ createRequest: {
6439
+ workspaceId,
6440
+ namespaceName: config.name,
6441
+ oauth2Client: newOAuth2Client
6442
+ }
6443
+ });
6444
+ else changeSet.updates.push({
5205
6445
  name: oauth2ClientName,
5206
6446
  request: {
5207
6447
  workspaceId,
5208
6448
  namespaceName: config.name,
5209
- oauth2Client: protoOAuth2Client(oauth2ClientName, oauth2Client)
6449
+ oauth2Client: newOAuth2Client
5210
6450
  }
5211
6451
  });
5212
- existingNameSet.delete(oauth2ClientName);
6452
+ existingClientsMap.delete(oauth2ClientName);
5213
6453
  } else changeSet.creates.push({
5214
6454
  name: oauth2ClientName,
5215
6455
  request: {
5216
6456
  workspaceId,
5217
6457
  namespaceName: config.name,
5218
- oauth2Client: protoOAuth2Client(oauth2ClientName, oauth2Client)
6458
+ oauth2Client: newOAuth2Client
5219
6459
  }
5220
6460
  });
5221
6461
  }
5222
- existingNameSet.forEach((name) => {
6462
+ existingClientsMap.forEach((_, name) => {
5223
6463
  changeSet.deletes.push({
5224
6464
  name,
5225
6465
  request: {
@@ -7034,46 +8274,7 @@ function assertValidMigrationFiles(migrationsDir, namespace) {
7034
8274
  }
7035
8275
  }
7036
8276
  /**
7037
- * Filter a TailorDBType to match the schema state in a snapshot
7038
- * This is used when TAILOR_INTERNAL_APPLY_MIGRATION_VERSION is specified to ensure
7039
- * the deployed schema matches the specified migration version
7040
- * @param {TailorDBType} type - Local parsed type (latest state)
7041
- * @param {SnapshotType} snapshotType - Target snapshot state
7042
- * @returns {TailorDBType} Filtered type matching the snapshot
7043
- */
7044
- function filterTypeToSnapshot(type, snapshotType) {
7045
- const filteredFields = {};
7046
- if (type.fields.id) filteredFields.id = type.fields.id;
7047
- for (const fieldName of Object.keys(snapshotType.fields)) if (type.fields[fieldName]) {
7048
- const snapshotFieldConfig = snapshotType.fields[fieldName];
7049
- const localField = type.fields[fieldName];
7050
- filteredFields[fieldName] = {
7051
- ...localField,
7052
- config: {
7053
- ...localField.config,
7054
- required: snapshotFieldConfig.required,
7055
- index: snapshotFieldConfig.index || false,
7056
- unique: snapshotFieldConfig.unique || false,
7057
- ...snapshotFieldConfig.type === "enum" && snapshotFieldConfig.allowedValues && { allowedValues: snapshotFieldConfig.allowedValues.map((v) => typeof v === "string" ? { value: v } : v) }
7058
- }
7059
- };
7060
- }
7061
- const filteredIndexes = {};
7062
- if (snapshotType.indexes) for (const [indexName, indexConfig] of Object.entries(snapshotType.indexes)) filteredIndexes[indexName] = {
7063
- fields: indexConfig.fields,
7064
- unique: indexConfig.unique
7065
- };
7066
- const filteredFiles = {};
7067
- if (snapshotType.files) for (const [fileName, description] of Object.entries(snapshotType.files)) filteredFiles[fileName] = description;
7068
- return {
7069
- ...type,
7070
- fields: filteredFields,
7071
- indexes: Object.keys(filteredIndexes).length > 0 ? filteredIndexes : void 0,
7072
- files: Object.keys(filteredFiles).length > 0 ? filteredFiles : void 0
7073
- };
7074
- }
7075
- /**
7076
- * Convert remote TailorDBType to SnapshotFieldConfig for comparison
8277
+ * Convert remote ParsedTailorDBType to SnapshotFieldConfig for comparison
7077
8278
  * @param {ProtoTailorDBType} remoteType - Remote TailorDB type from API
7078
8279
  * @returns {Record<string, SnapshotFieldConfig>} Converted field configs
7079
8280
  */
@@ -7147,7 +8348,7 @@ function compareFields(typeName, fieldName, remoteField, snapshotField) {
7147
8348
  const SYSTEM_FIELDS = new Set(["id"]);
7148
8349
  /**
7149
8350
  * Compare remote TailorDB types with a local snapshot
7150
- * @param {ProtoTailorDBType[]} remoteTypes - Remote types from listTailorDBTypes API
8351
+ * @param {ProtoTailorDBType[]} remoteTypes - Remote types from listParsedTailorDBTypes API
7151
8352
  * @param {SchemaSnapshot} snapshot - Local schema snapshot
7152
8353
  * @returns {SchemaDrift[]} List of drifts detected
7153
8354
  */
@@ -7224,24 +8425,6 @@ function formatSchemaDrifts(drifts) {
7224
8425
  *
7225
8426
  * Bundles migration scripts to be executed via TestExecScript API
7226
8427
  */
7227
- const REQUIRED_PACKAGES = ["kysely", "@tailor-platform/function-kysely-tailordb"];
7228
- let dependencyCheckDone = false;
7229
- /**
7230
- * Check if required packages for migration bundling are installed.
7231
- * Logs a warning if any are missing.
7232
- */
7233
- function checkMigrationDependencies() {
7234
- if (dependencyCheckDone) return;
7235
- dependencyCheckDone = true;
7236
- const require$1 = createRequire(path.resolve(process.cwd(), "package.json"));
7237
- const missing = [];
7238
- for (const pkg of REQUIRED_PACKAGES) try {
7239
- require$1.resolve(pkg);
7240
- } catch {
7241
- missing.push(pkg);
7242
- }
7243
- if (missing.length > 0) logger.warn(`Missing optional dependencies for migration bundling: ${missing.join(", ")}. Install them in your project: pnpm add -D ${missing.join(" ")}`);
7244
- }
7245
8428
  /**
7246
8429
  * Bundle a single migration script
7247
8430
  *
@@ -7256,15 +8439,13 @@ function checkMigrationDependencies() {
7256
8439
  * @returns {Promise<MigrationBundleResult>} Bundled migration result
7257
8440
  */
7258
8441
  async function bundleMigrationScript(sourceFile, namespace, migrationNumber) {
7259
- checkMigrationDependencies();
7260
8442
  const outputDir = path.resolve(getDistDir(), "migrations");
7261
8443
  fs$2.mkdirSync(outputDir, { recursive: true });
7262
8444
  const entryPath = path.join(outputDir, `migration_${namespace}_${migrationNumber}.entry.js`);
7263
8445
  const outputPath = path.join(outputDir, `migration_${namespace}_${migrationNumber}.js`);
7264
8446
  const entryContent = ml`
7265
8447
  import { main as _migrationMain } from "${path.resolve(sourceFile).replace(/\\/g, "/")}";
7266
- import { Kysely } from "kysely";
7267
- import { TailordbDialect } from "@tailor-platform/function-kysely-tailordb";
8448
+ import { Kysely, TailordbDialect } from "@tailor-platform/sdk/kysely";
7268
8449
 
7269
8450
  function getDB(namespace) {
7270
8451
  const client = new tailordb.Client({ namespace });
@@ -7446,19 +8627,11 @@ async function getCurrentMigrationNumber(client, workspaceId, namespace) {
7446
8627
  */
7447
8628
  async function detectPendingMigrations(client, workspaceId, namespacesWithMigrations) {
7448
8629
  const pendingMigrations = [];
7449
- const maxVersionEnv = process.env.TAILOR_INTERNAL_APPLY_MIGRATION_VERSION;
7450
- const maxVersion = maxVersionEnv ? parseInt(maxVersionEnv, 10) : void 0;
7451
- if (maxVersion !== void 0 && !Number.isInteger(maxVersion)) throw new Error(`Invalid TAILOR_INTERNAL_APPLY_MIGRATION_VERSION: "${maxVersionEnv}". Must be a valid integer.`);
7452
- if (maxVersion !== void 0) {
7453
- logger.newline();
7454
- logger.info(`Limiting migrations to version ${styles.bold(formatMigrationNumber(maxVersion))} or earlier`);
7455
- }
7456
8630
  for (const { namespace, migrationsDir } of namespacesWithMigrations) {
7457
8631
  const currentMigration = await getCurrentMigrationNumber(client, workspaceId, namespace);
7458
8632
  const migrationFiles = getMigrationFiles(migrationsDir);
7459
8633
  for (const file of migrationFiles) {
7460
8634
  if (file.number <= currentMigration) continue;
7461
- if (maxVersion !== void 0 && file.number > maxVersion) continue;
7462
8635
  const diffPath = getMigrationFilePath(migrationsDir, file.number, "diff");
7463
8636
  if (!fs$2.existsSync(diffPath)) continue;
7464
8637
  const diff = loadDiff(diffPath);
@@ -7597,35 +8770,6 @@ function groupMigrationsByNamespace(migrations) {
7597
8770
  }
7598
8771
  return grouped;
7599
8772
  }
7600
- /**
7601
- * Build filtered types map for a specific migration version
7602
- * @param {number} maxVersion - Maximum migration version to reconstruct
7603
- * @param {Application} application - Application instance
7604
- * @param {LoadedConfig} config - Loaded application config (includes path)
7605
- * @returns {Promise<Map<string, Record<string, TailorDBType>>>} Filtered types by namespace
7606
- */
7607
- async function buildFilteredTypesForVersion(maxVersion, application, config) {
7608
- const namespacesWithMigrations = getNamespacesWithMigrations(config, path.dirname(config.path));
7609
- const filteredTypesByNamespace = /* @__PURE__ */ new Map();
7610
- for (const { namespace, migrationsDir } of namespacesWithMigrations) {
7611
- const snapshot = reconstructSnapshotFromMigrations(migrationsDir, maxVersion);
7612
- if (!snapshot) throw new Error(`No migrations found in ${migrationsDir}`);
7613
- const tailordb = application.tailorDBServices.find((tdb) => tdb.namespace === namespace);
7614
- if (!tailordb) throw new Error(`TailorDB service not found for namespace: ${namespace}`);
7615
- await tailordb.loadTypes();
7616
- const localTypes = tailordb.getTypes();
7617
- const filteredTypes = {};
7618
- for (const typeName of Object.keys(snapshot.types)) {
7619
- const localType = localTypes[typeName];
7620
- if (localType) {
7621
- const snapshotType = snapshot.types[typeName];
7622
- filteredTypes[typeName] = filterTypeToSnapshot(localType, snapshotType);
7623
- }
7624
- }
7625
- filteredTypesByNamespace.set(namespace, filteredTypes);
7626
- }
7627
- return filteredTypesByNamespace;
7628
- }
7629
8773
 
7630
8774
  //#endregion
7631
8775
  //#region src/cli/apply/services/tailordb/index.ts
@@ -7778,11 +8922,30 @@ async function validateAndDetectMigrations(client, workspaceId, typesByNamespace
7778
8922
  return pendingMigrations;
7779
8923
  }
7780
8924
  /**
8925
+ * Build migration execution context for script-based migrations.
8926
+ * @param client - Operator client instance
8927
+ * @param migrationContext - Planned TailorDB context
8928
+ * @param migrationsRequiringScripts - Migrations that require scripts
8929
+ * @returns Migration context for script execution
8930
+ */
8931
+ function buildMigrationContextForScripts(client, migrationContext, migrationsRequiringScripts) {
8932
+ const authService = migrationContext.application.authService;
8933
+ if (!authService) throw new Error("Auth configuration is required to execute migration scripts.");
8934
+ const dbConfigMap = {};
8935
+ for (const migration of migrationsRequiringScripts) if (!(migration.namespace in dbConfigMap)) dbConfigMap[migration.namespace] = migrationContext.config.db?.[migration.namespace];
8936
+ return {
8937
+ client,
8938
+ workspaceId: migrationContext.workspaceId,
8939
+ authNamespace: authService.config.name,
8940
+ machineUsers: authService.config.machineUsers ? Object.keys(authService.config.machineUsers) : void 0,
8941
+ dbConfig: dbConfigMap
8942
+ };
8943
+ }
8944
+ /**
7781
8945
  * Apply TailorDB-related changes for the given phase.
7782
8946
  * @param client - Operator client instance
7783
8947
  * @param result - Planned TailorDB changes
7784
8948
  * @param phase - Apply phase (defaults to "create-update")
7785
- * @returns Promise that resolves when TailorDB changes are applied
7786
8949
  */
7787
8950
  async function applyTailorDB(client, result, phase = "create-update") {
7788
8951
  const { changeSet, context: migrationContext } = result;
@@ -7798,21 +8961,8 @@ async function applyTailorDB(client, result, phase = "create-update") {
7798
8961
  processedTypes.reset();
7799
8962
  deletedResources.reset();
7800
8963
  await executeServicesCreation(client, changeSet);
7801
- let migrationCtx;
7802
8964
  const migrationsRequiringScripts = pendingMigrations.filter((m) => m.diff.requiresMigrationScript);
7803
- if (migrationsRequiringScripts.length > 0) {
7804
- const authService = migrationContext.application.authService;
7805
- if (!authService) throw new Error("Auth configuration is required to execute migration scripts.");
7806
- const dbConfigMap = {};
7807
- for (const migration of migrationsRequiringScripts) if (!(migration.namespace in dbConfigMap)) dbConfigMap[migration.namespace] = migrationContext.config.db?.[migration.namespace];
7808
- migrationCtx = {
7809
- client,
7810
- workspaceId: migrationContext.workspaceId,
7811
- authNamespace: authService.config.name,
7812
- machineUsers: authService.config.machineUsers ? Object.keys(authService.config.machineUsers) : void 0,
7813
- dbConfig: dbConfigMap
7814
- };
7815
- }
8965
+ const migrationCtx = migrationsRequiringScripts.length > 0 ? buildMigrationContextForScripts(client, migrationContext, migrationsRequiringScripts) : void 0;
7816
8966
  if (migrationsRequiringScripts.length > 0) {
7817
8967
  logger.info(`Executing ${migrationsRequiringScripts.length} data migration(s)...`);
7818
8968
  logger.newline();
@@ -7850,17 +9000,6 @@ async function applyTailorDB(client, result, phase = "create-update") {
7850
9000
  await Promise.all(changeSet.gqlPermission.deletes.map((del) => client.deleteTailorDBGQLPermission(del.request)));
7851
9001
  await Promise.all(changeSet.type.deletes.map((del) => client.deleteTailorDBType(del.request)));
7852
9002
  } else if (phase === "delete-services") await Promise.all(changeSet.service.deletes.map((del) => client.deleteTailorDBService(del.request)));
7853
- if (phase === "create-update") {
7854
- const maxVersionEnv = process.env.TAILOR_INTERNAL_APPLY_MIGRATION_VERSION;
7855
- if (maxVersionEnv) {
7856
- const maxVersion = parseInt(maxVersionEnv, 10);
7857
- if (Number.isInteger(maxVersion)) {
7858
- const configDir = path.dirname(migrationContext.config.path);
7859
- const namespacesWithMigrations = getNamespacesWithMigrations(migrationContext.config, configDir);
7860
- for (const { namespace } of namespacesWithMigrations) await updateMigrationLabel(client, migrationContext.workspaceId, namespace, maxVersion);
7861
- }
7862
- }
7863
- }
7864
9003
  }
7865
9004
  /**
7866
9005
  * Handle optional-to-required field change error with helpful message
@@ -7890,23 +9029,27 @@ function buildBreakingChangesMap(pendingMigrations) {
7890
9029
  return map;
7891
9030
  }
7892
9031
  /**
7893
- * Check if a field change requires pre/post-migration handling
7894
- * - Adding a required field (pre: add as optional, post: make required)
7895
- * - Changing optional to required (post: make required)
7896
- * - Adding unique constraint (post: add unique)
7897
- * - Removing enum values (pre: add new values only, post: remove old values)
7898
- * @param {DiffChange} change - Diff change to check
7899
- * @returns {boolean} True if the change requires pre/post-migration handling
9032
+ * Apply pre-migration schema adjustments to avoid breaking changes before scripts run.
9033
+ * @param fields - Field configs to adjust
9034
+ * @param typeChanges - Breaking changes for a type
7900
9035
  */
7901
- function isBreakingChange(change) {
7902
- const before = change.before;
7903
- const after = change.after;
7904
- if (change.kind === "field_added") return after?.required === true;
7905
- if (change.kind !== "field_modified") return false;
7906
- if (!before?.required && after?.required) return true;
7907
- if (!(before?.unique ?? false) && (after?.unique ?? false)) return true;
7908
- if (before?.allowedValues && after?.allowedValues) return before.allowedValues.some((v) => !after.allowedValues.includes(v));
7909
- return false;
9036
+ function applyPreMigrationFieldAdjustments(fields, typeChanges) {
9037
+ for (const [fieldName, change] of typeChanges) {
9038
+ const field = fields[fieldName];
9039
+ if (!field) continue;
9040
+ const before = change.before;
9041
+ const after = change.after;
9042
+ if (change.kind === "field_added" && after?.required) field.required = false;
9043
+ if (change.kind !== "field_modified") continue;
9044
+ if (!before?.required && after?.required) field.required = false;
9045
+ if (!(before?.unique ?? false) && (after?.unique ?? false)) field.unique = false;
9046
+ if (before?.allowedValues && after?.allowedValues) {
9047
+ if (before.allowedValues.filter((value) => !after.allowedValues.includes(value)).length > 0) field.allowedValues = [...new Set([...before.allowedValues, ...after.allowedValues])].map((value) => ({
9048
+ value,
9049
+ description: ""
9050
+ }));
9051
+ }
9052
+ }
7910
9053
  }
7911
9054
  /**
7912
9055
  * Get the set of type names affected by a migration
@@ -7946,11 +9089,11 @@ async function executeServicesCreation(client, changeSet) {
7946
9089
  const processedTypes = {
7947
9090
  created: /* @__PURE__ */ new Set(),
7948
9091
  updated: /* @__PURE__ */ new Set(),
7949
- gqlPermissionsProcessed: false,
9092
+ gqlPermissionsProcessed: /* @__PURE__ */ new Set(),
7950
9093
  reset() {
7951
9094
  this.created.clear();
7952
9095
  this.updated.clear();
7953
- this.gqlPermissionsProcessed = false;
9096
+ this.gqlPermissionsProcessed.clear();
7954
9097
  }
7955
9098
  };
7956
9099
  /**
@@ -7963,57 +9106,68 @@ const processedTypes = {
7963
9106
  async function executeSingleMigrationPrePhase(client, changeSet, migration) {
7964
9107
  const breakingChanges = buildBreakingChangesMap([migration]);
7965
9108
  const affectedTypes = getAffectedTypeNames(migration);
7966
- await Promise.all([...changeSet.type.creates.filter((create$1) => {
7967
- const typeName = create$1.request.tailordbType?.name;
7968
- return typeName && affectedTypes.has(typeName) && !processedTypes.created.has(typeName);
7969
- }).map((create$1) => {
7970
- const typeName = create$1.request.tailordbType?.name;
7971
- if (typeName) processedTypes.created.add(typeName);
7972
- const typeChanges = typeName ? breakingChanges.get(typeName) : void 0;
7973
- if (!typeChanges || typeChanges.size === 0) return client.createTailorDBType(create$1.request);
7974
- const clonedRequest = structuredClone(create$1.request);
7975
- if (clonedRequest.tailordbType?.schema?.fields) {
7976
- const fields = clonedRequest.tailordbType.schema.fields;
7977
- for (const [fieldName, change] of typeChanges) {
7978
- if (!isBreakingChange(change) || !fields[fieldName]) continue;
7979
- const after = change.after;
7980
- if (change.kind === "field_added" && after?.required) fields[fieldName].required = false;
7981
- }
7982
- }
7983
- return client.createTailorDBType(clonedRequest);
7984
- }), ...changeSet.type.updates.filter((update) => {
7985
- const typeName = update.request.tailordbType?.name;
7986
- return typeName && affectedTypes.has(typeName);
7987
- }).map((update) => {
7988
- const typeName = update.request.tailordbType?.name;
7989
- if (typeName) processedTypes.updated.add(typeName);
7990
- const typeChanges = typeName ? breakingChanges.get(typeName) : void 0;
7991
- if (!typeChanges || typeChanges.size === 0) return client.updateTailorDBType(update.request);
7992
- const clonedRequest = structuredClone(update.request);
7993
- if (clonedRequest.tailordbType?.schema?.fields) {
7994
- const fields = clonedRequest.tailordbType.schema.fields;
7995
- for (const [fieldName, change] of typeChanges) {
7996
- if (!isBreakingChange(change) || !fields[fieldName]) continue;
7997
- const before = change.before;
7998
- const after = change.after;
7999
- if (change.kind === "field_added" && after?.required) fields[fieldName].required = false;
8000
- if (change.kind === "field_modified" && !(before?.unique ?? false) && (after?.unique ?? false)) fields[fieldName].unique = false;
8001
- if (change.kind === "field_modified" && before?.allowedValues && after?.allowedValues) {
8002
- if (before.allowedValues.filter((v) => !after.allowedValues.includes(v)).length > 0) {
8003
- const unionValues = [...new Set([...before.allowedValues, ...after.allowedValues])];
8004
- fields[fieldName].allowedValues = unionValues.map((v) => ({
8005
- value: v,
8006
- description: ""
8007
- }));
8008
- }
8009
- }
8010
- }
8011
- }
8012
- return client.updateTailorDBType(clonedRequest);
8013
- })]);
8014
- if (!processedTypes.gqlPermissionsProcessed) {
8015
- processedTypes.gqlPermissionsProcessed = true;
8016
- await Promise.all([...changeSet.gqlPermission.creates.map((create$1) => client.createTailorDBGQLPermission(create$1.request)), ...changeSet.gqlPermission.updates.map((update) => client.updateTailorDBGQLPermission(update.request))]);
9109
+ const createdBeforeMigration = new Set(processedTypes.created);
9110
+ await Promise.all([
9111
+ ...changeSet.type.creates.filter((create$1) => {
9112
+ const typeName = create$1.request.tailordbType?.name;
9113
+ return typeName && affectedTypes.has(typeName) && !createdBeforeMigration.has(typeName);
9114
+ }).map((create$1) => {
9115
+ const typeName = create$1.request.tailordbType?.name;
9116
+ if (typeName) processedTypes.created.add(typeName);
9117
+ const typeChanges = typeName ? breakingChanges.get(typeName) : void 0;
9118
+ if (!typeChanges || typeChanges.size === 0) return client.createTailorDBType(create$1.request);
9119
+ const clonedRequest = structuredClone(create$1.request);
9120
+ if (clonedRequest.tailordbType?.schema?.fields) applyPreMigrationFieldAdjustments(clonedRequest.tailordbType.schema.fields, typeChanges);
9121
+ return client.createTailorDBType(clonedRequest);
9122
+ }),
9123
+ ...changeSet.type.creates.filter((create$1) => {
9124
+ const typeName = create$1.request.tailordbType?.name;
9125
+ return typeName && affectedTypes.has(typeName) && createdBeforeMigration.has(typeName);
9126
+ }).map((create$1) => {
9127
+ const typeName = create$1.request.tailordbType?.name;
9128
+ if (typeName) processedTypes.updated.add(typeName);
9129
+ const typeChanges = typeName ? breakingChanges.get(typeName) : void 0;
9130
+ if (!typeChanges || typeChanges.size === 0) return client.updateTailorDBType({
9131
+ workspaceId: create$1.request.workspaceId,
9132
+ namespaceName: create$1.request.namespaceName,
9133
+ tailordbType: create$1.request.tailordbType
9134
+ });
9135
+ const clonedRequest = structuredClone(create$1.request);
9136
+ if (clonedRequest.tailordbType?.schema?.fields) applyPreMigrationFieldAdjustments(clonedRequest.tailordbType.schema.fields, typeChanges);
9137
+ return client.updateTailorDBType({
9138
+ workspaceId: create$1.request.workspaceId,
9139
+ namespaceName: create$1.request.namespaceName,
9140
+ tailordbType: clonedRequest.tailordbType
9141
+ });
9142
+ }),
9143
+ ...changeSet.type.updates.filter((update) => {
9144
+ const typeName = update.request.tailordbType?.name;
9145
+ return typeName && affectedTypes.has(typeName);
9146
+ }).map((update) => {
9147
+ const typeName = update.request.tailordbType?.name;
9148
+ if (typeName) processedTypes.updated.add(typeName);
9149
+ const typeChanges = typeName ? breakingChanges.get(typeName) : void 0;
9150
+ if (!typeChanges || typeChanges.size === 0) return client.updateTailorDBType(update.request);
9151
+ const clonedRequest = structuredClone(update.request);
9152
+ if (clonedRequest.tailordbType?.schema?.fields) applyPreMigrationFieldAdjustments(clonedRequest.tailordbType.schema.fields, typeChanges);
9153
+ return client.updateTailorDBType(clonedRequest);
9154
+ })
9155
+ ]);
9156
+ if (!processedTypes.gqlPermissionsProcessed.has(migration.namespace)) {
9157
+ const gqlPermissionCreatesForNamespace = changeSet.gqlPermission.creates.filter((create$1) => create$1.request.namespaceName === migration.namespace);
9158
+ const gqlPermissionUpdatesForNamespace = changeSet.gqlPermission.updates.filter((update) => update.request.namespaceName === migration.namespace);
9159
+ const gqlPermissionTypeNames = new Set(gqlPermissionCreatesForNamespace.map((create$1) => create$1.name));
9160
+ const missingTypeCreates = changeSet.type.creates.filter((create$1) => {
9161
+ const typeName = create$1.request.tailordbType?.name;
9162
+ return create$1.request.namespaceName === migration.namespace && typeName && gqlPermissionTypeNames.has(typeName) && !processedTypes.created.has(typeName);
9163
+ });
9164
+ if (missingTypeCreates.length > 0) await Promise.all(missingTypeCreates.map((create$1) => {
9165
+ const typeName = create$1.request.tailordbType?.name;
9166
+ if (typeName) processedTypes.created.add(typeName);
9167
+ return client.createTailorDBType(create$1.request);
9168
+ }));
9169
+ processedTypes.gqlPermissionsProcessed.add(migration.namespace);
9170
+ await Promise.all([...gqlPermissionCreatesForNamespace.map((create$1) => client.createTailorDBGQLPermission(create$1.request)), ...gqlPermissionUpdatesForNamespace.map((update) => client.updateTailorDBGQLPermission(update.request))]);
8017
9171
  }
8018
9172
  }
8019
9173
  /**
@@ -8090,23 +9244,9 @@ async function planTailorDB(context) {
8090
9244
  tailordbs.push(tailordb);
8091
9245
  }
8092
9246
  const executors = forRemoval ? [] : Object.values(await application.executorService?.loadExecutors() ?? {});
8093
- let filteredTypesByNamespace;
8094
- let skipSchemaCheck = false;
8095
- if (!forRemoval) {
8096
- const maxVersionEnv = process.env.TAILOR_INTERNAL_APPLY_MIGRATION_VERSION;
8097
- if (maxVersionEnv) {
8098
- const maxVersion = parseInt(maxVersionEnv, 10);
8099
- if (!Number.isInteger(maxVersion)) throw new Error(`Invalid TAILOR_INTERNAL_APPLY_MIGRATION_VERSION: "${maxVersionEnv}". Must be a valid integer.`);
8100
- logger.info(`Using schema reconstructed up to migration version ${styles.bold(formatMigrationNumber(maxVersion))}`);
8101
- logger.info("Schema check will be skipped (local types are ahead of target version)", { mode: "plain" });
8102
- logger.newline();
8103
- skipSchemaCheck = true;
8104
- filteredTypesByNamespace = await buildFilteredTypesForVersion(maxVersion, application, config);
8105
- }
8106
- }
8107
9247
  const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices(client, workspaceId, application.name, tailordbs);
8108
9248
  const deletedServices = serviceChangeSet.deletes.map((del) => del.name);
8109
- const typeChangeSet = await planTypes(client, workspaceId, tailordbs, executors, deletedServices, filteredTypesByNamespace);
9249
+ const typeChangeSet = await planTypes(client, workspaceId, tailordbs, executors, deletedServices);
8110
9250
  const gqlPermissionChangeSet = await planGqlPermissions(client, workspaceId, tailordbs, deletedServices);
8111
9251
  serviceChangeSet.print();
8112
9252
  typeChangeSet.print();
@@ -8124,7 +9264,7 @@ async function planTailorDB(context) {
8124
9264
  workspaceId,
8125
9265
  application,
8126
9266
  config,
8127
- noSchemaCheck: skipSchemaCheck || (noSchemaCheck ?? false)
9267
+ noSchemaCheck: noSchemaCheck ?? false
8128
9268
  }
8129
9269
  };
8130
9270
  }
@@ -8919,17 +10059,59 @@ async function loadWorkflowScripts() {
8919
10059
  * @returns Promise that resolves when apply completes
8920
10060
  */
8921
10061
  async function apply(options) {
8922
- const { config } = await loadConfig(options?.configPath);
10062
+ const { config, plugins } = await loadConfig(options?.configPath);
8923
10063
  const dryRun = options?.dryRun ?? false;
8924
10064
  const yes = options?.yes ?? false;
8925
10065
  const buildOnly = options?.buildOnly ?? process.env.TAILOR_PLATFORM_SDK_BUILD_ONLY === "true";
8926
- await generateUserTypes(config, config.path);
8927
- const application = defineApplication(config);
10066
+ let pluginManager;
10067
+ if (plugins.length > 0) pluginManager = new PluginManager(plugins);
10068
+ await generateUserTypes({
10069
+ config,
10070
+ configPath: config.path,
10071
+ plugins
10072
+ });
10073
+ const application = defineApplication({
10074
+ config,
10075
+ pluginManager
10076
+ });
8928
10077
  let workflowResult;
8929
10078
  if (application.workflowConfig) workflowResult = await loadAndCollectJobs(application.workflowConfig);
8930
10079
  const triggerContext = await buildTriggerContext(application.workflowConfig);
10080
+ let tailordbTypesLoaded = false;
10081
+ let pluginExecutorFiles = [];
10082
+ if (application.tailorDBServices.length > 0) {
10083
+ for (const tailordb of application.tailorDBServices) {
10084
+ await tailordb.loadTypes();
10085
+ await tailordb.processNamespacePlugins();
10086
+ }
10087
+ tailordbTypesLoaded = true;
10088
+ }
10089
+ const pluginOutputDir = path.join(getDistDir(), "plugin");
10090
+ const typeGenerationResult = generatePluginTypeFiles(pluginManager?.getPluginGeneratedTypes() ?? [], pluginOutputDir);
10091
+ const sourceTypeInfoMap = /* @__PURE__ */ new Map();
10092
+ for (const db$1 of application.tailorDBServices) {
10093
+ const typeSourceInfo = db$1.getTypeSourceInfo();
10094
+ for (const [typeName, sourceInfo] of Object.entries(typeSourceInfo)) if (sourceInfo.filePath) sourceTypeInfoMap.set(typeName, {
10095
+ filePath: sourceInfo.filePath,
10096
+ exportName: sourceInfo.exportName
10097
+ });
10098
+ }
10099
+ const generatedExecutorFiles = generatePluginExecutorFiles(pluginManager?.getPluginGeneratedExecutorsWithImportPath() ?? [], pluginOutputDir, typeGenerationResult, sourceTypeInfoMap, config.path);
10100
+ const pluginExecutorFileSet = new Set(generatedExecutorFiles);
10101
+ const legacyPluginExecutorDir = path.join(getDistDir(), "plugin-executors");
10102
+ if (fs$2.existsSync(legacyPluginExecutorDir)) {
10103
+ const legacyFiles = fs$2.readdirSync(legacyPluginExecutorDir).filter((f) => f.endsWith(".ts")).map((f) => path.join(legacyPluginExecutorDir, f));
10104
+ for (const file of legacyFiles) pluginExecutorFileSet.add(file);
10105
+ }
10106
+ pluginExecutorFiles = Array.from(pluginExecutorFileSet);
10107
+ const executorService = application.executorService ?? (pluginExecutorFiles.length > 0 ? createExecutorService({
10108
+ config: { files: [] },
10109
+ pluginManager
10110
+ }) : void 0);
10111
+ const mutableApplication = application;
10112
+ mutableApplication.executorService = executorService;
8931
10113
  for (const app$1 of application.applications) for (const pipeline$1 of app$1.resolverServices) await buildPipeline(pipeline$1.namespace, pipeline$1.config, triggerContext);
8932
- if (application.executorService) await buildExecutor(application.executorService.config, triggerContext);
10114
+ if (executorService) await buildExecutor(executorService.config, triggerContext, pluginExecutorFiles);
8933
10115
  let workflowBuildResult;
8934
10116
  if (workflowResult && workflowResult.jobs.length > 0) {
8935
10117
  const mainJobNames = workflowResult.workflowSources.map((ws) => ws.workflow.mainJob.name);
@@ -8944,9 +10126,15 @@ async function apply(options) {
8944
10126
  workspaceId: options?.workspaceId,
8945
10127
  profile: options?.profile
8946
10128
  });
8947
- for (const tailordb of application.tailorDBServices) await tailordb.loadTypes();
10129
+ if (!tailordbTypesLoaded) for (const tailordb of application.tailorDBServices) {
10130
+ await tailordb.loadTypes();
10131
+ await tailordb.processNamespacePlugins();
10132
+ }
8948
10133
  for (const pipeline$1 of application.resolverServices) await pipeline$1.loadResolvers();
8949
- if (application.executorService) await application.executorService.loadExecutors();
10134
+ if (executorService) {
10135
+ await executorService.loadExecutors();
10136
+ if (pluginExecutorFiles.length > 0) await executorService.loadPluginExecutorFiles(pluginExecutorFiles);
10137
+ }
8950
10138
  if (workflowResult) printLoadedWorkflows(workflowResult);
8951
10139
  logger.newline();
8952
10140
  const ctx = {
@@ -8993,6 +10181,14 @@ async function apply(options) {
8993
10181
  resourceType: "StaticWebsite",
8994
10182
  resourceName: del.name
8995
10183
  });
10184
+ for (const del of auth.changeSet.oauth2Client.deletes) importantDeletions.push({
10185
+ resourceType: "OAuth2 client",
10186
+ resourceName: del.name
10187
+ });
10188
+ for (const replace of auth.changeSet.oauth2Client.replaces) importantDeletions.push({
10189
+ resourceType: "OAuth2 client (client type change)",
10190
+ resourceName: replace.name
10191
+ });
8996
10192
  await confirmImportantResourceDeletion(importantDeletions, yes);
8997
10193
  const resourceOwners = new Set([
8998
10194
  ...tailorDB.resourceOwners,
@@ -9015,10 +10211,10 @@ async function apply(options) {
9015
10211
  logger.info("Dry run enabled. No changes applied.");
9016
10212
  return;
9017
10213
  }
9018
- await applyTailorDB(client, tailorDB, "create-update");
9019
10214
  await applyStaticWebsite(client, staticWebsite, "create-update");
9020
10215
  await applyIdP(client, idp, "create-update");
9021
10216
  await applyAuth(client, auth, "create-update");
10217
+ await applyTailorDB(client, tailorDB, "create-update");
9022
10218
  await applyPipeline(client, pipeline, "create-update");
9023
10219
  await applyPipeline(client, pipeline, "delete-resources");
9024
10220
  await applyAuth(client, auth, "delete-resources");
@@ -9039,8 +10235,12 @@ async function apply(options) {
9039
10235
  async function buildPipeline(namespace, config, triggerContext) {
9040
10236
  await bundleResolvers(namespace, config, triggerContext);
9041
10237
  }
9042
- async function buildExecutor(config, triggerContext) {
9043
- await bundleExecutors(config, triggerContext);
10238
+ async function buildExecutor(config, triggerContext, additionalFiles) {
10239
+ await bundleExecutors({
10240
+ config,
10241
+ triggerContext,
10242
+ additionalFiles
10243
+ });
9044
10244
  }
9045
10245
  async function buildWorkflow(collectedJobs, mainJobNames, env, triggerContext) {
9046
10246
  return bundleWorkflowJobs(collectedJobs, mainJobNames, env, triggerContext);
@@ -11154,11 +12354,17 @@ function createDependencyWatcher(options = {}) {
11154
12354
  * Creates a generation manager.
11155
12355
  * @param config - Application configuration
11156
12356
  * @param generators - List of generators
12357
+ * @param plugins - List of plugins
11157
12358
  * @param configPath - Path to the configuration file
11158
12359
  * @returns GenerationManager instance
11159
12360
  */
11160
- function createGenerationManager(config, generators = [], configPath) {
11161
- const application = defineApplication(config);
12361
+ function createGenerationManager(config, generators = [], plugins = [], configPath) {
12362
+ let pluginManager;
12363
+ if (plugins.length > 0) pluginManager = new PluginManager(plugins);
12364
+ const application = defineApplication({
12365
+ config,
12366
+ pluginManager
12367
+ });
11162
12368
  const baseDir = path.join(getDistDir(), "generated");
11163
12369
  fs$2.mkdirSync(baseDir, { recursive: true });
11164
12370
  const services = {
@@ -11208,7 +12414,8 @@ function createGenerationManager(config, generators = [], configPath) {
11208
12414
  results.tailordbResults[namespace][typeName] = await processType({
11209
12415
  type,
11210
12416
  namespace,
11211
- source: typeInfo.sourceInfo[typeName]
12417
+ source: typeInfo.sourceInfo[typeName],
12418
+ plugins: typeInfo.pluginAttachments.get(typeName) ?? []
11212
12419
  });
11213
12420
  } catch (error) {
11214
12421
  logger.error(`Error processing type ${styles.bold(typeName)} in ${namespace} with generator ${gen.id}`);
@@ -11381,13 +12588,15 @@ function createGenerationManager(config, generators = [], configPath) {
11381
12588
  logger.newline();
11382
12589
  logger.log(`Generation for application: ${styles.highlight(application.config.name)}`);
11383
12590
  const app = application;
11384
- for (const db of app.tailorDBServices) {
11385
- const namespace = db.namespace;
12591
+ for (const db$1 of app.tailorDBServices) {
12592
+ const namespace = db$1.namespace;
11386
12593
  try {
11387
- await db.loadTypes();
12594
+ await db$1.loadTypes();
12595
+ await db$1.processNamespacePlugins();
11388
12596
  services.tailordb[namespace] = {
11389
- types: db.getTypes(),
11390
- sourceInfo: db.getTypeSourceInfo()
12597
+ types: db$1.getTypes(),
12598
+ sourceInfo: db$1.getTypeSourceInfo(),
12599
+ pluginAttachments: db$1.getPluginAttachments()
11391
12600
  };
11392
12601
  } catch (error) {
11393
12602
  logger.error(`Error loading types for TailorDB service ${styles.bold(namespace)}`);
@@ -11395,8 +12604,23 @@ function createGenerationManager(config, generators = [], configPath) {
11395
12604
  if (!watch$1) throw error;
11396
12605
  }
11397
12606
  }
12607
+ const pluginOutputDir = path.join(getDistDir(), "plugin");
12608
+ const typeGenerationResult = generatePluginTypeFiles(pluginManager?.getPluginGeneratedTypes() ?? [], pluginOutputDir);
12609
+ const sourceTypeInfoMap = /* @__PURE__ */ new Map();
12610
+ for (const db$1 of app.tailorDBServices) {
12611
+ const typeSourceInfo = db$1.getTypeSourceInfo();
12612
+ for (const [typeName, sourceInfo] of Object.entries(typeSourceInfo)) if (sourceInfo.filePath) sourceTypeInfoMap.set(typeName, {
12613
+ filePath: sourceInfo.filePath,
12614
+ exportName: sourceInfo.exportName
12615
+ });
12616
+ }
12617
+ const generatedExecutorFiles = generatePluginExecutorFiles(pluginManager?.getPluginGeneratedExecutorsWithImportPath() ?? [], pluginOutputDir, typeGenerationResult, sourceTypeInfoMap, configPath);
12618
+ const executorService = application.executorService ?? (generatedExecutorFiles.length > 0 ? createExecutorService({
12619
+ config: { files: [] },
12620
+ pluginManager
12621
+ }) : void 0);
11398
12622
  if (app.authService) await app.authService.resolveNamespaces();
11399
- if (app.tailorDBServices.length > 0) logger.newline();
12623
+ if (app.tailorDBServices.length > 0 || generatedExecutorFiles.length > 0) logger.newline();
11400
12624
  const tailordbOnlyGens = generators.filter((g) => onlyHas(g, "tailordb"));
11401
12625
  if (tailordbOnlyGens.length > 0) {
11402
12626
  await runGenerators(tailordbOnlyGens, watch$1);
@@ -11421,9 +12645,11 @@ function createGenerationManager(config, generators = [], configPath) {
11421
12645
  await runGenerators(nonExecutorGens, watch$1);
11422
12646
  logger.newline();
11423
12647
  }
11424
- const executors = await application.executorService?.loadExecutors();
11425
- Object.entries(executors ?? {}).forEach(([filePath, executor]) => {
11426
- services.executor[filePath] = executor;
12648
+ await executorService?.loadExecutors();
12649
+ if (generatedExecutorFiles.length > 0) await executorService?.loadPluginExecutorFiles(generatedExecutorFiles);
12650
+ const allExecutors = executorService?.getExecutors() ?? {};
12651
+ Object.entries(allExecutors).forEach(([key, executor]) => {
12652
+ services.executor[key] = executor;
11427
12653
  });
11428
12654
  const executorGens = generators.filter((g) => hasAll(g, "executor"));
11429
12655
  if (executorGens.length > 0) {
@@ -11438,9 +12664,9 @@ function createGenerationManager(config, generators = [], configPath) {
11438
12664
  });
11439
12665
  if (configPath) await watcher.addWatchGroup("Config", [configPath]);
11440
12666
  const app = application;
11441
- for (const db of app.tailorDBServices) {
11442
- const dbNamespace = db.namespace;
11443
- await watcher?.addWatchGroup(`TailorDB/${dbNamespace}`, db.config.files);
12667
+ for (const db$1 of app.tailorDBServices) {
12668
+ const dbNamespace = db$1.namespace;
12669
+ await watcher?.addWatchGroup(`TailorDB/${dbNamespace}`, db$1.config.files);
11444
12670
  }
11445
12671
  for (const resolverService of app.resolverServices) {
11446
12672
  const resolverNamespace = resolverService.namespace;
@@ -11456,10 +12682,14 @@ function createGenerationManager(config, generators = [], configPath) {
11456
12682
  * @returns Promise that resolves when generation (and watch, if enabled) completes
11457
12683
  */
11458
12684
  async function generate$1(options) {
11459
- const { config, generators } = await loadConfig(options?.configPath);
12685
+ const { config, generators, plugins } = await loadConfig(options?.configPath);
11460
12686
  const watch$1 = options?.watch ?? false;
11461
- await generateUserTypes(config, config.path);
11462
- const manager = createGenerationManager(config, generators, config.path);
12687
+ await generateUserTypes({
12688
+ config,
12689
+ configPath: config.path,
12690
+ plugins
12691
+ });
12692
+ const manager = createGenerationManager(config, generators, plugins, config.path);
11463
12693
  await manager.generate(watch$1);
11464
12694
  if (watch$1) await manager.watch();
11465
12695
  }
@@ -11779,7 +13009,7 @@ async function loadOptions$9(options) {
11779
13009
  return {
11780
13010
  client,
11781
13011
  workspaceId,
11782
- application: defineApplication(config),
13012
+ application: defineApplication({ config }),
11783
13013
  config
11784
13014
  };
11785
13015
  }
@@ -12008,7 +13238,7 @@ function generateDbTypesFromSnapshot(snapshot, diff) {
12008
13238
  " * DO NOT EDIT - This file is auto-generated by the migration system.",
12009
13239
  " */",
12010
13240
  "",
12011
- `import { ${imports.join(", ")} } from "kysely";`,
13241
+ `import { ${imports.join(", ")} } from "@tailor-platform/sdk/kysely";`,
12012
13242
  "",
12013
13243
  ...utilityTypeDeclarations,
12014
13244
  "",
@@ -12033,7 +13263,7 @@ function generateEmptyDbTypes(namespace) {
12033
13263
  " * DO NOT EDIT - This file is auto-generated by the migration system.",
12034
13264
  " */",
12035
13265
  "",
12036
- "import { type Transaction as KyselyTransaction } from \"kysely\";",
13266
+ "import { type Transaction as KyselyTransaction } from \"@tailor-platform/sdk/kysely\";",
12037
13267
  "",
12038
13268
  "// eslint-disable-next-line @typescript-eslint/no-empty-object-type",
12039
13269
  "interface Database {}",
@@ -12420,7 +13650,7 @@ async function handleInitOption(namespaces, skipConfirmation) {
12420
13650
  */
12421
13651
  async function generate(options) {
12422
13652
  logBetaWarning("tailordb migration");
12423
- const { config } = await loadConfig(options.configPath);
13653
+ const { config, plugins } = await loadConfig(options.configPath);
12424
13654
  const namespacesWithMigrations = getNamespacesWithMigrations(config, path.dirname(config.path));
12425
13655
  if (namespacesWithMigrations.length === 0) {
12426
13656
  logger.warn("No TailorDB namespaces with migrations config found.");
@@ -12428,8 +13658,13 @@ async function generate(options) {
12428
13658
  return;
12429
13659
  }
12430
13660
  if (options.init) await handleInitOption(namespacesWithMigrations, options.yes);
12431
- const { defineApplication: defineApplication$1 } = await import("./application-a12-7TT3.mjs");
12432
- const application = defineApplication$1(config);
13661
+ let pluginManager;
13662
+ if (plugins.length > 0) pluginManager = new PluginManager(plugins);
13663
+ const { defineApplication: defineApplication$1 } = await import("./application-DM4zTgXU.mjs");
13664
+ const application = defineApplication$1({
13665
+ config,
13666
+ pluginManager
13667
+ });
12433
13668
  for (const { namespace, migrationsDir } of namespacesWithMigrations) {
12434
13669
  logger.info(`Processing namespace: ${styles.bold(namespace)}`);
12435
13670
  assertValidMigrationFiles(migrationsDir, namespace);
@@ -12439,6 +13674,7 @@ async function generate(options) {
12439
13674
  continue;
12440
13675
  }
12441
13676
  await tailordbService.loadTypes();
13677
+ await tailordbService.processNamespacePlugins();
12442
13678
  const currentSnapshot = createSnapshotFromLocalTypes(tailordbService.getTypes(), namespace);
12443
13679
  let previousSnapshot = null;
12444
13680
  try {
@@ -13634,5 +14870,5 @@ const updateCommand = defineCommand({
13634
14870
  });
13635
14871
 
13636
14872
  //#endregion
13637
- export { jobsCommand as $, readPackageJson as $t, generateCommand as A, getNextMigrationNumber as At, getMachineUserToken as B, loadConfig as Bt, resumeCommand as C, compareSnapshots as Ct, truncate as D, getMigrationDirPath as Dt, listWorkflows as E, getLatestMigrationNumber as Et, removeCommand$1 as F, formatMigrationDiff as Ft, generateCommand$1 as G, loadAccessToken as Gt, listCommand$5 as H, apiCall as Ht, listCommand$4 as I, hasChanges as It, triggerCommand as J, writePlatformConfig as Jt, listWebhookExecutors as K, loadWorkspaceId as Kt, listOAuth2Clients as L, getNamespacesWithMigrations as Lt, show as M, loadDiff as Mt, showCommand as N, reconstructSnapshotFromMigrations as Nt, truncateCommand as O, getMigrationFilePath as Ot, remove as P, formatDiffSummary as Pt, getExecutorJob as Q, initOperatorClient as Qt, getCommand$1 as R, trnPrefix as Rt, healthCommand as S, compareLocalTypesWithSnapshot as St, listCommand$3 as T, formatMigrationNumber as Tt, listMachineUsers as U, apiCommand as Ut, tokenCommand as V, getDistDir as Vt, generate$1 as W, fetchLatestToken as Wt, listCommand$6 as X, fetchUserInfo as Xt, triggerExecutor as Y, fetchAll as Yt, listExecutors as Z, initOAuth2Client as Zt, createCommand as _, DB_TYPES_FILE_NAME as _t, listCommand as a, withCommonArgs as an, getWorkflow as at, listCommand$2 as b, MIGRATE_FILE_NAME as bt, inviteUser as c, listWorkflowExecutions as ct, listCommand$1 as d, apply as dt, PATScope as en, listExecutorJobs as et, listWorkspaces as f, applyCommand as ft, deleteWorkspace as g, parseMigrationLabelNumber as gt, deleteCommand as h, MIGRATION_LABEL_KEY as ht, removeUser as i, jsonArgs as in, getCommand$2 as it, logBetaWarning as j, isValidMigrationNumber as jt, generate as k, getMigrationFiles as kt, restoreCommand as l, getCommand$3 as lt, getWorkspace as m, waitForExecution$1 as mt, updateUser as n, confirmationArgs as nn, startCommand as nt, listUsers as o, workspaceArgs as on, executionsCommand as ot, getCommand as p, executeScript as pt, webhookCommand as q, readPlatformConfig as qt, removeCommand as r, deploymentArgs as rn, startWorkflow as rt, inviteCommand as s, getWorkflowExecution as st, updateCommand as t, commonArgs as tn, watchExecutorJob as tt, restoreWorkspace as u, getExecutor as ut, createWorkspace as v, DIFF_FILE_NAME as vt, resumeWorkflow as w, createSnapshotFromLocalTypes as wt, getAppHealth as x, SCHEMA_FILE_NAME as xt, listApps as y, INITIAL_SCHEMA_NUMBER as yt, getOAuth2Client as z, generateUserTypes as zt };
13638
- //# sourceMappingURL=update-D0muqqOP.mjs.map
14873
+ export { jobsCommand as $, initOperatorClient as $t, generateCommand as A, getMigrationFiles as At, getMachineUserToken as B, generateUserTypes as Bt, resumeCommand as C, compareLocalTypesWithSnapshot as Ct, truncate as D, getLatestMigrationNumber as Dt, listWorkflows as E, formatMigrationNumber as Et, removeCommand$1 as F, formatDiffSummary as Ft, generateCommand$1 as G, fetchLatestToken as Gt, listCommand$5 as H, getDistDir as Ht, listCommand$4 as I, formatMigrationDiff as It, triggerCommand as J, readPlatformConfig as Jt, listWebhookExecutors as K, loadAccessToken as Kt, listOAuth2Clients as L, hasChanges as Lt, show as M, isValidMigrationNumber as Mt, showCommand as N, loadDiff as Nt, truncateCommand as O, getMigrationDirPath as Ot, remove as P, reconstructSnapshotFromMigrations as Pt, getExecutorJob as Q, initOAuth2Client as Qt, getCommand$1 as R, getNamespacesWithMigrations as Rt, healthCommand as S, SCHEMA_FILE_NAME as St, listCommand$3 as T, createSnapshotFromLocalTypes as Tt, listMachineUsers as U, apiCall as Ut, tokenCommand as V, loadConfig as Vt, generate$1 as W, apiCommand as Wt, listCommand$6 as X, fetchAll as Xt, triggerExecutor as Y, writePlatformConfig as Yt, listExecutors as Z, fetchUserInfo as Zt, createCommand as _, bundleMigrationScript as _t, listCommand as a, jsonArgs as an, getWorkflow as at, listCommand$2 as b, INITIAL_SCHEMA_NUMBER as bt, inviteUser as c, listWorkflowExecutions as ct, listCommand$1 as d, apply as dt, readPackageJson as en, listExecutorJobs as et, listWorkspaces as f, applyCommand as ft, deleteWorkspace as g, parseMigrationLabelNumber as gt, deleteCommand as h, MIGRATION_LABEL_KEY as ht, removeUser as i, deploymentArgs as in, getCommand$2 as it, logBetaWarning as j, getNextMigrationNumber as jt, generate as k, getMigrationFilePath as kt, restoreCommand as l, getCommand$3 as lt, getWorkspace as m, waitForExecution$1 as mt, updateUser as n, commonArgs as nn, startCommand as nt, listUsers as o, withCommonArgs as on, executionsCommand as ot, getCommand as p, executeScript as pt, webhookCommand as q, loadWorkspaceId as qt, removeCommand as r, confirmationArgs as rn, startWorkflow as rt, inviteCommand as s, workspaceArgs as sn, getWorkflowExecution as st, updateCommand as t, PATScope as tn, watchExecutorJob as tt, restoreWorkspace as u, getExecutor as ut, createWorkspace as v, DB_TYPES_FILE_NAME as vt, resumeWorkflow as w, compareSnapshots as wt, getAppHealth as x, MIGRATE_FILE_NAME as xt, listApps as y, DIFF_FILE_NAME as yt, getOAuth2Client as z, trnPrefix as zt };
14874
+ //# sourceMappingURL=update-B_W-UQnS.mjs.map